Compare commits

...

701 Commits

Author SHA1 Message Date
Tim Donohue
2fe9fbd584 Merge pull request #1376 from atmire/remove-authorization-mocks
remove authorization mocks left over from PR #1105
2021-10-26 11:34:29 -05:00
Art Lowel
26a3ea4178 remove authorization mocks left over from PR #1105 2021-10-26 17:27:39 +02:00
Tim Donohue
30b9eae872 Merge pull request #1375 from 4Science/CST-4767
[CST-4767] Password are not properly url encoded at login
2021-10-26 08:41:20 -05:00
Davide Negretti
035a7826ba [CST-4767] encode-decode.util.ts removed 2021-10-26 11:11:21 +02:00
Tim Donohue
e8bebea543 Merge pull request #1368 from tdonohue/initial_rest_test_script
Add script to test connection to REST API (yarn config:check:rest)
2021-10-25 12:13:15 -05:00
Tim Donohue
f926b53848 Merge pull request #1374 from the-library-code/german-message-keys
Contribution to the German translation of DSpace 7
2021-10-25 11:44:34 -05:00
Davide Negretti
6ff065cd27 [CST-4767] Password are not properly url encoded at login 2021-10-25 18:34:28 +02:00
Tim Donohue
d0e4055bf0 Address feedback 2021-10-25 11:27:33 -05:00
Pascal-Nicolas Becker
b12bc4adc6 Small additional changes to the German message catalog 2021-10-25 18:13:38 +02:00
Tim Donohue
45933ba3d0 Add initial yarn test:rest-api script 2021-10-25 10:38:08 -05:00
Tim Donohue
3065720817 Merge pull request #1329 from 4Science/CST-4504-Adding-new-relationships-in-edit-item-is-difficult-for-entities-with-many-relationships
Fix issue with new relationships for entities with many relationships
2021-10-25 10:09:16 -05:00
YPaulsen-TLC
f58e0cd0c4 Contribution to the German translation of DSpace 7 2021-10-25 16:59:42 +02:00
Tim Donohue
043decf7e4 Merge pull request #1079 from mspalti/iiif-mirador
Angular support for IIIF and Mirador
2021-10-22 12:17:42 -05:00
Giuseppe Digilio
9382b16171 Merge remote-tracking branch 'origin/main' into CST-4504-Adding-new-relationships-in-edit-item-is-difficult-for-entities-with-many-relationships
# Conflicts:
#	src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts
2021-10-21 17:20:04 +02:00
Rezart Vata
fe1c3417c7 [CST-4504] Bug fixing 2021-10-21 17:09:11 +02:00
Tim Donohue
936c5cabd3 Merge pull request #1332 from 4Science/CST-4510-entity-selection-porting
Assign an entity type to a collection and map external providers
2021-10-21 10:05:52 -05:00
Giuseppe Digilio
d1f16cdee3 Merge remote-tracking branch 'origin/main' into CST-4510-entity-selection-porting
# Conflicts:
#	src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts
2021-10-21 15:23:02 +02:00
Michael W Spalti
2c60cc4ca2 Updated versioned-item to provide route service in super constructor call. 2021-10-20 15:09:32 -07:00
Tim Donohue
07f20b1ad7 Merge pull request #1366 from atmire/w2p-84385_tabbed-view-breaks-on-tab-change
84385: Tabbed search view breaks if switch away/back between tabs
2021-10-20 17:09:31 -05:00
Tim Donohue
551404c08f Merge pull request #1363 from atmire/Search-results-LinkType-fix
linkType search-results bug fix
2021-10-20 16:31:34 -05:00
Michael W Spalti
2b19b1659c Merge branch 'main' into iiif-mirador 2021-10-20 14:24:59 -07:00
Tim Donohue
2568daedb4 Merge pull request #1318 from 4Science/CST-4499
Create new Item Version (basic Items only)
2021-10-20 11:17:57 -05:00
Marie Verdonck
cf0a526187 84385: Tabbed search view breaks if switch away/back between tabs 2021-10-20 16:50:02 +02:00
Davide Negretti
fd61ac0c41 [CST-4499] Version history - Test item-versions.component 2021-10-20 02:29:00 +02:00
Davide Negretti
3b0e1dbcc4 [CST-4499] Version history - Test item-versions.component 2021-10-20 02:28:02 +02:00
Tim Donohue
2650e1eb36 Merge pull request #1362 from atmire/issue-1294-fix-required-dropdown-initial-click-not-working
#1294 [Submission] - Fix for required dropdown initial select click not working
2021-10-19 16:37:33 -05:00
Davide Negretti
52baa819cf [CST-4499] Version history - Test versioned-item.component 2021-10-19 21:12:34 +02:00
Tim Donohue
238b47b2f9 Merge pull request #1364 from atmire/search-scope-small-fixes
Fixes to scope select in search
2021-10-19 14:12:12 -05:00
Davide Negretti
d12f932351 [CST-4499] Version history - Test version-history-data.service 2021-10-19 19:16:39 +02:00
Davide Negretti
75fffe2f15 [CST-4499] Version history - Test version-history-data.service 2021-10-19 17:54:11 +02:00
Davide Negretti
82f6a78511 Merge branch 'main' into CST-4499
# Conflicts:
#	src/app/item-page/item-page-routing.module.ts
2021-10-19 16:58:39 +02:00
Davide Negretti
3cc302bc42 [CST-4499] Version history - Test version-history-data.service 2021-10-19 16:49:13 +02:00
lotte
a4c957b8ef changed scope with to css variable and removed the search label alignment 2021-10-19 16:19:07 +02:00
Davide Negretti
6dac82989b Merge branch 'main' of github.com:4science/dspace-angular into CST-4499 2021-10-19 16:18:12 +02:00
Tim Donohue
a8900f7278 Merge pull request #1327 from atmire/w2p-83635_Request-a-copy
Request a copy
2021-10-19 09:09:45 -05:00
Kristof De Langhe
d7a5502d30 linkType search results bugfix 2021-10-19 16:03:38 +02:00
Giuseppe Digilio
9a30b8a9ae [CST-4504] fix issue with name variants 2021-10-19 15:16:10 +02:00
Marie Verdonck
0557750987 #1294 [Submission] - Fix for required dropdowns initial select click not working 2021-10-19 15:13:51 +02:00
Rezart Vata
5f05c1d7c0 [CST-4504] lint fixes 2021-10-19 14:12:59 +02:00
Rezart Vata
78403d9696 [CST-4504] Fixed discard issue and submit issue 2021-10-19 14:04:39 +02:00
Davide Negretti
376d803b96 [CST-4499] Version history - Test item-data.service and item-versions-shared.service 2021-10-19 13:34:58 +02:00
Kristof De Langhe
898548d399 Merge branch 'main' into w2p-83635_Request-a-copy 2021-10-19 10:04:24 +02:00
Giuseppe Digilio
e142a49479 [CST-4499] Add tests to version-data.service 2021-10-18 20:15:20 +02:00
Michael W Spalti
b0fcdf628f Updated test. 2021-10-18 11:15:06 -07:00
Michael W Spalti
8ac122b151 Updated viewport size check.
Updated test.

Mobile test added.

Fixed lint error.
2021-10-18 11:15:01 -07:00
Giuseppe Digilio
8f541c37a7 [CST-4499] Add tests to workspaceitem-data.service and workflowitem-data.service 2021-10-18 19:04:16 +02:00
Tim Donohue
03d9996dcc Merge pull request #1346 from 4Science/CST-4659
[Deque Re-Analysis] Submission "critical" accessibility issues
2021-10-18 11:01:21 -05:00
Michael W Spalti
a3511ab234 Merge branch 'iiif-mirador' of https://github.com/mspalti/dspace-angular into iiif-mirador 2021-10-18 08:54:07 -07:00
Davide Negretti
bee628921a [CST-4499] Version history - Test 2021-10-18 17:37:02 +02:00
Tim Donohue
7cc5bd0280 Merge pull request #1360 from tdonohue/enable_spanish
Add 'es' to languages. Order alphabetically by label
2021-10-18 09:54:28 -05:00
Kristof De Langhe
492a31dd10 83635: Remove f from fdescribe 2021-10-18 16:43:13 +02:00
Kristof De Langhe
0f7fc4907a Merge branch 'main' into w2p-83635_Request-a-copy 2021-10-18 16:01:19 +02:00
Davide Negretti
7c40c3ba68 [CST-4499] Version history - Pagination fixed 2021-10-18 13:20:03 +02:00
Davide Negretti
d264576a07 [CST-4659] Deque accessibility issues - fix 470001 2021-10-18 10:40:02 +02:00
Michael Spalti
6a2d856ecf Removed unused import. 2021-10-16 16:28:56 -07:00
Michael Spalti
073296eb07 Mirador component update and spec. 2021-10-16 14:28:34 -07:00
Tim Donohue
f4cdd6a9ab Merge pull request #1314 from tdonohue/add_a11y_tests
Add automated Accessibility testing for all public pages
2021-10-15 16:36:45 -05:00
Tim Donohue
8a1ff1ef70 Update to latest Cypress 2021-10-15 16:03:43 -05:00
Tim Donohue
305571bc25 Add accessibility tests for search/browse 2021-10-15 15:43:01 -05:00
Tim Donohue
4faa850fba Add logging for violations & testA11y() in utils.ts. Use that method everywhere else 2021-10-15 15:41:33 -05:00
Tim Donohue
fad74a4ca2 Upgrade Cypress 2021-10-15 15:41:33 -05:00
Tim Donohue
3c35c6c8ae Add basic accessibility scan for most public pages. Disable any a11y tests with known issues 2021-10-15 15:41:32 -05:00
Davide Negretti
dfeee7894a Merge branch 'main' into CST-4499
# Conflicts:
#	src/app/item-page/item-page.module.ts
#	src/app/shared/shared.module.ts
2021-10-15 22:35:27 +02:00
Tim Donohue
c3dbe00f0b Add 'es' to languages. Order alphabetically by label 2021-10-15 15:34:47 -05:00
Tim Donohue
ee70baafd9 Merge pull request #1351 from gsferreyro/spanish-translation
Complete Spanish translation
2021-10-15 15:24:10 -05:00
Tim Donohue
66b55bf910 Merge pull request #1336 from 4Science/CST-4634-Many-notifications-when-trying-to-create-an-eperson-with-already-existing-email
Added custom validator and mail check during eperson registration
2021-10-15 12:22:14 -05:00
Davide Negretti
e4d468b17c [CST-4499] Version history - Changes to version history table 2021-10-15 19:13:15 +02:00
Giuseppe Digilio
7b24e3bc8e [CST-4510] Fix issue with multiple selection in the collection-dropdown.component 2021-10-15 19:04:14 +02:00
Giuseppe Digilio
096a1d8427 [CST-4510] Close collection modal once entity is imported 2021-10-15 17:34:04 +02:00
Giuseppe Digilio
ded8a415cf Merge remote-tracking branch 'origin/main' into CST-4510-entity-selection-porting
# Conflicts:
#	src/assets/i18n/en.json5
2021-10-15 17:04:34 +02:00
Tim Donohue
d778f98b0d Merge pull request #1348 from atmire/search-scope-lookup
Replace search scope dropdown by modal
2021-10-15 09:52:37 -05:00
Giuseppe Digilio
05b288c8d0 [CST-4504] Fix LGTM issues 2021-10-15 14:58:34 +02:00
Davide Negretti
850970e204 [CST-4499] Version history - Code refactoring (WIP) and "Edit workspace item" button 2021-10-15 13:05:24 +02:00
Giuseppe Digilio
4d277991ef [CST-4510] Address feedback 2021-10-15 10:25:30 +02:00
Michael Spalti
80a9770936 More neutral primary color in mirador viewer configuration. 2021-10-14 16:13:36 -07:00
Michael Spalti
24a6a4c25a Checking for dev mode to prevent viewer embed. 2021-10-14 15:57:46 -07:00
Michael Spalti
3a1abe82a1 Removed unused import. 2021-10-14 09:29:02 -07:00
Michael Spalti
8c05c6d4df Merge branch 'main' into iiif-mirador 2021-10-14 08:38:13 -07:00
Tim Donohue
802a212b9b Merge pull request #1355 from the-library-code/PRs/dspace-angular/1354
Make 'FileSectionComponent' themeable
2021-10-14 10:17:17 -05:00
Giuseppe Digilio
bd02bcd0de [CST-4634] Remove impersonate button from eperson creation form 2021-10-14 16:02:20 +02:00
Rezart Vata
4c72fa7da1 [CST-4504] Fixes regarding code improvement & save spinner only 2021-10-14 15:55:20 +02:00
Kristof De Langhe
13dd4dfc05 83635: Remove fdescribe and unused imports 2021-10-14 15:48:50 +02:00
Kristof De Langhe
478c95f6d4 83635: Item request - processed message 2021-10-14 15:45:05 +02:00
Giuseppe Digilio
d08807544f [CST-4510] Fix LGTM issue 2021-10-14 15:44:59 +02:00
Giuseppe Digilio
f20776fb8f [CST-4510] Remove unused label 2021-10-14 15:44:42 +02:00
Rezart Vata
05261a91ea [CST-4504] Fixed multiple delete & multiple add in modal and changed unit testing 2021-10-14 13:06:55 +02:00
Kristof De Langhe
a745468b0c 83635: Keep bitstream param on link 2021-10-14 11:25:03 +02:00
Michael Spalti
b738065d88 Updated service mock for tests. 2021-10-13 17:58:34 -07:00
Giuseppe Digilio
9c47583a0a [CST-4634] Change email validator in order to show error also on focus 2021-10-13 23:13:39 +02:00
Tim Donohue
6356979994 Merge pull request #1323 from 4Science/DSC-227-myDspaceReloadIssue
Reload of My Dspace results is broken
2021-10-13 14:31:45 -05:00
Giuseppe Digilio
0fccf4c16a Merge remote-tracking branch 'origin/main' into CST-4510-entity-selection-porting
# Conflicts:
#	src/app/core/data/collection-data.service.ts
2021-10-13 19:24:25 +02:00
Vasilii Fedorov
7505a5a208 Make 'FileSectionComponent' themeable 2021-10-13 15:41:32 +02:00
Giuseppe Digilio
36be741eb3 [CST-4510] Remove check for entity type param in the import from external source page 2021-10-13 09:25:23 +02:00
Giuseppe Digilio
363e9b2dcb [CST-4510] Fix i18n labels 2021-10-13 09:25:09 +02:00
Giuseppe Digilio
b4a249245a [CST-4510] make entity type not mandatory in the collection form 2021-10-13 09:24:57 +02:00
Tim Donohue
ab9aed0143 Merge pull request #1328 from atmire/w2p-83707_Control-collection-harvest-from-the-UI
Control collection harvest from the UI
2021-10-12 16:18:34 -05:00
Michael W Spalti
a875f681a7 Merge branch 'iiif-mirador' of https://github.com/mspalti/dspace-angular into iiif-mirador 2021-10-12 14:00:27 -07:00
Michael Spalti
538e3cbd0f Mirador component update. 2021-10-12 07:56:23 -07:00
Giuseppe Digilio
93e0828135 [CST-4591] Hide none item type entry from available entities in the collection form 2021-10-12 16:23:47 +02:00
Yana De Pauw
72d1235b2c 83707: Add spinners to in progress buttons 2021-10-12 09:50:56 +02:00
Michael Spalti
2d1113626a Viewer updates. 2021-10-11 17:50:15 -07:00
Gustavo S. Ferreyro
82b88c85c2 Full Spanish translation
Full Spanish translation based on the English en.json5 file at 09 Oct, 2021.
2021-10-09 13:40:53 -03:00
Michael Spalti
681b10e070 Removed unused import. 2021-10-08 15:53:02 -07:00
Michael Spalti
baa94ca1cb Fixed proble with mirador iframe url for a single image. 2021-10-08 15:29:51 -07:00
Tim Donohue
bc89692acb Merge pull request #1344 from ybnd/Pause-notification-countdown-on-hover
Pause notification countdown on hover
2021-10-08 14:33:45 -05:00
lotte
b651f4e18f lgtm fixes 2021-10-08 14:37:57 +02:00
lotte
13bce1df60 Added missing typedoc 2021-10-08 14:08:04 +02:00
lotte
c28221623c Added search scope lookup to replace dropdown 2021-10-08 10:22:43 +02:00
Tim Donohue
a48074e5f1 Merge pull request #1341 from atmire/w2p-83783_show-all-collections-on-item-page-rebased
Show owning and mapped collections on item page
2021-10-07 11:38:33 -05:00
Tim Donohue
46c3ea2a7c Merge pull request #1326 from atmire/dynamic-theme-fixes
Dynamic theme improvements
2021-10-07 11:38:04 -05:00
Yura
1cc1674254 Add unit tests for notification countdown 2021-10-07 15:23:54 +02:00
Yura
26cfe7d9ea Remove old commented out code 2021-10-07 13:53:06 +02:00
Kristof De Langhe
881eb92fa1 83635: Feedback and test cases 2021-10-07 13:53:02 +02:00
Rezart Vata
ce2790a89b [CST-4504] Adding and removing in modal checkboxes, working on deleting paginated relationships 2021-10-06 18:35:52 +02:00
Corrado Lombardi
91218bb534 removed unnecessary new lines and commented out code 2021-10-06 18:00:19 +02:00
Davide Negretti
eaa3d96bc7 [CST-4659] Deque accessibility issues - 470010 and tests 2021-10-06 15:26:55 +02:00
Kristof De Langhe
03ddde9a97 83635: Button layout changes + LGTM fixes 2021-10-05 18:17:40 +02:00
Kristof De Langhe
120b9f5ce9 83635: Grant/Deny item requests 2021-10-05 18:17:40 +02:00
Kristof De Langhe
11bf10cbde 83635: Intermediate commit 2021-10-05 18:17:40 +02:00
Yana De Pauw
4feccb9989 intermittent commit 2021-10-05 18:17:40 +02:00
Yana De Pauw
90b706239e Fix page reload when requesting a copy 2021-10-05 18:17:40 +02:00
Yana De Pauw
416aa7adaf 83635: Update request a copy page and tests 2021-10-05 18:17:40 +02:00
Yana De Pauw
506883c960 83635: Request a copy - Request page 2021-10-05 18:17:40 +02:00
Davide Negretti
8173d7d805 [CST-4659] Deque accessibility issues - 470010 2021-10-05 17:51:31 +02:00
Davide Negretti
5c8448c36e [CST-4659] Deque accessibility issues - 469993 2021-10-05 16:22:46 +02:00
Davide Negretti
ad15c07a18 [CST-4659] Deque accessibility issues - 469984 2021-10-05 12:54:09 +02:00
Yura
08b79f0595 Fix undefined isPaused$ test failure (afterAll) 2021-10-05 10:51:37 +02:00
Yura
f4c50bc7a2 Pause all notifications when hovering over board 2021-10-05 10:06:30 +02:00
Yura
25ea75cf54 Pause notification countdown on hover 2021-10-05 09:52:31 +02:00
Giuseppe Digilio
e9bca1b51d [CST-4510] use alert component for info message 2021-10-05 09:19:10 +02:00
Tim Donohue
b29b87d0f6 Merge pull request #1315 from tdonohue/header_keyboard_fix
Header Accessibility Fix: Allow enter key or spacebar to expand or contract menu based on current active status
2021-10-04 10:59:34 -05:00
Tim Donohue
b4732f3c31 Move actions to keyup while having preventDefault on keydown 2021-10-04 10:18:01 -05:00
Davide Negretti
b7d6c8e557 [CST-4499] Version history - SSR bug fixed 2021-10-04 13:29:55 +02:00
Tim Donohue
1d0a5c0e87 Switch from keyup to keydown to fix spacebar scrolling issue. 2021-10-01 16:45:59 -05:00
Rezart Vata
74eac1f72c [CST-4504] code improvement and report fixes 2021-10-01 18:57:24 +02:00
Rezart Vata
78c88f212f [CST-4504] Merged conflicts 2021-10-01 18:16:46 +02:00
Rezart Vata
af09479203 [CST-4504] Added discard,reinstate & submit buttons , unit testing & lint fixes 2021-10-01 18:13:18 +02:00
Davide Negretti
69bd118001 [CST-4659] Deque accessibility issues - 469700 2021-10-01 17:22:03 +02:00
Davide Negretti
d59e6c630d [CST-4659] Deque accessibility issues - 470062 2021-10-01 17:10:54 +02:00
Yura
ed3bcaa574 83631: Reword & explain theme extension tests 2021-10-01 13:32:56 +02:00
Tim Donohue
7747a8c4e2 Attempt to stop scroll to bottom of page when clicking spacebar 2021-09-30 17:11:34 -05:00
Davide Negretti
a80d9694d8 [CST-4659] Deque accessibility issues - 470001 2021-09-30 18:11:24 +02:00
Davide Negretti
a2eb16d328 [CST-4659] Deque accessibility issues - 470065 2021-09-30 15:31:52 +02:00
Davide Negretti
ee9d54245c [CST-4659] Deque accessibility issues - 469979 2021-09-30 13:19:52 +02:00
Yana De Pauw
e434bb8952 83707: Implement feedback 2021-09-30 10:22:48 +02:00
Michael Spalti
d4a6ed6d08 Mirador component multi-view based on count of image files in the default bundle. 2021-09-29 22:03:23 -07:00
Michael Spalti
670a0b8a26 Completed work on angular tests. 2021-09-29 14:12:48 -07:00
Davide Negretti
e111f7c70b [CST-4499] Version history - Minor fixes 2021-09-29 20:21:42 +02:00
Bruno Roemers
578b5afa06 83783: Show owning and mapped collections on item page 2021-09-29 18:52:07 +02:00
Davide Negretti
8a38d06318 [CST-4499] Version history - Fixes and tests 2021-09-29 18:28:26 +02:00
Davide Negretti
ca9ca0105e [CST-4499] Version history - Redirect to Edit Item 2021-09-29 16:33:23 +02:00
Michael Spalti
ec0e8c7804 Completed the initial embed of mirador viewer. 2021-09-28 16:47:58 -07:00
Michael Spalti
abb733b9df Merge branch 'main' into iiif-mirador 2021-09-28 13:24:31 -07:00
Michael Spalti
3bf9c5f21c Starting update for iiif using non-entity metadata. 2021-09-28 13:22:09 -07:00
Tim Donohue
53cd1cfcb2 Merge pull request #1319 from 4Science/DSC-192
edit item metadata fix
2021-09-28 13:41:40 -05:00
Tim Donohue
e5e6e9c07a Also support using spacebar to open/close menu 2021-09-28 12:20:53 -05:00
Rezart Vata
7543af2a38 [CST-4634] Worked on unit testing formgroup input validation and custom validations 2021-09-27 16:06:29 +02:00
Davide Negretti
4f2697bf52 [CST-4499] Version history - New version refactored (with tests) 2021-09-27 12:26:20 +02:00
Yana De Pauw
4e9bd86012 83707: Implement tests 2021-09-27 11:11:54 +02:00
Davide Negretti
f60755b2b0 [CST-4499] Version history - New version refactored - Test TBD 2021-09-25 00:59:19 +02:00
Corrado Lombardi
eb14b6c5ad [DSC-192] lint fixes 2021-09-24 18:49:13 +02:00
Giuseppe Digilio
df8ff19550 Merge remote-tracking branch 'origin/main' into CST-4510-entity-selection-porting 2021-09-24 18:15:13 +02:00
Rezart Vata
d9237acb70 [CST-4634] Added custom validator and messages and show error message for not valid email, lint fixes & unit testing 2021-09-24 17:16:53 +02:00
Alba Aliu
010dee7a16 Merge branch 'DSC-192' of https://bitbucket.org/4Science/dspace-angular into DSC-192 2021-09-24 17:08:55 +02:00
Alba Aliu
b22d8358fb [DSC-112] unit testing 2021-09-24 17:07:38 +02:00
Giuseppe Digilio
93f2274c5d [CST-4591] add missing label 2021-09-24 14:33:30 +02:00
Giuseppe Digilio
d29c27b400 [CST-4591] Use search collection dropdown when importing from external source with lookup modal 2021-09-24 13:20:36 +02:00
Giuseppe Digilio
c6a73f2dcb [CST-4591] Fix issue with showing empty collection results 2021-09-24 13:18:50 +02:00
Giuseppe Digilio
a170f1f8a9 [CST-4591] Fix issue with navigate when entity type is empty 2021-09-24 13:17:35 +02:00
Tim Donohue
2b1e3c6b02 Merge pull request #1324 from atmire/upload-bitstream-bugfix
Fixed edit item bitstreams
2021-09-23 16:10:25 -05:00
lotte
50c2d918b0 fixed lgtm issue 2021-09-23 15:48:58 +02:00
Yana De Pauw
100166023a 83707: Control collection harvest from the UI 2021-09-23 15:47:43 +02:00
Giuseppe Digilio
2894045134 [CST-4504] fix lint error 2021-09-23 15:30:23 +02:00
Art Lowel
ac13b8b282 Merge branch 'w2p-83631_Add-the-ability-to-extend-a-theme' into w2p-83628_Dynamic-theme-fixes 2021-09-23 13:42:16 +02:00
Art Lowel
573a133815 fix ciruclar dependency 2021-09-23 13:30:40 +02:00
lotte
e41c66cdda 83681: fixed bundles issue on update bitstreams edit item page 2021-09-23 12:50:58 +02:00
Kristof De Langhe
e2c2423e53 83628: Dynamic theme fixes - test cases and JSDocs 2021-09-23 11:27:38 +02:00
Tim Donohue
4dd3b57539 Merge pull request #1298 from 4Science/CST-4058
Cannot easily tell similar named EPeople or Groups apart (usability issue)
2021-09-22 17:18:44 -05:00
Giuseppe Digilio
d10d0b039f [CST-4504] fix duplicated code 2021-09-22 18:16:32 +02:00
Giuseppe Digilio
6bf3b8e7cd [CST-4591] Change import from external source page in order to work with entity type 2021-09-22 18:02:52 +02:00
Kristof De Langhe
d9db65685b 83628: Dynamic theme fixes 2021-09-22 17:46:55 +02:00
Giuseppe Digilio
afdbf3541b [CST-4591] Add dropdown for selecting entity type when creating a new submission or importing from external source 2021-09-22 17:43:47 +02:00
Giuseppe Digilio
2e0095a587 [CST-4591] Create entity scrollable dropdown 2021-09-22 17:36:18 +02:00
Giuseppe Digilio
51ada1c933 [CST-4591] Change collection dropdown in order to accept entity type as input 2021-09-22 17:34:54 +02:00
Giuseppe Digilio
1f941acda4 [CST-4591] Add entity selection field to collection create/edit form 2021-09-22 17:32:41 +02:00
Giuseppe Digilio
b20b6d003f [CST-4591] Add methods to retrieve entity type related to collections 2021-09-22 17:29:47 +02:00
Davide Negretti
eaca1ac228 [CST-4058] Improvements applied in all places 2021-09-22 17:24:28 +02:00
Giuseppe Digilio
225a5640e5 [CST-4591] Add methods to retrieve collections by entity type 2021-09-22 17:20:04 +02:00
Rezart Vata
681ab1ff88 [CST-4504] Fixed issue for submission, removed tab when editing relationships, removed unused code & improved checking for remote data completed 2021-09-22 17:01:24 +02:00
Rezart Vata
f1135d8a96 [CST-4504] Finished Unit testing, ts lint, report changes and some fixes 2021-09-22 13:44:12 +02:00
Kristof De Langhe
2d638a738e 83628: Dynamic theme fixes 2021-09-22 11:51:05 +02:00
Alessandro Martelli
5a136f8865 [DSC-227] Reload of My Dspace results is broken 2021-09-22 11:35:59 +02:00
Davide Negretti
c816b97525 [CST-4499] Version history - Old tests fixed 2021-09-21 19:42:43 +02:00
Davide Negretti
468ad70751 [CST-4499] Version history - Old tests fixed 2021-09-21 18:21:38 +02:00
Davide Negretti
aee612e06d [CST-4499] Version history - Old tests fixed 2021-09-21 16:47:42 +02:00
Corrado Lombardi
8633b5207d Merge remote-tracking branch 'bb-cris/DSC-192' into DSC-192 2021-09-21 14:50:38 +02:00
Alba Aliu
7214f3fe6d [DSC-192] fix 2021-09-21 14:50:08 +02:00
Alba Aliu
a97f55c32d [DSC-192] disable edit metadata field name in edit mode 2021-09-21 14:50:08 +02:00
Davide Negretti
62fea1fa78 [CST-4499] Version history - Tests WIP 2021-09-21 12:11:23 +02:00
Davide Negretti
3255a29b30 [CST-4499] Version history - Tests WIP 2021-09-20 20:34:34 +02:00
Rezart Vata
3c26ecdcdf [CST-5404] Code refactoring, fixing issues , lint fixes and started unit testing 2021-09-20 19:43:11 +02:00
Davide Negretti
fbc69832d8 [CST-4499] Version history - Tests WIP 2021-09-20 17:38:32 +02:00
Yura
0b62144d97 83631: Add the ability to extend a theme 2021-09-20 17:03:34 +02:00
Alba Aliu
af12fc1a51 [DSC-192] fix 2021-09-20 11:12:45 +02:00
Alba Aliu
cbab3484e7 [DSC-94] reload tabs box after claim made 2021-09-17 23:07:48 +02:00
Michael Spalti
ded5e29f10 Merge branch 'main' into iiif-mirador 2021-09-17 10:38:57 -07:00
Davide Negretti
a547030ce0 [CST-4499] Version history - LGTM fixes - Test TBD 2021-09-17 18:22:48 +02:00
Tim Donohue
523fca2177 Minor updates to README to provide more helpful info on unit tests 2021-09-17 11:19:14 -05:00
Tim Donohue
8e2ab83d92 Fix existing tests. Add new specs for enter key presses 2021-09-17 11:19:14 -05:00
Davide Negretti
97ed08e5c8 [CST-4499] Version history - Test TBD 2021-09-17 18:04:33 +02:00
Davide Negretti
55a1a1c0eb [CST-4499] Version history (WIP) - Refactoring 2021-09-17 13:37:57 +02:00
Davide Negretti
025e7f5c38 [CST-4499] Version history (WIP) - Cache issue fixed 2021-09-17 12:19:18 +02:00
Davide Negretti
b1b2bd4562 [CST-4499] Version history (WIP) - Authorization features - Cache issue fixed 2021-09-16 18:53:45 +02:00
Davide Negretti
cf1c73bd32 [CST-4499] Version history (WIP) - Authorization features - Deleting 2021-09-16 15:49:11 +02:00
Davide Negretti
f858eeef46 [CST-4499] Version history (WIP) - Authorization features - Deleting not working 2021-09-16 00:28:45 +02:00
Tim Donohue
b86ae8dd14 Allow enter key to expand or contract menu based on current active status 2021-09-14 14:16:04 -05:00
Rezart Vata
a2605cdddd [CST-4504] Changed init api, disabled selected tab only for relationship edit page, fixed the related items checked, fixed the issue regarding the updated item control 2021-09-14 19:24:22 +02:00
Davide Negretti
77ff774c97 [CST-4499] Version history (WIP) - Minor fixes and code cleanup 2021-09-14 18:48:13 +02:00
Davide Negretti
60bbb9ace4 [CST-4499] Version history (WIP) - Redirect after deleting a version 2021-09-14 18:20:58 +02:00
Davide Negretti
5d8f625d01 [CST-4499] Version history (WIP) - Refactoring 2021-09-14 01:39:00 +02:00
Davide Negretti
f10f25d129 Merge remote-tracking branch 'bb/CST-4499' into CST-4499 2021-09-13 18:04:22 +02:00
Davide Negretti
b4111fe4b1 [CST-4499] Version history (WIP) - Version page added (redirecting to item's page) 2021-09-13 17:56:55 +02:00
Rezart Vata
ffee36607d [CST-5404] Started working on new feature,added services and started working on selection 2021-09-10 18:49:23 +02:00
Alessandro Martelli
ce399cb764 [CST-4499] Version history (WIP) - Missing tests and auth features 2021-09-10 18:22:55 +02:00
Davide Negretti
44d3558e87 [CST-4499] Version history (WIP) - 'Create version' button in item page and other fixes 2021-09-10 18:16:35 +02:00
Davide Negretti
5526cde869 [CST-4058] LGTM issues solved 2021-09-10 11:06:25 +02:00
Davide Negretti
8df8b1c574 [CST-4058] LGTM issues solved 2021-09-10 10:50:35 +02:00
Davide Negretti
ad5ace79fb Changes by alex 2021-09-10 10:36:37 +02:00
Davide Negretti
dd2df9106c [CST-4499] Version history (WIP) - Fixes to API calls 2021-09-10 10:36:33 +02:00
Davide Negretti
05385f3568 [CST-4499] Version history (WIP) - Fixes to API calls 2021-09-10 10:19:33 +02:00
Tim Donohue
706cc47197 Merge pull request #1300 from bbranan/issue-1299
Adds support for capturing i18n translation updates within a theme
2021-09-09 16:26:33 -05:00
Davide Negretti
d402ab7dcf [CST-4499] Version history (WIP) - New version modal and other changes 2021-09-09 00:57:44 +02:00
Tim Donohue
2f251a3a61 Merge pull request #1310 from DSpace/dependabot/npm_and_yarn/url-parse-1.5.3
Bump url-parse from 1.4.7 to 1.5.3
2021-09-08 16:39:39 -05:00
Tim Donohue
49773140c6 Merge pull request #1293 from DSpace/dependabot/npm_and_yarn/path-parse-1.0.7
Bump path-parse from 1.0.6 to 1.0.7
2021-09-08 16:23:48 -05:00
Tim Donohue
1c3dcefcec Merge pull request #1165 from DSpace/dependabot/npm_and_yarn/lodash-4.17.21
Bump lodash from 4.17.20 to 4.17.21
2021-09-08 15:49:13 -05:00
Bill Branan
3be2e6f08b Merge branch 'main' into issue-1299 2021-09-08 15:28:02 -04:00
Tim Donohue
879c23e002 Merge pull request #1166 from DSpace/dependabot/npm_and_yarn/hosted-git-info-2.8.9
Bump hosted-git-info from 2.8.8 to 2.8.9
2021-09-08 13:33:04 -05:00
dependabot[bot]
c40958f450 Bump path-parse from 1.0.6 to 1.0.7
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-08 17:37:21 +00:00
dependabot[bot]
285551674d Bump url-parse from 1.4.7 to 1.5.3
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.4.7 to 1.5.3.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.4.7...1.5.3)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-08 17:11:31 +00:00
dependabot[bot]
c9ac591dc3 Bump hosted-git-info from 2.8.8 to 2.8.9
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-08 17:11:13 +00:00
dependabot[bot]
17ed492a5f Bump lodash from 4.17.20 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-08 17:11:04 +00:00
Tim Donohue
5a82e1b60c Merge pull request #1307 from DSpace/dependabot/npm_and_yarn/tar-4.4.19
Bump tar from 4.4.13 to 4.4.19
2021-09-08 12:09:44 -05:00
Tim Donohue
fab1e3dbb0 Merge pull request #1311 from tdonohue/disable_a11y_checks
Temporarily disable a11y check until random failure for color contrast can be fixed
2021-09-08 12:09:30 -05:00
Tim Donohue
432fb4e3cc Temporarily disable a11y check until random failure for color contrast can be fixed 2021-09-08 11:14:24 -05:00
dependabot[bot]
173bf5460c Bump tar from 4.4.13 to 4.4.19
Bumps [tar](https://github.com/npm/node-tar) from 4.4.13 to 4.4.19.
- [Release notes](https://github.com/npm/node-tar/releases)
- [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-tar/compare/v4.4.13...v4.4.19)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-08 14:51:55 +00:00
Tim Donohue
15e7f75e39 Merge pull request #1301 from tdonohue/switch_to_cypress
Switch to Cypress.io for all e2e tests
2021-09-08 09:50:46 -05:00
Davide Negretti
72bfab16df [CST-4499] Version history (WIP) - Changes to in-row editing, code cleanup, added modal for new versions 2021-09-08 12:08:54 +02:00
Davide Negretti
95ffc7a016 [CST-4499] Version history (WIP) - Added in-row editing in version history (call to REST service TBD) and other fixes 2021-09-07 20:33:47 +02:00
Tim Donohue
4d4aadd83f Merge pull request #1309 from atmire/fix-themed-profile-page-component
fix the profile page component in the custom theme
2021-09-07 11:40:25 -05:00
Davide Negretti
6f24b31b39 [CST-4499] Version history (WIP) - Removed summary page (with routing) and modal 2021-09-07 14:47:36 +02:00
Davide Negretti
a1e312440f [CST-4499] Version history (WIP) - Buttons added to history, form replaced with modal, minor fixes 2021-09-06 20:51:34 +02:00
Art Lowel
f35f671dc3 fix the profile page component in the custom theme 2021-09-06 11:23:46 +02:00
Alessandro Martelli
5b036e84e1 [CST-4499] Version history module (WIP) 2021-09-02 16:20:43 +02:00
Davide Negretti
d66cf881fb [CST-4058] 'colspan' removed from table header; code cleanup 2021-09-02 16:05:22 +02:00
Bill Branan
edb814c49a Updates formatting to follow code style rules 2021-08-31 15:59:09 -04:00
Bill Branan
81988179e9 Adds setting to maintain the use of double quotes 2021-08-31 15:47:46 -04:00
Tim Donohue
098779a82d Fix to Cypress tsconfig.json, don't override sourceMap settings 2021-08-27 13:09:33 -05:00
Tim Donohue
13d8d75cfa Upgrade to latest Cypress 2021-08-27 12:24:35 -05:00
Tim Donohue
fec71ebf99 Add link to Cypress docs on creating first test in our README 2021-08-27 12:02:23 -05:00
Tim Donohue
03a11aeda1 Minor cleanup to GitHub CI. No longer need chromedriver. Use latest version of Chrome. 2021-08-27 11:33:06 -05:00
Tim Donohue
9d118dc2fd Add docs on writing e2e tests. Minor cleanup to README elsewhere 2021-08-27 11:08:30 -05:00
Tim Donohue
3c3f2251bb Minor test cleanup. Ensure artifacts are available based on e2e search results 2021-08-27 10:07:07 -05:00
Tim Donohue
fbfd4a2769 Fixes to e2e CI process 2021-08-27 09:11:23 -05:00
Tim Donohue
c86b68a6e0 Replace Protrator e2e tests with Cypress. Move all existing tests to Cypress & add basic Deque Axe integration. 2021-08-26 17:14:08 -05:00
Bill Branan
95a2da6410 Adds script to merge in i18n translation files from themes
See: https://github.com/DSpace/dspace-angular/issues/1299
2021-08-26 09:37:53 -04:00
Michael Spalti
668a08be45 Updated the iiif endpoint that is passed to the viewer. 2021-08-25 11:09:47 -07:00
Davide Negretti
4b3e0e8d09 [CST-4058] lint errors fixed 2021-08-24 17:53:43 +02:00
Davide Negretti
cbbac01313 [CST-4058] bug fixed 2021-08-24 17:06:00 +02:00
Davide Negretti
dfae46d92e [DSC-198] commented lines removed 2021-08-24 15:16:02 +02:00
Davide Negretti
befc0d4c9e [CST-4058] lint errors fixed 2021-08-24 13:41:04 +02:00
Tim Donohue
adb40d8712 Merge pull request #1295 from atmire/w2p-81901_Fix-sidebar-a11y-issues
Fix sidebar a11y issues
2021-08-23 16:01:33 -05:00
Davide Negretti
2cb9345ed5 [CST-4058] tests 2021-08-23 18:09:03 +02:00
Yura Bondarenko
714ea5d8f9 81901: Add in forgotten translate pipe 2021-08-23 15:45:22 +02:00
Yura Bondarenko
9cb8a77bf8 81901: Fix OnClickMenuItemComponent unit test 2021-08-20 18:24:38 +02:00
Davide Negretti
8134e6bc17 [CST-4058] tests (WIP) 2021-08-20 15:13:10 +02:00
Yura Bondarenko
3c4f3e9104 81901: Fix navigation/onclick function ~ enter key 2021-08-20 10:53:01 +02:00
Yura Bondarenko
9d66bc5167 81901: Use sidebar section text for tooltip 2021-08-20 09:43:36 +02:00
Michael Spalti
fb0d51c574 Improved performance of mirador component by avoiding IIIF bundle bitstream lookup. 2021-08-19 13:00:58 -07:00
Davide Negretti
9e710f7093 [CST-4058] tests (WIP) 2021-08-18 18:58:16 +02:00
Davide Negretti
3d894d02d3 [CST-4058] Show EPerson's email address and netid; tests fixed 2021-08-17 18:38:35 +02:00
Davide Negretti
5c070428d3 [CST-4058] Show the name of the community/collection in "Edit group" page 2021-08-17 17:10:32 +02:00
Davide Negretti
1bb98119f5 [CST-4058] Show the name of the community/collection next to the name of the group 2021-08-17 09:33:54 +02:00
Yura Bondarenko
ab6e8be6b1 Remove unused import 2021-08-16 17:53:17 +02:00
Yura Bondarenko
2a10f6bf9e 81901: AdminSidebarSectionComponent navigate on spacebar 2021-08-16 17:36:04 +02:00
Yura Bondarenko
7ae26d3ebb 81901: Fix expandable section submenu spacing
* Selection outlines were cut off by the icon (which needs to be full height for the animation-
2021-08-16 17:36:04 +02:00
Yura Bondarenko
2fa06d28e3 81901: Make expandable section not a link 2021-08-16 17:36:04 +02:00
Davide Negretti
ed12770494 [CST-4058] Show EPerson's email address - Show netid as fallback value 2021-08-16 17:23:45 +02:00
Davide Negretti
8274594848 [CST-4058] Show EPerson's email address 2021-08-16 17:03:07 +02:00
Yura Bondarenko
214833af68 81901: Add aria-expanded attribute to expandable items 2021-08-16 16:50:36 +02:00
Yura Bondarenko
c63d049d55 81901: Make expanded submenu items slide in from under collapsed sidebar 2021-08-16 16:39:56 +02:00
Yura Bondarenko
303e69a6b9 81901: Make admin sidebar header not a link 2021-08-16 09:50:04 +02:00
Yura Bondarenko
129ea726e8 81901: Enable spacebar toggling for admin sidebar items 2021-08-16 09:50:04 +02:00
Yura Bondarenko
b32226c5ed 81901: Fix keyboard/hover expand/collapse interactions 2021-08-13 17:14:54 +02:00
Yura Bondarenko
b84e003f27 81901: Update unit tests 2021-08-06 19:03:31 +02:00
Yura Bondarenko
4ae526d6b2 81901: Expand/collapse admin sidebar preview on focusin/focusout 2021-08-06 18:32:14 +02:00
Yura Bondarenko
1bad7fae3f 81901: Set aria-disabled for menu links and sidebar sections 2021-08-06 18:14:44 +02:00
Yura Bondarenko
2265de8d44 81901: Single link in sidebar menu entries
+ improved focus ring styling
2021-08-06 18:14:44 +02:00
Yura Bondarenko
d095c98b13 81901: Menu item components as ul > li 2021-08-06 18:14:43 +02:00
Tim Donohue
5a12f34593 Merge pull request #1287 from atmire/language-attribute-html-tag
Language attribute on HTML tag
2021-08-05 16:53:33 -05:00
Tim Donohue
a45780566f Merge pull request #1283 from josekarvalho/patch-2
Update en.json5
2021-08-05 15:55:12 -05:00
lotte
43c03de0ad Language attribute on HTML tag 2021-08-04 17:38:27 +02:00
Michael Spalti
6019a21ebd Removed unused import. 2021-07-31 09:02:10 -07:00
Michael Spalti
9cc3351fcf Fixed item page component paths. 2021-07-30 16:52:33 -07:00
Michael Spalti
20477f0aaa Merge branch 'main' into iiif-mirador 2021-07-30 16:27:18 -07:00
Tim Donohue
9fc7b57157 Correct year in LICENSE 2021-07-29 12:55:59 -05:00
Tim Donohue
f0474ce2f2 Merge branch 'main' of github.com:DSpace/dspace-angular 2021-07-29 12:50:42 -05:00
Tim Donohue
3664a06d8b Update README for 7.0. Sync some sections with DSpace/DSpace 2021-07-29 12:50:15 -05:00
José Carvalho
85ae3da0c0 Update en.json5
removed strings not used
2021-07-29 18:46:28 +01:00
Tim Donohue
232b457eff Merge pull request #1264 from josekarvalho/patch-1
Update pt-PT.json5
2021-07-29 12:37:04 -05:00
José Carvalho
14f026f8c4 Update en.json5
Updated with missing strings (found by revising the portuguese translation), and added new strings to support relations between entities and the import of external funding from the OpenAIRE API. Removed one duplicate string.
2021-07-29 15:47:31 +01:00
José Carvalho
681fa3cbe9 Update pt-PT.json5
Update additional strings
2021-07-29 15:16:11 +01:00
Tim Donohue
47302acf66 Merge pull request #1282 from tdonohue/test_ssr_working
SEO + SSR fixes: Fix <meta> tag syntax and add a basic test that SSR is working
2021-07-26 15:49:47 -05:00
Tim Donohue
91bb5e2fd4 Add a basic SSR test to GitHub CI 2021-07-26 15:15:03 -05:00
Tim Donohue
7fcd055458 Meta tags should use "name" not "property" attribute, as "name" is HTML5 2021-07-26 15:14:36 -05:00
Tim Donohue
4292af4294 Merge pull request #1280 from atmire/fix-first-request-issue
Fix issue where the app wouldn't load for the first request in prod mode
2021-07-26 10:03:08 -05:00
Art Lowel
8ee12868aa deal with errors that aren't error responses 2021-07-26 13:38:40 +02:00
Art Lowel
8f43fbd399 check if correlationid exists before adding it as a header 2021-07-26 11:54:25 +02:00
Tim Donohue
e9a03994e2 Merge pull request #1278 from atmire/remove-plus-from-folders
Update folder structure based on style guide
2021-07-23 11:28:38 -05:00
Art Lowel
acf2b0ee57 switch scss imports back to relative paths 2021-07-23 17:59:57 +02:00
Art Lowel
1c5bfbac24 remove a missed plus sign 2021-07-23 17:41:42 +02:00
Tim Donohue
9483e3ff2c Merge pull request #1277 from atmire/test-date-timezone-issue
Fix timezone issue with test dates
2021-07-23 10:19:46 -05:00
Art Lowel
124845bee1 update folder structure based on style guide 2021-07-23 17:18:51 +02:00
Art Lowel
3c1263ada6 fix timezone issue with test dates 2021-07-23 16:30:25 +02:00
Tim Donohue
fa02d1efe6 Merge pull request #1276 from atmire/remove-unused-config-params
Remove unused config params
2021-07-22 14:57:01 -05:00
Art Lowel
d0771715b6 add prototcol to origin in ServerHardRedirectService 2021-07-22 18:48:06 +02:00
Art Lowel
03f6f75e49 remove unused config params and related methods 2021-07-22 17:59:10 +02:00
Art Lowel
146ec49a32 Merge pull request #1275 from 4Science/#1273_no_js_issue
#1273 no js issue
2021-07-22 10:14:46 +02:00
Tim Donohue
2f6501f1af Merge pull request #1242 from atmire/w2p-80141_Support-for-legacy-bitstream-download-urls
Add support for legacy bitstream download URLs
2021-07-21 09:10:15 -05:00
Giuseppe Digilio
85fa1c5a9c move effect for cleaning cache to auth.effects 2021-07-21 09:46:15 +02:00
Tim Donohue
b33c859d67 Merge pull request #1254 from 4Science/#1206
Improve error handling during submission
2021-07-20 13:39:17 -05:00
Tim Donohue
83d61a5929 Merge pull request #1255 from 4Science/#1248
Improve logging Angular implementation
2021-07-20 11:03:30 -05:00
Giuseppe Digilio
d3fe33a837 Address feedback 2021-07-20 11:54:25 +02:00
José Carvalho
580a67a18d Update pt-PT.json5
Update some strings and add new ones to support OpenAIRE 4.0 guidelines compatibility (by naming relationships)
2021-07-20 10:33:03 +01:00
Giuseppe Digilio
f7ebffec45 Merge remote-tracking branch 'origin/main' into #1206
# Conflicts:
#	src/app/submission/form/submission-upload-files/submission-upload-files.component.spec.ts
#	src/app/submission/form/submission-upload-files/submission-upload-files.component.ts
2021-07-20 11:26:06 +02:00
Giuseppe Digilio
b04c33a5fb Invalidate authorization requests cache on REHYDRATE 2021-07-20 11:02:02 +02:00
Tim Donohue
8b88e7f4cd Merge pull request #1252 from 4Science/#1171
Fix accessibility issues #1172 #1167  #1171
2021-07-19 14:13:35 -05:00
Giuseppe Digilio
6cadae4e7f Fix missing button title 2021-07-19 19:19:50 +02:00
Giuseppe Digilio
aaae62de5f remove unnecessary aria-label 2021-07-19 19:19:25 +02:00
Giuseppe Digilio
fbde0cbad9 Revert "[CSTPER-144] Fixed issue with authorization request encountered while logging-in with external idp"
This reverts commit 73a9fe16
2021-07-19 17:54:02 +02:00
Tim Donohue
88c591054f Merge pull request #1250 from 4Science/CST-4319
Cannot map new items with Collection Admin
2021-07-15 15:14:18 -05:00
Giuseppe Digilio
d6990b3cd1 Add keyup event for selecting a new language from lang dropdown 2021-07-15 11:02:33 +02:00
Tim Donohue
73e70e6546 Merge pull request #1269 from 4Science/submission-footer-improvement
improve positioning of buttons in the submission form
2021-07-13 11:18:06 -05:00
Tim Donohue
6fc8ef6b50 Merge pull request #1262 from tdonohue/date_fixes
Ensure we are always using UTC dates in the UI, as the backend expects/uses UTC-based dates
2021-07-13 09:06:08 -05:00
Michael Spalti
de352a839e Retrieve thumbnail from remote data object. 2021-07-12 09:53:17 -07:00
Tim Donohue
008e0cb66c Another small fix from @atarix83 2021-07-12 11:47:53 -05:00
Tim Donohue
9ee733ea80 Stop using localized Dates. Ensure we always use UTC dates, as the backend expects/uses UTC. 2021-07-12 10:11:55 -05:00
Tim Donohue
6d13ba42c6 Fix saving of dates to UTC 2021-07-12 10:11:54 -05:00
Giuseppe Digilio
9ef4a11771 fix test 2021-07-12 16:19:03 +02:00
Giuseppe Digilio
aac9f02447 fix test 2021-07-12 15:04:06 +02:00
Giuseppe Digilio
5185c90ad6 improve positioning of buttons in the submission form 2021-07-12 14:31:24 +02:00
Giuseppe Digilio
ff6f6fea33 fix issue with Hierarchical vocabulary onebox toggle 2021-07-12 11:54:36 +02:00
José Carvalho
ae1d462bec Update pt-PT.json5
Added additional corrections. The strings until line 44 where not in the file to be translated.  After checking our DSpace 7 instance they where missing so we added. It's fine by now to be included in DSpace 7.0.
2021-07-12 09:56:14 +01:00
Michael Spalti
9c41cd7f8f Merge branch 'main' into iiif-mirador 2021-07-11 07:37:03 -07:00
Art Lowel
98cecd3e84 Merge pull request #1249 from 4Science/CST-4320
No way to view an Item when in simple "Approve/Reject" workflow stage
2021-07-09 09:34:41 +02:00
Giuseppe Digilio
28707f9a4c fix issue with misaligned loading icon on tag field 2021-07-08 20:33:43 +02:00
Giuseppe Digilio
9875b61082 fix issue with wrong fontawesome icon that show odd char on firefox 2021-07-08 20:22:51 +02:00
Giuseppe Digilio
d30fdb1db7 fix issue with aria-expanded attribute that is invalid on input 2021-07-08 20:19:07 +02:00
Giuseppe Digilio
eae89e1be0 fix issue with missing aria label 2021-07-08 19:37:39 +02:00
Giuseppe Digilio
ab73bf87b0 fix issue with dropdowns key accessibility 2021-07-08 18:10:43 +02:00
Giuseppe Digilio
186e420db5 fix issue with not unique ids 2021-07-08 17:46:04 +02:00
Alessandro Martelli
de2ba413a1 [CSTPER-4320] fixed back button for accessibility 2021-07-08 17:16:13 +02:00
Giuseppe Digilio
3b264015cb Merge remote-tracking branch 'origin/main' into #1171
# Conflicts:
#	src/app/submission/sections/upload/file/section-upload-file.component.html
2021-07-08 17:09:01 +02:00
Giuseppe Digilio
2bd68b0585 fix issue with ssr 2021-07-08 17:06:02 +02:00
Alessandro Martelli
cf7d1ed108 Merge branch 'main' into CST-4320
# Conflicts:
#	src/assets/i18n/en.json5
2021-07-08 16:02:52 +02:00
Giuseppe Digilio
9607a5248b Merge remote-tracking branch 'origin/main' into #1248 2021-07-08 15:30:34 +02:00
Art Lowel
d55ae48e4a Merge pull request #1239 from atmire/Fix-hardcoded-submission-section-IDs
Fix hardcoded submission section IDs
2021-07-08 15:14:47 +02:00
Art Lowel
2c8d72b971 Merge pull request #1240 from atmire/w2p-79700_Improve-session-timeout-UX
Improve session timeout UX
2021-07-08 13:27:41 +02:00
Yura Bondarenko
bfcff12499 Fix hardcoded reference to 'upload' section 2021-07-07 18:05:26 +02:00
Alessandro Martelli
dbb4d08ca0 [CST-4320] back button standardized style 2021-07-07 16:28:27 +02:00
Alessandro Martelli
3d6cacadff [CST-4320] fixed item version component initialization 2021-07-07 16:09:19 +02:00
Alessandro Martelli
f5cd878096 [CST-4320] resolved merge conflicts 2021-07-05 16:01:32 +02:00
Alessandro Martelli
0212df5bd1 Merge branch 'main' into CST-4320
# Conflicts:
#	src/app/+item-page/full/full-item-page.component.ts
2021-07-05 15:56:40 +02:00
Alessandro Martelli
111170e7d4 [CST-4320] workflow item view resolved as full item page 2021-07-05 15:47:58 +02:00
Tim Donohue
873feb9cfe Merge pull request #1266 from 4Science/submission-bitstream-download
fix bitstream download during submission
2021-07-02 14:19:36 -05:00
Yura Bondarenko
8736c0b572 Add unit tests for SectionsService#isSectionType 2021-07-02 18:12:18 +02:00
Tim Donohue
1dba4c55f5 Merge pull request #1228 from atmire/w2p-79768_fix-issues-with-meta-tags
Fix issues with meta tags
2021-07-02 10:00:50 -05:00
Yura Bondarenko
7670ba8a43 Fix duplicate notifications 2021-07-02 14:26:58 +02:00
Yura Bondarenko
a3ba4e59b3 Fix lint issues 2021-07-02 14:26:58 +02:00
Yura Bondarenko
72e97ca6b4 Update unit tests 2021-07-02 14:26:58 +02:00
Yura Bondarenko
32003a72fd Fix hardcoded submission section IDs 2021-07-02 14:26:57 +02:00
Yana De Pauw
5b869cce46 80141: Add support for legacy bitstream download URLs 2021-07-02 14:12:07 +02:00
Giuseppe Digilio
c190cdc9bd fix comment 2021-07-02 13:53:11 +02:00
Giuseppe Digilio
badf901361 Use ds-file-download-link component to allow bitstream download during submission 2021-07-02 13:46:17 +02:00
José Carvalho
97144f74da Update pt-PT.json5
Big update of the translation to Portuguese
2021-07-02 12:14:17 +01:00
Giuseppe Digilio
193de508cd Merge remote-tracking branch 'origin/main' into #1206
# Conflicts:
#	src/app/submission/edit/submission-edit.component.ts
2021-07-02 13:12:04 +02:00
Giuseppe Digilio
5fd30e1be9 Merge remote-tracking branch 'origin/main' into #1171 2021-07-02 13:10:50 +02:00
Yura Bondarenko
a91f16ed62 79768: Fix followLink syntax 2021-07-01 23:11:57 +02:00
Yura Bondarenko
711abfca35 Merge branch 'main' into w2p-79768_fix-issues-with-meta-tags 2021-07-01 22:55:29 +02:00
Yura Bondarenko
5ed41b3f9b 79768: Fix unused imports & lint issue 2021-07-01 22:47:26 +02:00
Yura Bondarenko
8caa916316 79768: Fix typo 2021-07-01 22:37:55 +02:00
Yura Bondarenko
fb8f28f17d 79768: Update & add MetadataService unit tests 2021-07-01 22:37:07 +02:00
Yura Bondarenko
50400895de 79768: Add unit tests for metaTagReducer 2021-07-01 22:36:08 +02:00
Tim Donohue
0ee451f3d3 Merge pull request #1247 from atmire/w2p-80195_Fix-move-item-page
Fix move item page
2021-07-01 10:13:00 -05:00
Tim Donohue
8ad8824e3a Merge pull request #1233 from 4Science/#1129
Add possibility to edit embargo on bitstream of archived Item
2021-07-01 10:11:44 -05:00
Marie Verdonck
1721ba5ddf Icon changes & fix after rebase 2021-07-01 16:19:31 +02:00
Marie Verdonck
6c219e72d5 79700: Doc fixes, Spec tests authService & ariaLabelledBy for idle-modal 2021-07-01 15:52:12 +02:00
Marie Verdonck
829ce12710 lgtm alerts 2021-07-01 15:52:12 +02:00
Marie Verdonck
ddcb27da3f 79700: logout via store, automatic redirect 2021-07-01 15:52:12 +02:00
Marie Verdonck
c696b78393 79700: idle time and grace period testing times removed 2021-07-01 15:52:12 +02:00
Marie Verdonck
4b1f086469 79700: Feedback 2021-06-15 applied 2021-07-01 15:52:12 +02:00
Art Lowel
91b4c81986 run idle timer outside of angular zone 2021-07-01 15:52:12 +02:00
Marie Verdonck
e88baa1995 79700: specs for modal, auth check for idleness tracking & stop blocking at token success 2021-07-01 15:52:12 +02:00
Marie Verdonck
38387d1a0f 79700: Tracking idleness & idle modal 2021-07-01 15:52:12 +02:00
Marie Verdonck
b23522d39f 79700: Auto-refreshing the token & Needed config 2021-07-01 15:51:23 +02:00
Art Lowel
c86f163cb7 move tagstore to ngrx 2021-07-01 11:36:12 +02:00
Tim Donohue
de8e306d9f Merge pull request #1244 from 4Science/#1179
[CSTPER-144] Fixed issue with authorization request encountered while logging-in with shibboleth
2021-06-30 16:05:19 -05:00
Tim Donohue
c46abae264 Merge pull request #1261 from atmire/w2p-80369_Use-thumbnail-links-for-Items-and-Bitstreams
Use thumbnail links for items and bitstreams
2021-06-30 14:30:32 -05:00
Tim Donohue
a14230afcc Merge pull request #1251 from 4Science/1217
Delete pending json patch operations
2021-06-30 09:02:31 -05:00
Art Lowel
03e2e30510 fix issue where meta tags wouldn't be updated properly based on the route 2021-06-30 14:21:43 +02:00
Yura Bondarenko
fedf6aa847 80369: Fix unused imports 2021-06-29 21:18:06 +02:00
Yura Bondarenko
7bb32f31de Merge remote-tracking branch 'upstream/main' into w2p-80369_Use-thumbnail-links-for-Items-and-Bitstreams 2021-06-29 18:03:57 +02:00
Yura Bondarenko
abf6d26641 80369: Remove unneeded bundles followLink 2021-06-29 17:34:00 +02:00
Yura Bondarenko
b0599fa27c 80195: Fix ItemMoveComponent unit tests 2021-06-29 09:56:29 +02:00
Yura Bondarenko
72a6754245 80195: Fix breadcrumbs not updating 2021-06-29 09:14:17 +02:00
Tim Donohue
e6c2680d2f Merge pull request #1253 from 4Science/#1234
Fix issue when switching submission's collection between those with different submission definition
2021-06-28 16:28:23 -05:00
Tim Donohue
87e458bc0f Merge pull request #1246 from atmire/w2p-80233_add-pagination-to-edit-relationships-tab
Add pagination to edit relationships tab
2021-06-28 13:46:28 -05:00
Yura Bondarenko
eb0b3ae5f4 80369: Add optional thumbnail followLink in SearchComponent 2021-06-28 12:51:57 +02:00
Yura Bondarenko
c1b1cb2a27 80369: Remove deprecated front-end thumbnail methods 2021-06-28 12:42:53 +02:00
Yura Bondarenko
d21d0eae55 80369: Add followLinks for thumbnail 2021-06-28 12:42:51 +02:00
Yura Bondarenko
acff2186b4 80369: Add thumbnail link to Item & Bitstream models 2021-06-28 12:40:39 +02:00
Art Lowel
0d5fc8a1c0 refactor followlinks to use a single object for all params and add an isOptional param. Also add support for embedding links to search service 2021-06-28 12:28:59 +02:00
Tim Donohue
7714ef47c8 Merge pull request #1245 from 4Science/#1122
Disable community/collection delete button while the delete operation is being processed
2021-06-25 17:14:09 -05:00
Tim Donohue
ff0291d346 Merge pull request #1222 from atmire/w2p-79730_Fix-search-sidebar-a11y-issues
Fix search sidebar a11y issues
2021-06-25 16:56:09 -05:00
Tim Donohue
c33e3909b9 Merge pull request #1201 from 4Science/#1110
Fixes for submission forms bugs
2021-06-25 14:44:13 -05:00
Giuseppe Digilio
75d641dc82 Create a new log interceptor with the scope to attach new X-CORRELATION-ID and X-REFERRER headers 2021-06-25 16:06:07 +02:00
Giuseppe Digilio
ac7c038703 Fix tests 2021-06-25 15:31:23 +02:00
Giuseppe Digilio
4d12236930 Fix visualization of error in date field 2021-06-25 15:24:31 +02:00
Giuseppe Digilio
44d2310cdb Changes in order to keep server side validation errors into the submission form state 2021-06-25 15:23:56 +02:00
Giuseppe Digilio
0724692d40 Merge remote-tracking branch 'origin/main' into #1206 2021-06-25 14:18:26 +02:00
Giuseppe Digilio
a089e662fb fix issue with date picker that didn't save the date entered manually 2021-06-25 14:08:21 +02:00
Yura Bondarenko
98b2c75a3c 79730: Add unit tests for sequence service & ds-search-filter IDs 2021-06-25 10:00:21 +02:00
Yura Bondarenko
24ffa6c63a 79730: Replace component ID with unique number 2021-06-25 09:32:45 +02:00
Tim Donohue
980d6a6154 Merge pull request #1237 from atmire/w2p-bugfix-issue-1124-access-control-section-visible-to-non-admin-users
Access Control - Groups only editable by Com/Col admin if group related to com/col & Only site admin access to access control epeople
2021-06-24 13:47:20 -05:00
Bruno Roemers
511a2e18ab 80113: Display loading indicator after using pagination nav 2021-06-24 19:50:39 +02:00
Bruno Roemers
fc398c15f4 80113: Rename searching$ into loading$ 2021-06-24 19:44:06 +02:00
Giuseppe Digilio
434ef5628f [DSC-130] Fix issue when switching submission's collection between those with different submission definition 2021-06-24 18:52:00 +02:00
Giuseppe Digilio
714b713134 fix failed tests 2021-06-24 18:47:27 +02:00
Giuseppe Digilio
f8e4b770e8 fix a11y issue with drag and drop 2021-06-24 18:35:30 +02:00
Tim Donohue
1187edb486 Merge pull request #1219 from atmire/w2p-79597_Fix-thumbnail-a11y-issues
Fix thumbnail a11y issues
2021-06-24 10:24:44 -05:00
Giuseppe Digilio
b03c7fbe4d fix a11y issue with delete community/collection logo 2021-06-24 17:20:35 +02:00
Giuseppe Digilio
3bc5737f6d fix a11y issue with item edit metadata 2021-06-24 17:20:12 +02:00
Alessandro Martelli
da4be7b57f [DSC-76] Delete pending json patch operations 2021-06-24 17:13:58 +02:00
Tim Donohue
0dee03e0bc Merge pull request #1243 from DSpace/missing-i18n-key
Add missing i18n key for Sherpa Journals by ISSN
2021-06-24 09:58:53 -05:00
Alessandro Martelli
8be77017e9 [CST-4319] Corrected test assertion 2021-06-24 16:57:41 +02:00
Alessandro Martelli
4b11a2f1c1 [CST-4320] Added missing translations 2021-06-24 16:38:59 +02:00
Alessandro Martelli
3795ab0495 [CST-4319] Cannot map new items with Collection Admin 2021-06-24 16:24:18 +02:00
Giuseppe Digilio
71c36f59af fix issue with navbar issues 2021-06-24 15:38:27 +02:00
Alessandro Martelli
67ce3e6741 [CST-4320] No way to view an Item when in simple "Approve/Reject" workflow stage 2021-06-24 15:34:11 +02:00
Alessandro Martelli
a572b0acea [CST-4320] No way to view an Item when in simple "Approve/Reject" workflow stage 2021-06-24 15:28:26 +02:00
Giuseppe Digilio
dccf04d4c0 fix issue with wrong aria-labels 2021-06-24 14:53:06 +02:00
Giuseppe Digilio
b4cd7bdf26 fix issue with submission section missing elements 2021-06-24 14:47:35 +02:00
Giuseppe Digilio
2681a31e87 fix issue with comboboxes 2021-06-24 14:30:46 +02:00
Giuseppe Digilio
601bd5e922 fix issue with elements with missing/wrong role 2021-06-24 14:23:06 +02:00
Giuseppe Digilio
606915f39a fix issue with elements with missing name/label 2021-06-24 13:04:41 +02:00
Giuseppe Digilio
56487a44f3 fix issue with elements not accessible with Keyboard navigation 2021-06-24 12:49:41 +02:00
Giuseppe Digilio
4b6a0d8aa2 fix issue with missing labels in the mydspace page 2021-06-24 12:05:00 +02:00
Yura Bondarenko
a48e77810d 80195: Fix unused import 2021-06-24 12:01:05 +02:00
Giuseppe Digilio
3ca1e61a8c fix issue with missing labels in the submission form 2021-06-24 11:58:41 +02:00
Art Lowel
7dc4609a83 add getFirstCompletedRemoteData, remove force redownload of owningcollection 2021-06-24 11:16:38 +02:00
Giuseppe Digilio
396393e9b7 fix issue with multiple elements in the submission form that use the same ID 2021-06-24 11:09:25 +02:00
Giuseppe Digilio
217f7b5b21 fix issue with the aria-multiline attribute that is invalid on combobox role 2021-06-24 10:56:13 +02:00
Giuseppe Digilio
ea2030666c fix issue with aria-expanded attribute that is invalid on input 2021-06-24 10:55:46 +02:00
Tim Donohue
f6c48494b9 Merge pull request #1241 from atmire/w2p-80154_Fix-contrast-issues-with-grays
Fix contrast issues with grays
2021-06-23 14:05:31 -05:00
Yura Bondarenko
290abf7c43 80195: Fix unit tests 2021-06-23 18:39:15 +02:00
Yura Bondarenko
84274c3911 80195: Fix ItemMoveComponent#moveToCollection 2021-06-23 18:08:52 +02:00
Tim Donohue
c1ddf7f667 Add production warning 2021-06-23 10:55:22 -05:00
Art Lowel
04f4a25870 fix an issue where the results of a dso-selector wouldn't update until you clicked anywhere on the page 2021-06-23 17:28:36 +02:00
Art Lowel
2599068ccd add a focus style for truncatable parts 2021-06-23 17:27:23 +02:00
Art Lowel
b586a264ca ensure dynamic components are updated when their inputs change 2021-06-23 17:27:01 +02:00
Bruno Roemers
4c1a72fdb2 80233: Apply Art's feedback 2021-06-23 15:39:53 +02:00
Yura Bondarenko
c825f911f5 80195: Mimic back/save/discard buttons of other edit pages 2021-06-23 15:31:13 +02:00
Yura Bondarenko
86cd7ba03f 80195: Select original owningCollection on load 2021-06-23 15:29:24 +02:00
Art Lowel
f683d1219e make truncatable css global to ensure it adapts to the theme 2021-06-23 13:43:10 +02:00
Giuseppe Digilio
10aa2e14f1 Merge remote-tracking branch 'origin/#1110' into #1129 2021-06-23 12:54:44 +02:00
Giuseppe Digilio
fba5504dd9 fix issue while editing bitstream with an existing access condition 2021-06-23 12:42:32 +02:00
Yura Bondarenko
2c19d80a16 80195: Refresh moved Item's owningCollection before redirecting 2021-06-23 12:06:11 +02:00
Giuseppe Digilio
421c37f010 Disable community/collection delete button while the delete operation is being processed 2021-06-23 11:46:32 +02:00
Yura Bondarenko
0ac9d58194 80195: Update ItemMoveComponent unit tests 2021-06-23 11:35:37 +02:00
Yura Bondarenko
1407ea85d7 80195: Fix move request handling 2021-06-23 11:29:11 +02:00
Yura Bondarenko
b2c002057e 80195: Replace input suggestions with collection selector 2021-06-23 11:07:51 +02:00
Giuseppe Digilio
73a9fe16c5 [CSTPER-144] Fixed issue with authorization request encountered while logging-in with external idp 2021-06-23 10:41:54 +02:00
Tim Donohue
ddb7d5181f Merge pull request #1210 from DSpace/dependabot/npm_and_yarn/dns-packet-1.3.4
Bump dns-packet from 1.3.1 to 1.3.4
2021-06-22 14:46:29 -05:00
Tim Donohue
720126647c Add missing i18n key 2021-06-22 14:42:57 -05:00
Bruno Roemers
627b271d3b 80233: Write tests for pagination 2021-06-22 18:53:28 +02:00
Tim Donohue
3d4f493f9f Merge pull request #1221 from atmire/move-header-changes-to-dspace-theme
Move header changes to dspace theme
2021-06-22 09:15:22 -05:00
Bruno Roemers
c5689df73d 80233: Add required providers to test 2021-06-22 16:08:02 +02:00
Bruno Roemers
45ad6b6f0b 80233: Use correct relationship direction 2021-06-22 16:06:49 +02:00
Yura Bondarenko
a617e04290 80154: Change badge-light to badge-info 2021-06-22 09:09:39 +02:00
Art Lowel
d9a8b8f3fd add pagination to edit relationships tab 2021-06-21 16:18:12 +02:00
Yura Bondarenko
7c609820b1 80154: Fix semicolon placement 2021-06-18 15:42:07 +02:00
Yura Bondarenko
3a8e658d54 80154: Keep using Bootstrap variables 2021-06-18 15:24:16 +02:00
Tim Donohue
e546a4f0b5 Merge pull request #1225 from atmire/issue-1224-workflow-rejection-reason-not-properly-encoded
Workflow rejection reasons are not properly encoded
2021-06-17 15:58:04 -05:00
Tim Donohue
cfd6512856 Merge pull request #1230 from atmire/w2p-80103_Fix-admin-sidebar-link-a11y-issues
Fix admin sidebar link a11y issues
2021-06-17 15:48:54 -05:00
Tim Donohue
4b541a166f Merge pull request #1204 from DSpace/dependabot/npm_and_yarn/browserslist-4.16.6
Bump browserslist from 4.16.0 to 4.16.6
2021-06-17 15:02:38 -05:00
Yura Bondarenko
f3faa0df2c 80154: Revert to default Bootstrap grays 2021-06-17 16:58:00 +02:00
Yura Bondarenko
8085885da4 79730: Move aria-label from span to button 2021-06-17 14:05:57 +02:00
dependabot[bot]
aa5a395d91 Bump browserslist from 4.16.0 to 4.16.6
Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.16.0 to 4.16.6.
- [Release notes](https://github.com/browserslist/browserslist/releases)
- [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/browserslist/browserslist/compare/4.16.0...4.16.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-16 20:57:10 +00:00
dependabot[bot]
f20b139841 Bump dns-packet from 1.3.1 to 1.3.4
Bumps [dns-packet](https://github.com/mafintosh/dns-packet) from 1.3.1 to 1.3.4.
- [Release notes](https://github.com/mafintosh/dns-packet/releases)
- [Changelog](https://github.com/mafintosh/dns-packet/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mafintosh/dns-packet/compare/v1.3.1...v1.3.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-16 20:56:35 +00:00
Tim Donohue
700afc2709 Create a SECURITY.md to document security policies 2021-06-16 12:03:30 -05:00
Marie Verdonck
cf515fe6f0 80113: Prevent anyone but site admin from accessing /access-control/epeople 2021-06-16 16:49:57 +02:00
Tim Donohue
166784b7f2 Merge pull request #1235 from tdonohue/fix_1231
Fix incorrect i18n tags in Collection Delete page
2021-06-16 09:15:40 -05:00
Art Lowel
b556234207 add aria and alt attributes to base header as well 2021-06-16 13:05:03 +02:00
Tim Donohue
98598a53d9 Fix incorrect i18n tags 2021-06-15 14:30:18 -05:00
Tim Donohue
f0ae00e7df Merge pull request #1211 from 4Science/CST-4223
Creating a new submission should redirect to workspaceitem edit page
2021-06-15 14:28:38 -05:00
Bruno Roemers
3fc44f6fc1 80113: Write tests for GroupsRegistryComponent 2021-06-15 19:38:33 +02:00
Bruno Roemers
3b07738d4d 80113: Write tests for GroupPageGuard 2021-06-15 18:43:50 +02:00
Bruno Roemers
3bc031ff00 80113: Create Group Page guard 2021-06-15 17:17:44 +02:00
Bruno Roemers
a69a4e9696 80113: Set ableToEdit on GroupDTOModel 2021-06-15 15:40:35 +02:00
Giuseppe Digilio
b85ebb2791 Merge remote-tracking branch 'origin/main' into #1129 2021-06-15 11:32:21 +02:00
Giuseppe Digilio
28c0c38d0d Merge remote-tracking branch 'origin/main' into #1110 2021-06-14 17:27:07 +02:00
Yana De Pauw
15e45c3dec 80103: Fix admin sidebar link a11y issues 2021-06-14 16:14:39 +02:00
Yura Bondarenko
34b117efe3 80084: Fix unit test & LGTM issues 2021-06-14 10:53:53 +02:00
Art Lowel
04b4f1cf58 Fix comment punctuation 2021-06-14 10:30:17 +02:00
Yura Bondarenko
64049fdcf7 79768: Make relative URLs absolute 2021-06-14 10:08:15 +02:00
Yura Bondarenko
2ed16aa66e 79768: Update typedocs 2021-06-14 10:08:15 +02:00
Yura Bondarenko
6a4e56322f 79768: Rename method 2021-06-14 10:08:15 +02:00
Yura Bondarenko
81b76dd327 79768: Fix tag: citation_publisher / citation_*_institution 2021-06-14 10:08:15 +02:00
Yura Bondarenko
304d8f7386 79768: Fix tag: citation_abstract_html_url 2021-06-14 10:08:15 +02:00
Yura Bondarenko
67f8ce7849 79768: Rename citation_date to citation_publication_date 2021-06-14 10:08:15 +02:00
Yura Bondarenko
6f7b76ec39 79768: Remove og:title & og:description 2021-06-14 10:08:15 +02:00
Yura Bondarenko
cb4446b79d 79768: Update & add unit tests 2021-06-14 10:08:15 +02:00
Yura Bondarenko
1caba78b4d 79768: Check against list of allowed mimetypes 2021-06-14 10:08:15 +02:00
Yura Bondarenko
9fe5a91bc2 79768: Extract download route method for Bundles with multiple Bitstreams 2021-06-14 10:08:15 +02:00
Art Lowel
dde57b9387 update citation_pdf_url logic 2021-06-14 10:06:18 +02:00
Yura Bondarenko
737728f603 79730: Remove label margins 2021-06-14 08:06:15 +02:00
Yura Bondarenko
d37d043531 79730: Show input labels when available 2021-06-14 08:06:15 +02:00
Yura Bondarenko
08878941ab 79730: Fix tslint issues 2021-06-14 08:06:15 +02:00
Yura Bondarenko
c60fa2c441 79730: Don't submit date slider changes until keyup 2021-06-14 08:06:15 +02:00
Yura Bondarenko
6e95990431 79730: Improve slider handles keyboard control 2021-06-14 08:06:15 +02:00
Yura Bondarenko
cb3f5ad259 79730: Add null href to more/collapse toggle links 2021-06-14 08:06:15 +02:00
Yura Bondarenko
abe26ce9f8 79730: Keyboard navigation for expandable filter facets 2021-06-14 08:06:15 +02:00
Yura Bondarenko
3e86efc66a 79730: Add labels around date range inputs 2021-06-14 08:06:15 +02:00
Yura Bondarenko
5b490203b2 79730: Add labels around FilterInputSuggestionsComponent inputs 2021-06-14 08:06:15 +02:00
Yura Bondarenko
ffb320373d 79730: Add labels around facet checkbox inputs 2021-06-14 08:06:15 +02:00
Yura Bondarenko
4683df431c 79730: Exclude search facet link from tablist 2021-06-14 08:06:15 +02:00
David Cavrenne [Atmire]
d253790c7d Updated version of the FR wordings (#1223)
Completed French message catalog.
2021-06-12 08:35:31 +02:00
Tim Donohue
22548c1e9d Merge pull request #1215 from atmire/remove-duplicate-author-field
Remove duplicate author field
2021-06-11 15:45:54 -05:00
Tim Donohue
79d5645f81 Merge pull request #1227 from tdonohue/demo_homepage
Remove references to Testathon & Beta 5 from Demo / Default theme
2021-06-11 14:59:15 -05:00
Tim Donohue
4c6d355376 Merge pull request #1212 from atmire/separate-startup-css
Fix theme switching issues
2021-06-11 14:57:55 -05:00
Tim Donohue
5edef6f942 Merge pull request #1209 from 4Science/issue-1208
Fix the rel name for the submissioncclicenseUrls-search link
2021-06-11 12:22:35 -05:00
Tim Donohue
9b80173b87 Merge pull request #1216 from atmire/w2p-79698_fix-browse-by-author-name-special-chars
Escape browse by author data requests
2021-06-11 11:21:46 -05:00
Tim Donohue
015f3b9607 Remove references to Testathon & Beta 5 2021-06-11 11:01:31 -05:00
Bruno Roemers
586de36835 Merge branch 'main' into issue-1224-workflow-rejection-reason-not-properly-encoded 2021-06-11 17:09:23 +02:00
Art Lowel
eead947352 put more useful example sass variable overrides in custom theme 2021-06-11 17:08:29 +02:00
Art Lowel
f2a29a6425 improve structure double footer 2021-06-11 17:08:29 +02:00
Art Lowel
2ddda1c766 enable e2e stacktrace 2021-06-11 17:08:29 +02:00
Art Lowel
74a17da5b8 fix issue where home page background image would wrap around on certain screen widths 2021-06-11 17:08:29 +02:00
Art Lowel
c756c68f28 move header changes to dspace theme 2021-06-11 17:08:29 +02:00
Bruno Roemers
412822db7c Merge branch 'main' into w2p-79698_fix-browse-by-author-name-special-chars 2021-06-11 17:06:24 +02:00
Tim Donohue
2cd78470ce Merge pull request #1226 from tdonohue/fix_ci_issues
Add option to pin to a specific version of Chrome/ChromeDriver in our CI process
2021-06-11 09:46:30 -05:00
Tim Donohue
e7282bdbd7 Minor cleanup, print chromedriver version after installation 2021-06-11 08:46:17 -05:00
Art Lowel
326bffae7f switch chromedriver to npm 2021-06-11 08:42:43 -05:00
Tim Donohue
b104958473 Add option to pin to a specific version of Chrome/ChromeDriver. Pin to v90 until v91 bugs are fixed 2021-06-10 12:56:40 -05:00
Bruno Roemers
a27a7a4083 Test requestService.uriEncodeBody 2021-06-10 15:00:03 +02:00
Bruno Roemers
bb2892edd8 BUGFIX: Encode special characters when sending workflow action 2021-06-10 14:45:30 +02:00
Giuseppe Digilio
4862a399c9 Merge remote-tracking branch 'origin/main' into #1110 2021-06-01 14:14:47 +02:00
Giuseppe Digilio
eaaad88443 [CST-4248] Remove embargo form field and add link to bitstream authorization page 2021-06-01 14:13:19 +02:00
Giuseppe Digilio
c150fb881e [CST-4248] bitstream authorizations page 2021-06-01 14:12:12 +02:00
Giuseppe Digilio
3e53b7c7b1 [CST-4248] Add possibility to add additional content to form.component 2021-06-01 14:09:17 +02:00
Giuseppe Digilio
2dfed863ed [DSC-75] Fix issue while deleting multiple qualdrop value 2021-05-31 12:20:42 +02:00
Yura Bondarenko
95b98d3f79 79597: Remove unused imports 2021-05-31 10:20:28 +02:00
Bruno Roemers
4ad089ef54 79698: Escape browse by author data requests 2021-05-28 15:40:20 +02:00
Samuel
bcfecc53a1 add support for multiple metadata fields to the MetadataRepresentationListComponent - fix bug when unauthorized for related item 2021-05-28 11:53:59 +02:00
Yura Bondarenko
4b238e1842 79597: Update ds-metadata-field-wrapper unit tests 2021-05-27 18:03:53 +02:00
Yura Bondarenko
120ecc6988 79597: Fix ds-metadata-field-wrapper for thumbnails 2021-05-27 17:10:42 +02:00
Yura Bondarenko
bcfb890e1a 79597: Update unit tests 2021-05-27 16:34:52 +02:00
Yura Bondarenko
c717fc5ec8 79597: Fix thumbnails ~ item page direct request 2021-05-27 16:00:06 +02:00
Art Lowel
899b30213e show full screen loader when switching themes 2021-05-27 15:06:26 +02:00
Art Lowel
772ac12329 create separate startup css to load before the theme is chosen 2021-05-27 11:51:59 +02:00
Yura Bondarenko
9b95fc5de9 79597: Add space between rows in FullFileSectionComponent 2021-05-27 11:18:40 +02:00
Yura Bondarenko
6cbd9dc920 79597: Remove GridThumbnailComponent 2021-05-27 11:06:08 +02:00
Yura Bondarenko
4f38821bb3 79597: Replace ds-grid-thumbnail with ds-thumbnail 2021-05-27 11:06:08 +02:00
Yura Bondarenko
41c07e74ca 79597: Add input to toggle thumbnail max-width 2021-05-27 11:05:48 +02:00
Alessandro Martelli
060d0dd556 [CST-4223] Creating a new submission should redirect to workspaceitem edit page 2021-05-27 09:43:29 +02:00
Andrea Bollini
fce77104d4 Fix the rel name for the submissioncclicenseUrls-search link 2021-05-26 22:39:06 +02:00
Samuel
21686c86df add support for multiple metadata fields to the MetadataRepresentationListComponent 2021-05-25 11:51:19 +02:00
Yura Bondarenko
ca7d45ff0c 79597: Fix tslint issue 2021-05-25 11:18:16 +02:00
Yura Bondarenko
363d1d74df 79597: Update unit tests 2021-05-25 11:03:51 +02:00
Yura Bondarenko
4567f8cc2c 79597: Limit thumbnail width & set to portrait 2021-05-25 10:20:46 +02:00
Yura Bondarenko
c9ff89a143 79597: Thumbnail placeholder style ~ CSS variables 2021-05-25 10:08:33 +02:00
Yura Bondarenko
4b6e02f773 79597: Specify i18n text for person, project & orgunit 2021-05-25 09:55:19 +02:00
Yura Bondarenko
7a69a23f0c 79597: Improve placeholder contrast 2021-05-25 09:46:54 +02:00
Yura Bondarenko
d80da3bbfe 79597: Add HTML placeholder for missing thumbnails 2021-05-25 09:44:39 +02:00
Yura Bondarenko
55affdebce 79597: Add alt text to ds-thumbnail 2021-05-25 09:28:02 +02:00
Tim Donohue
f85a5e65ad Merge pull request #1182 from atmire/w2p-79327_Fix-item-level-statistics
Fix item-level statistics pages
2021-05-24 11:47:22 -05:00
Tim Donohue
8daf52e8c2 Merge pull request #1200 from atmire/w2p-79219_update-metadata-import-export-script-params
Update metadata import export script params
2021-05-24 11:22:40 -05:00
Tim Donohue
fab567400d Merge pull request #1180 from atmire/w2p-79325_Fix-pagination-in-lookup
Fix pagination in author lookup window
2021-05-24 10:20:42 -05:00
Yana De Pauw
579f98d027 79327: Follow up fixes to test 2021-05-20 10:45:14 +02:00
Yana De Pauw
926dd46627 79327: Fix LGTM issues and add e2e tests 2021-05-20 09:45:48 +02:00
Giuseppe Digilio
3c0cb33bc7 fix failed build 2021-05-19 18:16:31 +02:00
Giuseppe Digilio
98dde58f9d [D4CRIS-1080] Fix issue where a replace patch operation was dispatched instead of an add one when field's previous value is empty 2021-05-19 16:28:49 +02:00
Giuseppe Digilio
d6dbbd1f1f Add tests for handleArrayGroupPatch method 2021-05-19 15:36:46 +02:00
Giuseppe Digilio
e18c66d688 Fix issue with patch operations related to repeatable fields 2021-05-19 15:36:43 +02:00
Giuseppe Digilio
e0edcd64d2 Fix wrong visualization of bitstream access condition form within submission form 2021-05-19 15:35:37 +02:00
Giuseppe Digilio
91137a216f Merge remote-tracking branch 'origin/main' into #1110 2021-05-19 15:08:02 +02:00
Giuseppe Digilio
d06b76af3f Fix issue with patching value with a date 2021-05-19 15:04:12 +02:00
Art Lowel
7e129f282f remove extra author field from item pages 2021-05-19 13:05:15 +02:00
Michael Spalti
da2bbcee09 Merge branch 'main' into iiif-mirador 2021-05-18 15:45:23 -07:00
Michael Spalti
3bd8e355f6 Added description field to iiif item view. 2021-05-18 15:42:46 -07:00
Tim Donohue
68bd8d40e8 Merge pull request #1181 from atmire/Fix-dso-selector-issues
Fix dso-selector issues
2021-05-17 12:33:36 -05:00
Michael Spalti
3dacbe51a1 Merge branch 'main' into iiif-mirador 2021-05-16 16:30:34 -07:00
Tim Donohue
5f87d69e9b Merge pull request #1145 from atmire/w2p-79218_remove-menu-options-for-disabled-features
Remove menu options for disabled features
2021-05-12 14:43:16 -05:00
Giuseppe Digilio
d3466c3e82 Fix issue where uploaded files disappear 2021-05-12 17:59:11 +02:00
Kristof De Langhe
43b8f45eee 79252: Remove unused import 2021-05-12 16:50:18 +02:00
Yana De Pauw
6631980ee6 79327: Fix item-level statistics pages 2021-05-12 16:31:48 +02:00
Yana De Pauw
4a8becf662 Fix ORCID tab loading 2021-05-12 14:47:16 +02:00
Yana De Pauw
a77ca2f126 79325: Fix pagination in the external metadata lookup 2021-05-11 11:39:46 +02:00
Tim Donohue
a87bf009ef Merge pull request #1146 from atmire/w2p-79220_fix-edit-group-navigation-bug
Fix edit group navigation bug
2021-05-07 09:42:35 -05:00
Kristof De Langhe
86bf7ff4f2 79252: Fix dso-selector issues 2021-05-06 18:03:46 +02:00
Tim Donohue
a69033917d Merge pull request #1086 from 4Science/CST-4009
[CST-4009] Discovery result sort options not reflecting what is configured
2021-05-05 10:52:07 -05:00
Tim Donohue
9dc9b5accf Merge pull request #1138 from atmire/w2p-78991_Issue-1112_Fix-date-filter-bugs
Fix date filter bugs
2021-05-04 14:19:04 -05:00
Tim Donohue
7dd2683fc3 Merge pull request #1139 from atmire/w2p-78994_Issue-1103_Fix-relationship-modal-on-item-page
Fix relationship modal on item page
2021-05-04 12:41:19 -05:00
Tim Donohue
abca095b9e Merge pull request #1105 from atmire/Edit-item-page-permission-checks
Edit item page permission checks
2021-05-04 09:24:16 -05:00
Bruno Roemers
f0fb8c1005 79219: Improve tests and wording 2021-05-04 13:43:51 +02:00
Bruno Roemers
042afd9bb8 79220: Fix edit group navigation bug 2021-05-04 11:46:04 +02:00
Yura Bondarenko
15ad31bd84 78991: Clean up SearchOptions.toRestUrl
* Extract "selective encoding" ops into separate methods
* Add comments to clarify regex
* Use hasValue() instead of just checking on 'match'
* Leave only the last comma of filter values unencoded
2021-05-04 11:26:19 +02:00
Bruno Roemers
f49a7746fb 79219: Remove -f from ExportMetadataSelectorComponent and pass uuid to -i 2021-05-04 11:19:01 +02:00
Bruno Roemers
93450e1dcf 79219: Remove -e option from MetadataImportPageComponent 2021-05-04 11:08:55 +02:00
Yura Bondarenko
b9a8bfb2bd 78991: Also test SearchOptions.fixedFilter 2021-05-04 10:44:06 +02:00
Yura Bondarenko
e682997195 78991: URI-encode SearchOptions query values 2021-05-04 10:34:37 +02:00
Yura Bondarenko
340f9518cd 78991: Fix SearchOptions typing 2021-05-04 10:34:37 +02:00
Bruno Roemers
fe4fe9e8d3 79218: Comment out all disabled menu options 2021-05-03 19:07:02 +02:00
Bruno Roemers
b5342e0fab 79218: Remove duplicate menu item 2021-05-03 18:41:56 +02:00
Tim Donohue
eb3cd85680 Merge pull request #1137 from atmire/w2p-78849_Issue-1113_Fix-forgot-password-page
Fix forgot password page
2021-04-30 16:57:49 -05:00
Tim Donohue
09a7685a3f Merge pull request #1109 from DSpace/dependabot/npm_and_yarn/ssri-6.0.2
Bump ssri from 6.0.1 to 6.0.2
2021-04-30 12:13:04 -05:00
Tim Donohue
6d00cad749 Merge pull request #1045 from DSpace/dependabot/npm_and_yarn/elliptic-6.5.4
Bump elliptic from 6.5.3 to 6.5.4
2021-04-29 17:19:46 -05:00
Yura Bondarenko
60009144a1 78994: Remove unused import 2021-04-29 14:28:37 +02:00
Yura Bondarenko
b2b077868e 78994: Disable no-shadowed-variable 2021-04-29 14:26:33 +02:00
Kristof De Langhe
a24cfe4cc7 78243: Feedback 2021-04-29 2021-04-29 14:15:03 +02:00
Yura Bondarenko
032231f10e 78849: Fix lint issues 2021-04-29 13:57:31 +02:00
Yura Bondarenko
781a88bc4c 78849: Fix double notification on submit 2021-04-29 13:42:57 +02:00
dependabot[bot]
d2b44318fa Bump elliptic from 6.5.3 to 6.5.4
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-28 21:57:44 +00:00
Alessandro Martelli
ad7824460b [CST-4009] update en.json5 2021-04-28 15:28:02 +02:00
Tim Donohue
8cade4fbf5 Merge pull request #1104 from atmire/Angular-10-upgrade-test-fixes
Angular 10 upgrade test fixes
2021-04-27 14:23:52 -05:00
Tim Donohue
fecf59e433 Merge pull request #1118 from 4Science/#1111
Fix visibility issue with collapsed navbar menu
2021-04-27 13:27:31 -05:00
Alessandro Martelli
bdc2dd5f9c [CST-4009] fixed search configuration stream 2021-04-27 09:53:38 +02:00
Yura Bondarenko
0fe199a97c 78849: Fix unit test 2021-04-26 16:49:42 +02:00
Yura Bondarenko
aca1c86455 78994: Provide Item's owning collection to relation modal 2021-04-26 15:13:41 +02:00
Yura Bondarenko
d62d9b0f48 78994: Update unit tests 2021-04-26 15:13:41 +02:00
Yura Bondarenko
5f45e93d12 78994: Remove setItem method 2021-04-26 13:24:26 +02:00
Yura Bondarenko
4634d2a4a8 78991: Don't initialize SearchFilterComponent with closed=true 2021-04-26 11:48:15 +02:00
Yura Bondarenko
6c2a3431c1 78991: Fix range handle lines
& remove duplicate CSS variable
2021-04-26 11:48:15 +02:00
Yura Bondarenko
4fa6a3e976 78991: Set fallback max date to the current year 2021-04-26 10:53:37 +02:00
Yura Bondarenko
eb9a7a15d6 78991: Specify filter operator for (date) ranges 2021-04-26 10:18:40 +02:00
Yura Bondarenko
8433f49ed9 78991: Initialize slider handle width 2021-04-26 09:09:51 +02:00
Alessandro Martelli
7c0d9acbf1 [CST-4009] default sort option configured with default sort order 2021-04-23 16:46:44 +02:00
Yura Bondarenko
7d0ea04b3e 78849: Add unit tests for EPerson registration caching 2021-04-23 15:45:46 +02:00
Yura Bondarenko
1aa659e6b7 78849: Fix cache/re-request issue & other improvements 2021-04-23 13:28:41 +02:00
Giuseppe Digilio
b6ab3d2067 [CST-4087] fix issue with mydspace result default order 2021-04-23 12:51:27 +02:00
Giuseppe Digilio
a205aa02b3 [CST-4009] Retrieve configuration by search config service 2021-04-23 12:47:53 +02:00
Alessandro Martelli
cba089081b Merge branch 'main' into CST-4009 2021-04-23 12:47:30 +02:00
Michael W Spalti
98571a496c Merge remote-tracking branch 'origin/iiif-mirador' into iiif-mirador 2021-04-20 14:46:44 -07:00
Giuseppe Digilio
ed7454ffc9 Fix visibility issue with collapsed navbar menu 2021-04-20 09:37:28 +02:00
dependabot[bot]
add2aac934 Bump ssri from 6.0.1 to 6.0.2
Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-19 15:41:33 +00:00
Michael Spalti
4791cce928 Merge branch 'main' into iiif-mirador 2021-04-16 08:47:01 -07:00
Michael Spalti
2561d54b2d Added comments 2021-04-16 08:46:20 -07:00
reetagithub
a86f9444ce update fi.json5 (#1088)
* Update fi.json5

Part of the keys translated to Finnish

* Update fi.json5

The rest of the Finnish translations.

* Update fi.json5

Parent organization unit -> Ylemmän tason organisaatioyksikkö (was 'Ylätason')

* Update fi.json5
2021-04-16 17:28:21 +02:00
Kristof De Langhe
37be3530e6 Merge branch 'main' into w2p-78243_edit-item-page-permission-checks
Conflicts:
	src/app/core/data/feature-authorization/feature-id.ts
2021-04-16 12:35:30 +02:00
Kristof De Langhe
25f02b99d3 78243: message change 2021-04-16 12:31:22 +02:00
Kristof De Langhe
fd437eb7ee 78243: edit-item-page mocks, item-status operation refactoring 2021-04-16 11:36:33 +02:00
Kristof De Langhe
5fca681222 78001: RelationshipEffects test fixes 2021-04-15 13:19:27 +02:00
Kristof De Langhe
3504feedf3 78243: edit item page fine-grained permission checks - mocks + tests 2021-04-09 13:34:04 +02:00
Kristof De Langhe
a2e00bbd9f 78243: edit item page fine-grained permission checks 2021-04-08 17:54:27 +02:00
Alessandro Martelli
23fe338c5d [CST-4009] fix sort options on pagination change 2021-04-08 16:18:31 +02:00
Alessandro Martelli
1cbd41ef38 Merge branch 'main' into CST-4009
# Conflicts:
#	src/app/core/shared/search/search-configuration.service.ts
#	src/app/core/shared/search/search.service.ts
#	src/app/shared/search/search-settings/search-settings.component.ts
2021-04-08 15:51:05 +02:00
Alessandro Martelli
d54b7d9f7c [CST-4009] fix sort options on pagination change 2021-04-08 15:43:07 +02:00
Kristof De Langhe
9c85328b17 77998: xdescribe fixes 2021-04-07 15:45:22 +02:00
Giuseppe Digilio
b4686deb63 [CST-4009] Retrieve sort options also for search page 2021-04-06 17:49:06 +02:00
Alessandro Martelli
4d85c0270f [CST-4009] sorting options translations 2021-04-06 10:30:26 +02:00
Alessandro Martelli
9b8ada0326 [CST-4009] Discovery result sort options not reflecting what is configured 2021-04-06 10:06:15 +02:00
Michael Spalti
c37b31599c Merge remote-tracking branch 'upstream/main' into iiif-mirador 2021-04-02 16:35:54 -07:00
Michael Spalti
6f55225aee Removed unused variable. 2021-04-02 15:59:36 -07:00
Michael Spalti
247ae73c64 Merge branch 'main' into iiif-mirador 2021-04-02 14:41:50 -07:00
Michael Spalti
3bc7739098 Minor fix in module. 2021-04-01 16:02:28 -07:00
Michael Spalti
ce11ae2480 Merge branch 'main' into iiif-mirador 2021-04-01 16:01:08 -07:00
Michael Spalti
3744abb26f Allow canvas side panel on all views. 2021-04-01 15:04:40 -07:00
Michael Spalti
97e8c9b955 Added title to the mirador iframe. 2021-03-30 16:03:02 -07:00
Michael Spalti
0250b182b6 Changed property name 2021-03-30 15:42:51 -07:00
Michael Spalti
e4a51af46e Merge branch 'iiif-mirador' of https://github.com/mspalti/dspace-angular into iiif-mirador 2021-03-30 14:02:11 -07:00
Michael Spalti
c0aac8a5e9 Added mobile breakpoint for mirador viewer configuration. 2021-03-30 13:46:28 -07:00
Michael Spalti
f52ed2a218 Updated iiif component tests. 2021-03-30 13:41:11 -07:00
Michael Spalti
0330b3ea4c yarn.lock 2021-03-30 10:54:03 -07:00
Michael Spalti
5a8f38abdc Updates to iiif list elements. 2021-03-30 10:54:03 -07:00
Michael Spalti
45c6f4eb84 Added iiif labels to en.json5 2021-03-30 10:54:03 -07:00
Michael Spalti
187ae50d29 Added iiif entity module to parent item module. 2021-03-30 10:54:03 -07:00
Michael Spalti
2d694a864e Added mirador files for webpack build 2021-03-30 10:54:03 -07:00
Michael Spalti
71851fe141 Added FileSectionComponent to shared module. 2021-03-30 10:54:03 -07:00
Michael Spalti
23e7675171 Added iiif entity group. 2021-03-30 10:54:03 -07:00
Michael Spalti
42802bd543 Added mirador route to express server. 2021-03-30 10:54:03 -07:00
Michael Spalti
c02983c889 Added webpack configuration for mirador. 2021-03-30 10:54:03 -07:00
Michael Spalti
3b5df5d432 Added mirador deps and scripts to package.json 2021-03-30 10:54:03 -07:00
Michael Spalti
aa27ddad02 yarn.lock 2021-03-27 13:44:01 -07:00
Michael Spalti
f377347dcc Updates to iiif list elements. 2021-03-27 13:37:07 -07:00
Michael Spalti
2ec1096e21 Added iiif labels to en.json5 2021-03-27 13:35:11 -07:00
Michael Spalti
30ff10ea90 Added iiif entity module to parent item module. 2021-03-27 13:34:26 -07:00
Michael Spalti
6081dabe48 Added mirador files for webpack build 2021-03-27 10:18:15 -07:00
Michael Spalti
27afd4192f Added FileSectionComponent to shared module. 2021-03-27 10:12:18 -07:00
Michael Spalti
c6bc21f9db Added iiif entity group. 2021-03-27 10:09:22 -07:00
Michael Spalti
7e1c441ee8 Added mirador route to express server. 2021-03-27 10:03:20 -07:00
Michael Spalti
a56ebad133 Added webpack configuration for mirador. 2021-03-27 10:01:50 -07:00
Michael Spalti
156e9a08b0 Added mirador deps and scripts to package.json 2021-03-27 09:57:45 -07:00
1246 changed files with 36663 additions and 22283 deletions

View File

@@ -16,6 +16,9 @@ jobs:
DSPACE_REST_PORT: 8080
DSPACE_REST_NAMESPACE: '/server'
DSPACE_REST_SSL: false
# When Chrome version is specified, we pin to a specific version of Chrome
# Comment this out to use the latest release
#CHROME_VERSION: "90.0.4430.212-1"
strategy:
# Create a matrix of Node versions to test against (in parallel)
matrix:
@@ -34,10 +37,20 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- name: Install latest Chrome (for e2e tests)
# If CHROME_VERSION env variable specified above, then pin to that version.
# Otherwise, just install latest version of Chrome.
- name: Install Chrome (for e2e tests)
run: |
if [[ -z "${CHROME_VERSION}" ]]
then
echo "Installing latest stable version"
sudo apt-get update
sudo apt-get --only-upgrade install google-chrome-stable -y
else
echo "Installing version ${CHROME_VERSION}"
wget -q "https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}_amd64.deb"
sudo dpkg -i "google-chrome-stable_${CHROME_VERSION}_amd64.deb"
fi
google-chrome --version
# https://github.com/actions/cache/blob/main/examples.md#node---yarn
@@ -53,9 +66,6 @@ jobs:
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: ${{ runner.os }}-yarn-
- name: Install the latest chromedriver compatible with the installed chrome version
run: yarn global add chromedriver --detect_chromedriver_version
- name: Install Yarn dependencies
run: yarn install --frozen-lockfile
@@ -83,23 +93,63 @@ jobs:
docker-compose -f ./docker/cli.yml -f ./docker/cli.assetstore.yml run --rm dspace-cli
docker container ls
# Wait until the REST API returns a 200 response (or for a max of 30 seconds)
# https://github.com/nev7n/wait_for_response
- name: Wait for DSpace REST Backend to be ready (for e2e tests)
uses: nev7n/wait_for_response@v1
with:
# We use the 'sites' endpoint to also ensure the database is ready
url: 'http://localhost:8080/server/api/core/sites'
responseCode: 200
timeout: 30000
- name: Get DSpace REST Backend info/properties
run: curl http://localhost:8080/server/api
# Run integration tests via Cypress.io
# https://github.com/cypress-io/github-action
# (NOTE: to run these e2e tests locally, just use 'ng e2e')
- name: Run e2e tests (integration tests)
uses: cypress-io/github-action@v2
with:
# Run tests in Chrome, headless mode
browser: chrome
headless: true
# Start app before running tests (will be stopped automatically after tests finish)
start: yarn run serve:ssr
# Wait for backend & frontend to be available
# NOTE: We use the 'sites' REST endpoint to also ensure the database is ready
wait-on: http://localhost:8080/server/api/core/sites, http://localhost:4000
# Wait for 2 mins max for everything to respond
wait-on-timeout: 120
# Cypress always creates a video of all e2e tests (whether they succeeded or failed)
# Save those in an Artifact
- name: Upload e2e test videos to Artifacts
uses: actions/upload-artifact@v2
if: always()
with:
name: e2e-test-videos
path: cypress/videos
# If e2e tests fail, Cypress creates a screenshot of what happened
# Save those in an Artifact
- name: Upload e2e test failure screenshots to Artifacts
uses: actions/upload-artifact@v2
if: failure()
with:
name: e2e-test-screenshots
path: cypress/screenshots
# Start up the app with SSR enabled (run in background)
- name: Start app in SSR (server-side rendering) mode
run: |
chromedriver --url-base='/wd/hub' --port=4444 &
yarn run e2e:ci
nohup yarn run serve:ssr &
printf 'Waiting for app to start'
until curl --output /dev/null --silent --head --fail http://localhost:4000/home; do
printf '.'
sleep 2
done
echo "App started successfully."
# Get homepage and verify that the <meta name="title"> tag includes "DSpace".
# If it does, then SSR is working, as this tag is created by our MetadataService.
# This step also prints entire HTML of homepage for easier debugging if grep fails.
- name: Verify SSR (server-side rendering)
run: |
result=$(wget -O- -q http://localhost:4000/home)
echo "$result"
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep DSpace
- name: Stop running app
run: kill -9 $(lsof -t -i:4000)
- name: Shutdown Docker containers
run: docker-compose -f ./docker/docker-compose-ci.yml down

View File

@@ -1,6 +1,6 @@
DSpace source code BSD License:
Copyright (c) 2002-2020, LYRASIS. All rights reserved.
Copyright (c) 2002-2021, LYRASIS. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are

212
README.md
View File

@@ -3,12 +3,34 @@
dspace-angular
==============
> The next UI for DSpace 7, based on Angular Universal.
> The DSpace User Interface built on [Angular](https://angular.io/), written in [TypeScript](https://www.typescriptlang.org/) and using [Angular Universal](https://angular.io/guide/universal).
This project is currently under active development. For more information on the DSpace 7 release see the [DSpace 7.0 Release Status wiki page](https://wiki.lyrasis.org/display/DSPACE/DSpace+Release+7.0+Status)
Overview
--------
You can find additional information on the DSpace 7 Angular UI on the [wiki](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+-+Angular+UI+Development).
DSpace open source software is a turnkey repository application used by more than
2,000 organizations and institutions worldwide to provide durable access to digital resources.
For more information, visit http://www.dspace.org/
DSpace consists of both a Java-based backend and an Angular-based frontend.
* Backend (https://github.com/DSpace/DSpace/) provides a REST API, along with other machine-based interfaces (e.g. OAI-PMH, SWORD, etc)
* The REST Contract is at https://github.com/DSpace/RestContract
* Frontend (this codebase) is the User Interface built on the REST API
Downloads
---------
* Backend (REST API): https://github.com/DSpace/DSpace/releases
* Frontend (User Interface): https://github.com/DSpace/dspace-angular/releases
## Documentation / Installation
Documentation for each release may be viewed online or downloaded via our [Documentation Wiki](https://wiki.lyrasis.org/display/DSDOC/).
The latest DSpace Installation instructions are available at:
https://wiki.lyrasis.org/display/DSDOC7x/Installing+DSpace
Quick start
-----------
@@ -47,6 +69,9 @@ Table of Contents
- [Cleaning](#cleaning)
- [Testing](#testing)
- [Test a Pull Request](#test-a-pull-request)
- [Unit Tests](#unit-tests)
- [E2E Tests](#e2e-tests)
- [Writing E2E Tests](#writing-e2e-tests)
- [Documentation](#documentation)
- [Other commands](#other-commands)
- [Recommended Editors/IDEs](#recommended-editorsides)
@@ -146,6 +171,9 @@ This will build the application and put the result in the `dist` folder. You ca
### Running the application with Docker
NOTE: At this time, we do not have production-ready Docker images for DSpace.
That said, we do have quick-start Docker Compose scripts for development or testing purposes.
See [Docker Runtime Options](docker/README.md)
@@ -184,34 +212,66 @@ Once you have tested the Pull Request, please add a comment and/or approval to t
### Unit Tests
Unit tests use Karma. You can find the configuration file at the same level of this README file:`./karma.conf.js` If you are going to use a remote test environment you need to edit the `./karma.conf.js`. Follow the instructions you will find inside it. To executing tests whenever any file changes you can modify the 'autoWatch' option to 'true' and 'singleRun' option to 'false'. A coverage report is also available at: http://localhost:9876/ after you run: `yarn run coverage`.
Unit tests use the [Jasmine test framework](https://jasmine.github.io/), and are run via [Karma](https://karma-runner.github.io/).
You can find the Karma configuration file at the same level of this README file:`./karma.conf.js` If you are going to use a remote test environment you need to edit the `./karma.conf.js`. Follow the instructions you will find inside it. To executing tests whenever any file changes you can modify the 'autoWatch' option to 'true' and 'singleRun' option to 'false'. A coverage report is also available at: http://localhost:9876/ after you run: `yarn run coverage`.
The default browser is Google Chrome.
Place your tests in the same location of the application source code files that they test.
Place your tests in the same location of the application source code files that they test, e.g. ending with `*.component.spec.ts`
and run: `yarn run test`
and run: `yarn test`
### E2E test
If you run into odd test errors, see the Angular guide to debugging tests: https://angular.io/guide/test-debugging
E2E tests use Protractor + Selenium server + browsers. You can find the configuration file at the same level of this README file:`./protractor.conf.js` Protractor is installed as 'local' as a dev dependency.
### E2E Tests
If you are going to use a remote test enviroment you need to edit the './e2e//protractor.conf.js'. Follow the instructions you will find inside it.
E2E tests (aka integration tests) use [Cypress.io](https://www.cypress.io/). Configuration for cypress can be found in the `cypress.json` file in the root directory.
The default browser is Google Chrome.
The test files can be found in the `./cypress/integration/` folder.
Place your tests at the following path: `./e2e`
Before you can run e2e tests, you MUST have a running backend (i.e. REST API). By default, the e2e tests look for this at http://localhost:8080/server/ or whatever `rest` backend is defined in your `environment.prod.ts` or `environment.common.ts`. You may override this using env variables, see [Configuring](#configuring).
and run: `ng e2e`
Run `ng e2e` to kick off the tests. This will start Cypress and allow you to select the browser you wish to use, as well as whether you wish to run all tests or an individual test file. Once you click run on test(s), this opens the [Cypress Test Runner](https://docs.cypress.io/guides/core-concepts/test-runner) to run your test(s) and show you the results.
### Continuous Integration (CI) Test
#### Writing E2E Tests
To run all the tests (e.g.: to run tests with Continuous Integration software) you can execute:`yarn run ci` Keep in mind that this command prerequisites are the sum of unit test and E2E tests.
All E2E tests must be created under the `./cypress/integration/` folder, and must end in `.spec.ts`. Subfolders are allowed.
* The easiest way to start creating new tests is by running `ng e2e`. This builds the app and brings up Cypress.
* From here, if you are editing an existing test file, you can either open it in your IDE or run it first to see what it already does.
* To create a new test file, click `+ New Spec File`. Choose a meaningful name ending in `spec.ts` (Please make sure it ends in `.ts` so that it's a Typescript file, and not plain Javascript)
* Start small. Add a basic `describe` and `it` which just [cy.visit](https://docs.cypress.io/api/commands/visit) the page you want to test. For example:
```
describe('Community/Collection Browse Page', () => {
it('should exist as a page', () => {
cy.visit('/community-list');
});
});
```
* Run your test file from the Cypress window. This starts the [Cypress Test Runner](https://docs.cypress.io/guides/core-concepts/test-runner) in a new browser window.
* In the [Cypress Test Runner](https://docs.cypress.io/guides/core-concepts/test-runner), you'll Cypress automatically visit the page. This first test will succeed, as all you are doing is making sure the _page exists_.
* From here, you can use the [Selector Playground](https://docs.cypress.io/guides/core-concepts/test-runner#Selector-Playground) in the Cypress Test Runner window to determine how to tell Cypress to interact with a specific HTML element on that page.
* Most commands start by telling Cypress to [get()](https://docs.cypress.io/api/commands/get) a specific element, using a CSS or jQuery style selector
* Cypress can then do actions like [click()](https://docs.cypress.io/api/commands/click) an element, or [type()](https://docs.cypress.io/api/commands/type) text in an input field, etc.
* Cypress can also validate that something occurs, using [should()](https://docs.cypress.io/api/commands/should) assertions.
* Any time you save your test file, the Cypress Test Runner will reload & rerun it. This allows you can see your results quickly as you write the tests & correct any broken tests rapidly.
* Cypress also has a great guide on [writing your first test](https://on.cypress.io/writing-first-test) with much more info. Keep in mind, while the examples in the Cypress docs often involve Javascript files (.js), the same examples will work in our Typescript (.ts) e2e tests.
_Hint: Creating e2e tests is easiest in an IDE (like Visual Studio), as it can help prompt/autocomplete your Cypress commands._
More Information: [docs.cypress.io](https://docs.cypress.io/) has great guides & documentation helping you learn more about writing/debugging e2e tests in Cypress.
### Learning how to build tests
See our [DSpace Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide) for more hints/tips.
Documentation
--------------
See [`./docs`](docs) for further documentation.
Official DSpace documentation is available in the DSpace wiki at https://wiki.lyrasis.org/display/DSDOC7x/
Some UI specific configuration documentation is also found in the [`./docs`](docs) folder of htis codebase.
### Building code documentation
@@ -234,8 +294,6 @@ To get the most out of TypeScript, you'll need a TypeScript-aware editor. We've
- Free
- [Visual Studio Code](https://code.visualstudio.com/)
- [Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome)
- [Atom](https://atom.io/)
- [TypeScript plugin](https://atom.io/packages/atom-typescript)
- Paid
- [Webstorm](https://www.jetbrains.com/webstorm/download/) or [IntelliJ IDEA Ultimate](https://www.jetbrains.com/idea/)
- [Sublime Text](http://www.sublimetext.com/3)
@@ -257,95 +315,43 @@ dspace-angular
│   ├── environment.default.js * Default configuration files
│   └── environment.test.js * Test configuration files
├── docs * Folder for documentation
├── e2e * Folder for e2e test files
│   ├── app.e2e-spec.ts *
│   ├── app.po.ts *
│   ├── pagenotfound *
│   │   ├── pagenotfound.e2e-spec.ts *
│   │   └── pagenotfound.po.ts *
├── cypress * Folder for Cypress (https://cypress.io/) / e2e tests
│   ├── integration * Folder for e2e/integration test files
│   ├── fixtures * Folder for any fixtures needed by e2e tests
│   ├── plugins * Folder for Cypress plugins (if any)
│   ├── support * Folder for global e2e test actions/commands (run for all tests)
│   └── tsconfig.json * TypeScript configuration file for e2e tests
├── karma.conf.js * Karma configuration file for Unit Test
├── nodemon.json * Nodemon (https://nodemon.io/) configuration
├── package.json * This file describes the npm package for this project, its dependencies, scripts, etc.
├── postcss.config.js * PostCSS (http://postcss.org/) configuration file
├── protractor.conf.js *
├── resources * Folder for static resources
│   ├── data * Folder for static data
│   │   └── en * Folder for i18n English data
│   ├── i18n * Folder for i18n translations
│   │   └── en.json * i18n translations for English
│   └── images * Folder for images
│   ├── dspace-logo-old.png *
│   ├── dspace-logo.png *
│   └── favicon.ico *
├── rollup.config.js * Rollup (http://rollupjs.org/) configuration
├── spec-bundle.js *
├── src * The source of the application
│   ├── app *
│   │   ├── app-routing.module.ts *
│   │   ├── app.component.html *
│   │   ├── app.component.scss *
│   │   ├── app.component.spec.ts *
│   │   ── app.component.ts *
│   │   ├── app.effects.ts *
│   │   ├── app.module.ts *
│   │   ├── app.reducer.ts *
│   │   ├── browser-app.module.ts * The root module for the client
│   │   ├── +collection-page * Lazily loaded route for collection module
│   │   ├── +community-page * Lazily loaded route for community module
│   │   ├── core *
│   │   ├── header *
│   │   ├── +home * Lazily loaded route for home module
│   │   ├── +item-page * Lazily loaded route for item module
│   │   ├── object-list *
│   │   ├── pagenotfound *
│   │   ├── server-app.module.ts * The root module for the server
│   │   ├── shared *
│   │   ├── store.actions.ts *
│   │   ├── store.effects.ts *
│   │   ├── thumbnail *
│   │   └── typings.d.ts * File that allows you to add custom typings for libraries without TypeScript support
│   ├── app * The source code of the application, subdivided by module/page.
│   ├── assets * Folder for static resources
│   │   ├── fonts * Folder for fonts
│   │   ├── i18n * Folder for i18n translations
│   | └── en.json5 * i18n translations for English
│   │   ── images * Folder for images
│   ├── backend * Folder containing a mock of the REST API, hosted by the express server
│   │   ├── api.ts *
│   │   ├── cache.ts *
│   │   ├── data *
│   │   └── db.ts *
│   ├── config *
│   │   ├── cache-config.interface.ts *
│   │   ├── config.interface.ts *
│   │   ├── global-config.interface.ts *
│   │   ├── server-config.interface.ts *
│   │   └── universal-config.interface.ts *
│   ├── config.ts * File that loads environmental and shareable settings and makes them available to app components
│   ├── index.csr.html * The index file for client side rendering fallback
│   ├── index.html * The index file
│   ├── main.browser.ts * The bootstrap file for the client
│   ├── main.server.ts * The express (http://expressjs.com/) config and bootstrap file for the server
│   ├── robots.txt * The robots.txt file
│   ├── modules *
│   │   ├── cookies *
│   │   ├── data-loader *
│   │   ├── transfer-http *
│   │   ├── transfer-state *
│   │   ├── transfer-store *
│   │   └── translate-universal-loader.ts *
│   ├── routes.ts * The routes file for the server
│   ├── styles * Folder containing global styles
│   │   ├── _mixins.scss *
│      ── variables.scss * Global sass variables file
│   ├── tsconfig.browser.json * TypeScript config for the client build
│   ├── tsconfig.server.json * TypeScript config for the server build
│   └── tsconfig.test.json * TypeScript config for the test build
│   └── themes * Folder containing available themes
│      ── custom * Template folder for creating a custom theme
│      └── dspace * Default 'dspace' theme
├── tsconfig.json * TypeScript config
├── tslint.json * TSLint (https://palantir.github.io/tslint/) configuration
├── typedoc.json * TYPEDOC configuration
├── webpack * Webpack (https://webpack.github.io/) config directory
│   ├── webpack.aot.js * Webpack (https://webpack.github.io/) config for AoT build
│   ├── webpack.client.js * Webpack (https://webpack.github.io/) config for client build
│   ├── webpack.common.js *
│   ── webpack.prod.js * Webpack (https://webpack.github.io/) config for production build
│   ├── webpack.server.js * Webpack (https://webpack.github.io/) config for server build
│   └── webpack.test.js * Webpack (https://webpack.github.io/) config for test build
├── webpack.config.ts *
│   ├── webpack.browser.ts * Webpack (https://webpack.github.io/) config for client build
│   ├── webpack.common.ts *
│   ├── webpack.prod.ts * Webpack (https://webpack.github.io/) config for production build
│   ── webpack.test.ts * Webpack (https://webpack.github.io/) config for test build
└── yarn.lock * Yarn lockfile (https://yarnpkg.com/en/docs/yarn-lock)
```
@@ -403,8 +409,8 @@ Frequently asked questions
- You can write your tests next to your component files. e.g. for `src/app/home/home.component.ts` call it `src/app/home/home.component.spec.ts`
- How do I start the app when I get `EACCES` and `EADDRINUSE` errors?
- The `EADDRINUSE` error means the port `4000` is currently being used and `EACCES` is lack of permission to build files to `./dist/`
- What are the naming conventions for Angular 2?
- See [the official angular 2 style guide](https://angular.io/styleguide)
- What are the naming conventions for Angular?
- See [the official angular style guide](https://angular.io/styleguide)
- Why is the size of my app larger in development?
- The production build uses a whole host of techniques (ahead-of-time compilation, rollup to remove unreachable code, minification, etc.) to reduce the size, that aren't used during development in the intrest of build speed.
- node-pre-gyp ERR in yarn install (Windows)
@@ -415,6 +421,32 @@ Frequently asked questions
- then run `git add yarn.lock` to stage the lockfile for commit
- and `git commit` to conclude the merge
Getting Help
------------
DSpace provides public mailing lists where you can post questions or raise topics for discussion.
We welcome everyone to participate in these lists:
* [dspace-community@googlegroups.com](https://groups.google.com/d/forum/dspace-community) : General discussion about DSpace platform, announcements, sharing of best practices
* [dspace-tech@googlegroups.com](https://groups.google.com/d/forum/dspace-tech) : Technical support mailing list. See also our guide for [How to troubleshoot an error](https://wiki.lyrasis.org/display/DSPACE/Troubleshoot+an+error).
* [dspace-devel@googlegroups.com](https://groups.google.com/d/forum/dspace-devel) : Developers / Development mailing list
Great Q&A is also available under the [DSpace tag on Stackoverflow](http://stackoverflow.com/questions/tagged/dspace)
Additional support options are at https://wiki.lyrasis.org/display/DSPACE/Support
DSpace also has an active service provider network. If you'd rather hire a service provider to
install, upgrade, customize or host DSpace, then we recommend getting in touch with one of our
[Registered Service Providers](http://www.dspace.org/service-providers).
Issue Tracker
-------------
DSpace uses GitHub to track issues:
* Backend (REST API) issues: https://github.com/DSpace/DSpace/issues
* Frontend (User Interface) issues: https://github.com/DSpace/dspace-angular/issues
License
-------
This project's source code is made available under the DSpace BSD License: http://www.dspace.org/license

15
SECURITY.md Normal file
View File

@@ -0,0 +1,15 @@
# Security Policy
## Supported Versions
For information regarding which versions of DSpace are currently under support, please see our DSpace Software Support Policy:
https://wiki.lyrasis.org/display/DSPACE/DSpace+Software+Support+Policy
## Reporting a Vulnerability
If you believe you have found a security vulnerability in a supported version of DSpace, we encourage you to let us know right away.
We will investigate all legitimate reports and do our best to quickly fix the problem. Please see our DSpace Software Support Policy
for information on privately reporting vulnerabilities:
https://wiki.lyrasis.org/display/DSPACE/DSpace+Software+Support+Policy

View File

@@ -47,6 +47,7 @@
"src/robots.txt"
],
"styles": [
"src/styles/startup.scss",
{
"input": "src/styles/base-theme.scss",
"inject": false,
@@ -146,7 +147,7 @@
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
"cypress/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
@@ -154,10 +155,11 @@
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"builder": "@cypress/schematic:cypress",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "dspace-angular:serve"
"devServerTarget": "dspace-angular:serve",
"watch": true,
"headless": false
},
"configurations": {
"production": {
@@ -214,6 +216,24 @@
"configurations": {
"production": {}
}
},
"cypress-run": {
"builder": "@cypress/schematic:cypress",
"options": {
"devServerTarget": "dspace-angular:serve"
},
"configurations": {
"production": {
"devServerTarget": "dspace-angular:serve:production"
}
}
},
"cypress-open": {
"builder": "@cypress/schematic:cypress",
"options": {
"watch": true,
"headless": false
}
}
}
}

9
cypress.json Normal file
View File

@@ -0,0 +1,9 @@
{
"integrationFolder": "cypress/integration",
"supportFile": "cypress/support/index.ts",
"videosFolder": "cypress/videos",
"screenshotsFolder": "cypress/screenshots",
"pluginsFile": "cypress/plugins/index.ts",
"fixturesFolder": "cypress/fixtures",
"baseUrl": "http://localhost:4000"
}

View File

@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -0,0 +1,15 @@
import { TEST_ENTITY_PUBLICATION } from 'cypress/support';
import { testA11y } from 'cypress/support/utils';
describe('Breadcrumbs', () => {
it('should pass accessibility tests', () => {
// Visit an Item, as those have more breadcrumbs
cy.visit('/entities/publication/' + TEST_ENTITY_PUBLICATION);
// Wait for breadcrumbs to be visible
cy.get('ds-breadcrumbs').should('be.visible');
// Analyze <ds-breadcrumbs> for accessibility
testA11y('ds-breadcrumbs');
});
});

View File

@@ -0,0 +1,13 @@
import { testA11y } from 'cypress/support/utils';
describe('Browse By Author', () => {
it('should pass accessibility tests', () => {
cy.visit('/browse/author');
// Wait for <ds-browse-by-metadata-page> to be visible
cy.get('ds-browse-by-metadata-page').should('be.visible');
// Analyze <ds-browse-by-metadata-page> for accessibility
testA11y('ds-browse-by-metadata-page');
});
});

View File

@@ -0,0 +1,13 @@
import { testA11y } from 'cypress/support/utils';
describe('Browse By Date Issued', () => {
it('should pass accessibility tests', () => {
cy.visit('/browse/dateissued');
// Wait for <ds-browse-by-date-page> to be visible
cy.get('ds-browse-by-date-page').should('be.visible');
// Analyze <ds-browse-by-date-page> for accessibility
testA11y('ds-browse-by-date-page');
});
});

View File

@@ -0,0 +1,13 @@
import { testA11y } from 'cypress/support/utils';
describe('Browse By Subject', () => {
it('should pass accessibility tests', () => {
cy.visit('/browse/subject');
// Wait for <ds-browse-by-metadata-page> to be visible
cy.get('ds-browse-by-metadata-page').should('be.visible');
// Analyze <ds-browse-by-metadata-page> for accessibility
testA11y('ds-browse-by-metadata-page');
});
});

View File

@@ -0,0 +1,13 @@
import { testA11y } from 'cypress/support/utils';
describe('Browse By Title', () => {
it('should pass accessibility tests', () => {
cy.visit('/browse/title');
// Wait for <ds-browse-by-title-page> to be visible
cy.get('ds-browse-by-title-page').should('be.visible');
// Analyze <ds-browse-by-title-page> for accessibility
testA11y('ds-browse-by-title-page');
});
});

View File

@@ -0,0 +1,15 @@
import { TEST_COLLECTION } from 'cypress/support';
import { testA11y } from 'cypress/support/utils';
describe('Collection Page', () => {
it('should pass accessibility tests', () => {
cy.visit('/collections/' + TEST_COLLECTION);
// <ds-collection-page> tag must be loaded
cy.get('ds-collection-page').should('exist');
// Analyze <ds-collection-page> for accessibility issues
testA11y('ds-collection-page');
});
});

View File

@@ -0,0 +1,32 @@
import { TEST_COLLECTION } from 'cypress/support';
import { testA11y } from 'cypress/support/utils';
describe('Collection Statistics Page', () => {
const COLLECTIONSTATISTICSPAGE = '/statistics/collections/' + TEST_COLLECTION;
it('should load if you click on "Statistics" from a Collection page', () => {
cy.visit('/collections/' + TEST_COLLECTION);
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
cy.location('pathname').should('eq', COLLECTIONSTATISTICSPAGE);
});
it('should contain a "Total visits" section', () => {
cy.visit(COLLECTIONSTATISTICSPAGE);
cy.get('.' + TEST_COLLECTION + '_TotalVisits').should('exist');
});
it('should contain a "Total visits per month" section', () => {
cy.visit(COLLECTIONSTATISTICSPAGE);
cy.get('.' + TEST_COLLECTION + '_TotalVisitsPerMonth').should('exist');
});
it('should pass accessibility tests', () => {
cy.visit(COLLECTIONSTATISTICSPAGE);
// <ds-collection-statistics-page> tag must be loaded
cy.get('ds-collection-statistics-page').should('exist');
// Analyze <ds-collection-statistics-page> for accessibility issues
testA11y('ds-collection-statistics-page');
});
});

View File

@@ -0,0 +1,25 @@
import { Options } from 'cypress-axe';
import { testA11y } from 'cypress/support/utils';
describe('Community List Page', () => {
it('should pass accessibility tests', () => {
cy.visit('/community-list');
// <ds-community-list-page> tag must be loaded
cy.get('ds-community-list-page').should('exist');
// Open first Community (to show Collections)...that way we scan sub-elements as well
cy.get('ds-community-list :nth-child(1) > .btn-group > .btn').click();
// Analyze <ds-community-list-page> for accessibility issues
// Disable heading-order checks until it is fixed
testA11y('ds-community-list-page',
{
rules: {
'heading-order': { enabled: false }
}
} as Options
);
});
});

View File

@@ -0,0 +1,15 @@
import { TEST_COMMUNITY } from 'cypress/support';
import { testA11y } from 'cypress/support/utils';
describe('Community Page', () => {
it('should pass accessibility tests', () => {
cy.visit('/communities/' + TEST_COMMUNITY);
// <ds-community-page> tag must be loaded
cy.get('ds-community-page').should('exist');
// Analyze <ds-community-page> for accessibility issues
testA11y('ds-community-page',);
});
});

View File

@@ -0,0 +1,32 @@
import { TEST_COMMUNITY } from 'cypress/support';
import { testA11y } from 'cypress/support/utils';
describe('Community Statistics Page', () => {
const COMMUNITYSTATISTICSPAGE = '/statistics/communities/' + TEST_COMMUNITY;
it('should load if you click on "Statistics" from a Community page', () => {
cy.visit('/communities/' + TEST_COMMUNITY);
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
cy.location('pathname').should('eq', COMMUNITYSTATISTICSPAGE);
});
it('should contain a "Total visits" section', () => {
cy.visit(COMMUNITYSTATISTICSPAGE);
cy.get('.' + TEST_COMMUNITY + '_TotalVisits').should('exist');
});
it('should contain a "Total visits per month" section', () => {
cy.visit(COMMUNITYSTATISTICSPAGE);
cy.get('.' + TEST_COMMUNITY + '_TotalVisitsPerMonth').should('exist');
});
it('should pass accessibility tests', () => {
cy.visit(COMMUNITYSTATISTICSPAGE);
// <ds-community-statistics-page> tag must be loaded
cy.get('ds-community-statistics-page').should('exist');
// Analyze <ds-community-statistics-page> for accessibility issues
testA11y('ds-community-statistics-page');
});
});

View File

@@ -0,0 +1,13 @@
import { testA11y } from 'cypress/support/utils';
describe('Footer', () => {
it('should pass accessibility tests', () => {
cy.visit('/');
// Footer must first be visible
cy.get('ds-footer').should('be.visible');
// Analyze <ds-footer> for accessibility
testA11y('ds-footer');
});
});

View File

@@ -0,0 +1,19 @@
import { testA11y } from 'cypress/support/utils';
describe('Header', () => {
it('should pass accessibility tests', () => {
cy.visit('/');
// Header must first be visible
cy.get('ds-header').should('be.visible');
// Analyze <ds-header> for accessibility
testA11y({
include: ['ds-header'],
exclude: [
['#search-navbar-container'], // search in navbar has duplicative ID. Will be fixed in #1174
['.dropdownLogin'] // "Log in" link has color contrast issues. Will be fixed in #1149
],
});
});
});

View File

@@ -0,0 +1,19 @@
import { testA11y } from 'cypress/support/utils';
describe('Site Statistics Page', () => {
it('should load if you click on "Statistics" from homepage', () => {
cy.visit('/');
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
cy.location('pathname').should('eq', '/statistics');
});
it('should pass accessibility tests', () => {
cy.visit('/statistics');
// <ds-site-statistics-page> tag must be loaded
cy.get('ds-site-statistics-page').should('exist');
// Analyze <ds-site-statistics-page> for accessibility issues
testA11y('ds-site-statistics-page');
});
});

View File

@@ -0,0 +1,32 @@
import { testA11y } from 'cypress/support/utils';
describe('Homepage', () => {
beforeEach(() => {
// All tests start with visiting homepage
cy.visit('/');
});
it('should display translated title "DSpace Angular :: Home"', () => {
cy.title().should('eq', 'DSpace Angular :: Home');
});
it('should contain a news section', () => {
cy.get('ds-home-news').should('be.visible');
});
it('should have a working search box', () => {
const queryString = 'test';
cy.get('ds-search-form input[name="query"]').type(queryString);
cy.get('ds-search-form button.search-button').click();
cy.url().should('include', '/search');
cy.url().should('include', 'query=' + encodeURI(queryString));
});
it('should pass accessibility tests', () => {
// Wait for homepage tag to appear
cy.get('ds-home-page').should('be.visible');
// Analyze <ds-home-page> for accessibility issues
testA11y('ds-home-page');
});
});

View File

@@ -0,0 +1,31 @@
import { Options } from 'cypress-axe';
import { TEST_ENTITY_PUBLICATION } from 'cypress/support';
import { testA11y } from 'cypress/support/utils';
describe('Item Page', () => {
const ITEMPAGE = '/items/' + TEST_ENTITY_PUBLICATION;
const ENTITYPAGE = '/entities/publication/' + TEST_ENTITY_PUBLICATION;
// Test that entities will redirect to /entities/[type]/[uuid] when accessed via /items/[uuid]
it('should redirect to the entity page when navigating to an item page', () => {
cy.visit(ITEMPAGE);
cy.location('pathname').should('eq', ENTITYPAGE);
});
it('should pass accessibility tests', () => {
cy.visit(ENTITYPAGE);
// <ds-item-page> tag must be loaded
cy.get('ds-item-page').should('exist');
// Analyze <ds-item-page> for accessibility issues
// Disable heading-order checks until it is fixed
testA11y('ds-item-page',
{
rules: {
'heading-order': { enabled: false }
}
} as Options
);
});
});

View File

@@ -0,0 +1,38 @@
import { TEST_ENTITY_PUBLICATION } from 'cypress/support';
import { testA11y } from 'cypress/support/utils';
describe('Item Statistics Page', () => {
const ITEMSTATISTICSPAGE = '/statistics/items/' + TEST_ENTITY_PUBLICATION;
it('should load if you click on "Statistics" from an Item/Entity page', () => {
cy.visit('/entities/publication/' + TEST_ENTITY_PUBLICATION);
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
cy.location('pathname').should('eq', ITEMSTATISTICSPAGE);
});
it('should contain element ds-item-statistics-page when navigating to an item statistics page', () => {
cy.visit(ITEMSTATISTICSPAGE);
cy.get('ds-item-statistics-page').should('exist');
cy.get('ds-item-page').should('not.exist');
});
it('should contain a "Total visits" section', () => {
cy.visit(ITEMSTATISTICSPAGE);
cy.get('.' + TEST_ENTITY_PUBLICATION + '_TotalVisits').should('exist');
});
it('should contain a "Total visits per month" section', () => {
cy.visit(ITEMSTATISTICSPAGE);
cy.get('.' + TEST_ENTITY_PUBLICATION + '_TotalVisitsPerMonth').should('exist');
});
it('should pass accessibility tests', () => {
cy.visit(ITEMSTATISTICSPAGE);
// <ds-item-statistics-page> tag must be loaded
cy.get('ds-item-statistics-page').should('exist');
// Analyze <ds-item-statistics-page> for accessibility issues
testA11y('ds-item-statistics-page');
});
});

View File

@@ -0,0 +1,13 @@
describe('PageNotFound', () => {
it('should contain element ds-pagenotfound when navigating to page that doesnt exist', () => {
// request an invalid page (UUIDs at root path aren't valid)
cy.visit('/e9019a69-d4f1-4773-b6a3-bd362caa46f2', { failOnStatusCode: false });
cy.get('ds-pagenotfound').should('exist');
});
it('should not contain element ds-pagenotfound when navigating to existing page', () => {
cy.visit('/home');
cy.get('ds-pagenotfound').should('not.exist');
});
});

View File

@@ -0,0 +1,49 @@
const page = {
fillOutQueryInNavBar(query) {
// Click the magnifying glass
cy.get('.navbar-container #search-navbar-container form a').click();
// Fill out a query in input that appears
cy.get('.navbar-container #search-navbar-container form input[name = "query"]').type(query);
},
submitQueryByPressingEnter() {
cy.get('.navbar-container #search-navbar-container form input[name = "query"]').type('{enter}');
},
submitQueryByPressingIcon() {
cy.get('.navbar-container #search-navbar-container form .submit-icon').click();
}
};
describe('Search from Navigation Bar', () => {
// NOTE: these tests currently assume this query will return results!
const query = 'test';
it('should go to search page with correct query if submitted (from home)', () => {
cy.visit('/');
page.fillOutQueryInNavBar(query);
page.submitQueryByPressingEnter();
// New URL should include query param
cy.url().should('include', 'query=' + query);
// At least one search result should be displayed
cy.get('ds-item-search-result-list-element').should('be.visible');
});
it('should go to search page with correct query if submitted (from search)', () => {
cy.visit('/search');
page.fillOutQueryInNavBar(query);
page.submitQueryByPressingEnter();
// New URL should include query param
cy.url().should('include', 'query=' + query);
// At least one search result should be displayed
cy.get('ds-item-search-result-list-element').should('be.visible');
});
it('should allow user to also submit query by clicking icon', () => {
cy.visit('/');
page.fillOutQueryInNavBar(query);
page.submitQueryByPressingIcon();
// New URL should include query param
cy.url().should('include', 'query=' + query);
// At least one search result should be displayed
cy.get('ds-item-search-result-list-element').should('be.visible');
});
});

View File

@@ -0,0 +1,72 @@
import { Options } from 'cypress-axe';
import { testA11y } from 'cypress/support/utils';
describe('Search Page', () => {
// unique ID of the search form (for selecting specific elements below)
const SEARCHFORM_ID = '#search-form';
it('should contain query value when navigating to page with query parameter', () => {
const queryString = 'test query';
cy.visit('/search?query=' + queryString);
cy.get(SEARCHFORM_ID + ' input[name="query"]').should('have.value', queryString);
});
it('should redirect to the correct url when query was set and submit button was triggered', () => {
const queryString = 'Another interesting query string';
cy.visit('/search');
// Type query in searchbox & click search button
cy.get(SEARCHFORM_ID + ' input[name="query"]').type(queryString);
cy.get(SEARCHFORM_ID + ' button.search-button').click();
cy.url().should('include', 'query=' + encodeURI(queryString));
});
it('should pass accessibility tests', () => {
cy.visit('/search');
// <ds-search-page> tag must be loaded
cy.get('ds-search-page').should('exist');
// Click each filter toggle to open *every* filter
// (As we want to scan filter section for accessibility issues as well)
cy.get('.filter-toggle').click({ multiple: true });
// Analyze <ds-search-page> for accessibility issues
testA11y(
{
include: ['ds-search-page'],
exclude: [
['nouislider'] // Date filter slider is missing ARIA labels. Will be fixed by #1175
],
},
{
rules: {
// Search filters fail these two "moderate" impact rules
'heading-order': { enabled: false },
'landmark-unique': { enabled: false }
}
} as Options
);
});
it('should pass accessibility tests in Grid view', () => {
cy.visit('/search');
// Click to display grid view
// TODO: These buttons should likely have an easier way to uniquely select
cy.get('#search-sidebar-content > ds-view-mode-switch > .btn-group > [href="/search?spc.sf=score&spc.sd=DESC&view=grid"] > .fas').click();
// <ds-search-page> tag must be loaded
cy.get('ds-search-page').should('exist');
// Analyze <ds-search-page> for accessibility issues
testA11y('ds-search-page',
{
rules: {
// Search filters fail these two "moderate" impact rules
'heading-order': { enabled: false },
'landmark-unique': { enabled: false }
}
} as Options
);
});
});

16
cypress/plugins/index.ts Normal file
View File

@@ -0,0 +1,16 @@
// Plugins enable you to tap into, modify, or extend the internal behavior of Cypress
// For more info, visit https://on.cypress.io/plugins-api
module.exports = (on, config) => {
// Define "log" and "table" tasks, used for logging accessibility errors during CI
// Borrowed from https://github.com/component-driven/cypress-axe#in-cypress-plugins-file
on('task', {
log(message: string) {
console.log(message);
return null;
},
table(message: string) {
console.table(message);
return null;
}
});
};

View File

@@ -0,0 +1,43 @@
// ***********************************************
// This example namespace declaration will help
// with Intellisense and code completion in your
// IDE or Text Editor.
// ***********************************************
// declare namespace Cypress {
// interface Chainable<Subject = any> {
// customCommand(param: any): typeof customCommand;
// }
// }
//
// function customCommand(param: any): void {
// console.warn(param);
// }
//
// NOTE: You can use it like so:
// Cypress.Commands.add('customCommand', customCommand);
//
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

26
cypress/support/index.ts Normal file
View File

@@ -0,0 +1,26 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// When a command from ./commands is ready to use, import with `import './commands'` syntax
// import './commands';
// Import Cypress Axe tools for all tests
// https://github.com/component-driven/cypress-axe
import 'cypress-axe';
// Global constants used in tests
export const TEST_COLLECTION = '282164f5-d325-4740-8dd1-fa4d6d3e7200';
export const TEST_COMMUNITY = '0958c910-2037-42a9-81c7-dca80e3892b4';
export const TEST_ENTITY_PUBLICATION = 'e98b0f27-5c19-49a0-960d-eb6ad5287067';

44
cypress/support/utils.ts Normal file
View File

@@ -0,0 +1,44 @@
import { Result } from 'axe-core';
import { Options } from 'cypress-axe';
// Log violations to terminal/commandline in a table format.
// Uses 'log' and 'table' tasks defined in ../plugins/index.ts
// Borrowed from https://github.com/component-driven/cypress-axe#in-your-spec-file
function terminalLog(violations: Result[]) {
cy.task(
'log',
`${violations.length} accessibility violation${violations.length === 1 ? '' : 's'} ${violations.length === 1 ? 'was' : 'were'} detected`
);
// pluck specific keys to keep the table readable
const violationData = violations.map(
({ id, impact, description, helpUrl, nodes }) => ({
id,
impact,
description,
helpUrl,
nodes: nodes.length,
html: nodes.map(node => node.html)
})
);
// Print violations as an array, since 'node.html' above often breaks table alignment
cy.task('log', violationData);
// Optionally, uncomment to print as a table
// cy.task('table', violationData);
}
// Custom "testA11y()" method which checks accessibility using cypress-axe
// while also ensuring any violations are logged to the terminal (see terminalLog above)
// This method MUST be called after cy.visit(), as cy.injectAxe() must be called after page load
export const testA11y = (context?: any, options?: Options) => {
cy.injectAxe();
cy.configureAxe({
rules: [
// Disable color contrast checks as they are inaccurate / result in a lot of false positives
// See also open issues in axe-core: https://github.com/dequelabs/axe-core/labels/color%20contrast
{ id: 'color-contrast', enabled: false },
]
});
cy.checkA11y(context, options, terminalLog);
};

13
cypress/tsconfig.json Normal file
View File

@@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"include": [
"**/*.ts"
],
"compilerOptions": {
"types": [
"cypress",
"cypress-axe",
"node"
]
}
}

View File

@@ -1,5 +1,9 @@
# Docker Compose files
***
:warning: **NOT PRODUCTION READY** The below Docker Compose resources are not guaranteed "production ready" at this time. They have been built for development/testing only. Therefore, DSpace Docker images may not be fully secured or up-to-date. While you are welcome to base your own images on these DSpace images/resources, these should not be used "as is" in any production scenario.
***
## docker directory
- docker-compose.yml
- Starts DSpace Angular with Docker Compose from the current branch. This file assumes that a DSpace 7 REST instance will also be started in Docker.

View File

@@ -1,14 +0,0 @@
const config = require('./protractor.conf').config;
config.capabilities = {
browserName: 'chrome',
chromeOptions: {
args: ['--headless', '--no-sandbox', '--disable-gpu']
}
};
// don't use protractor's webdriver, as it may be incompatible with the installed chrome version
config.directConnect = false;
config.seleniumAddress = 'http://localhost:4444/wd/hub';
exports.config = config;

View File

@@ -1,92 +0,0 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/docs/referenceConf.js
/*global jasmine */
var SpecReporter = require('jasmine-spec-reporter').SpecReporter;
exports.config = {
allScriptsTimeout: 600000,
// -----------------------------------------------------------------
// Uncomment to run tests using a remote Selenium server
//seleniumAddress: 'http://selenium.address:4444/wd/hub',
// Change to 'false' to run tests using a remote Selenium server
directConnect: true,
// Change if the website to test is not on the localhost
baseUrl: 'http://localhost:4000/',
// -----------------------------------------------------------------
specs: [
'./src/**/*.e2e-spec.ts'
],
// -----------------------------------------------------------------
// Browser and Capabilities: PhantomJS
// -----------------------------------------------------------------
// capabilities: {
// 'browserName': 'phantomjs',
// 'version': '',
// 'platform': 'ANY'
// },
// -----------------------------------------------------------------
// Browser and Capabilities: Chrome
// -----------------------------------------------------------------
capabilities: {
'browserName': 'chrome',
'version': '',
'platform': 'ANY',
'chromeOptions': {
'args': [ '--headless', '--disable-gpu' ]
}
},
// -----------------------------------------------------------------
// Browser and Capabilities: Firefox
// -----------------------------------------------------------------
// capabilities: {
// 'browserName': 'firefox',
// 'version': '',
// 'platform': 'ANY'
// },
// -----------------------------------------------------------------
// Browser and Capabilities: MultiCapabilities
// -----------------------------------------------------------------
//multiCapabilities: [
// {
// 'browserName': 'phantomjs',
// 'version': '',
// 'platform': 'ANY'
// },
// {
// 'browserName': 'chrome',
// 'version': '',
// 'platform': 'ANY'
// }
// {
// 'browserName': 'firefox',
// 'version': '',
// 'platform': 'ANY'
// }
//],
plugins: [{
path: '../node_modules/protractor-istanbul-plugin'
}],
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 600000,
print: function () {}
},
useAllAngular2AppRoots: true,
beforeLaunch: function () {
require('ts-node').register({
project: './e2e/tsconfig.json'
});
},
onPrepare: function () {
jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: true
}
}));
}
};

View File

@@ -1,22 +0,0 @@
import { ProtractorPage } from './app.po';
describe('protractor App', () => {
let page: ProtractorPage;
beforeEach(() => {
page = new ProtractorPage();
});
it('should display translated title "DSpace Angular :: Home"', () => {
page.navigateTo();
page.waitUntilNotLoading();
expect<any>(page.getPageTitleText()).toEqual('DSpace Angular :: Home');
});
it('should contain a news section', () => {
page.navigateTo();
page.waitUntilNotLoading();
const text = page.getHomePageNewsText();
expect<any>(text).toBeDefined();
});
});

View File

@@ -1,23 +0,0 @@
import { browser, element, by, protractor, promise } from 'protractor';
export class ProtractorPage {
navigateTo() {
return browser.get('/')
.then(() => browser.waitForAngular());
}
getPageTitleText() {
return browser.getTitle();
}
getHomePageNewsText() {
return element(by.css('ds-home-news')).getText();
}
waitUntilNotLoading(): promise.Promise<unknown> {
const loading = element(by.css('.loader'));
const EC = protractor.ExpectedConditions;
const notLoading = EC.not(EC.presenceOf(loading));
return browser.wait(notLoading, 10000);
}
}

View File

@@ -1,19 +0,0 @@
import { ProtractorPage } from './pagenotfound.po';
describe('protractor PageNotFound', () => {
let page: ProtractorPage;
beforeEach(() => {
page = new ProtractorPage();
});
it('should contain element ds-pagenotfound when navigating to page that doesnt exist', () => {
page.navigateToNonExistingPage();
expect<any>(page.elementTagExists('ds-pagenotfound')).toEqual(true);
});
it('should not contain element ds-pagenotfound when navigating to existing page', () => {
page.navigateToExistingPage();
expect<any>(page.elementTagExists('ds-pagenotfound')).toEqual(false);
});
});

View File

@@ -1,18 +0,0 @@
import { browser, element, by } from 'protractor';
export class ProtractorPage {
HOMEPAGE = '/home';
NONEXISTINGPAGE = '/e9019a69-d4f1-4773-b6a3-bd362caa46f2';
navigateToNonExistingPage() {
return browser.get(this.NONEXISTINGPAGE);
}
navigateToExistingPage() {
return browser.get(this.HOMEPAGE);
}
elementTagExists(tag: string) {
return element(by.tagName(tag)).isPresent();
}
}

View File

@@ -1,46 +0,0 @@
import { ProtractorPage } from './search-navbar.po';
import { browser } from 'protractor';
describe('protractor SearchNavbar', () => {
let page: ProtractorPage;
let queryString: string;
beforeEach(() => {
page = new ProtractorPage();
queryString = 'the test query';
});
it('should go to search page with correct query if submitted (from home)', () => {
page.navigateToHome();
return checkIfSearchWorks();
});
it('should go to search page with correct query if submitted (from search)', () => {
page.navigateToSearch();
return checkIfSearchWorks();
});
it('check if can submit search box with pressing button', () => {
page.navigateToHome();
page.expandAndFocusSearchBox();
page.setCurrentQuery(queryString);
page.submitNavbarSearchForm();
browser.wait(() => {
return browser.getCurrentUrl().then((url: string) => {
return url.indexOf('query=' + encodeURI(queryString)) !== -1;
});
});
});
function checkIfSearchWorks(): boolean {
page.setCurrentQuery(queryString);
page.submitByPressingEnter();
browser.wait(() => {
return browser.getCurrentUrl().then((url: string) => {
return url.indexOf('query=' + encodeURI(queryString)) !== -1;
});
});
return false;
}
});

View File

@@ -1,35 +0,0 @@
import { browser, by, element, protractor } from 'protractor';
import { promise } from 'selenium-webdriver';
export class ProtractorPage {
HOME = '/home';
SEARCH = '/search';
navigateToHome() {
return browser.get(this.HOME);
}
navigateToSearch() {
return browser.get(this.SEARCH);
}
getCurrentQuery(): promise.Promise<string> {
return element(by.css('.navbar-container #search-navbar-container form input')).getAttribute('value');
}
expandAndFocusSearchBox() {
element(by.css('.navbar-container #search-navbar-container form a')).click();
}
setCurrentQuery(query: string) {
element(by.css('.navbar-container #search-navbar-container form input[name="query"]')).sendKeys(query);
}
submitNavbarSearchForm() {
element(by.css('.navbar-container #search-navbar-container form .submit-icon')).click();
}
submitByPressingEnter() {
element(by.css('.navbar-container #search-navbar-container form input[name="query"]')).sendKeys(protractor.Key.ENTER);
}
}

View File

@@ -1,60 +0,0 @@
import { ProtractorPage } from './search-page.po';
import { browser } from 'protractor';
describe('protractor SearchPage', () => {
let page: ProtractorPage;
beforeEach(() => {
page = new ProtractorPage();
});
it('should contain query value when navigating to page with query parameter', () => {
const queryString = 'Interesting query string';
page.navigateToSearchWithQueryParameter(queryString)
.then(() => page.getCurrentQuery())
.then((query: string) => {
expect<string>(query).toEqual(queryString);
});
});
it('should have right scope selected when navigating to page with scope parameter', () => {
page.navigateToSearch()
.then(() => page.getRandomScopeOption())
.then((scopeString: string) => {
page.navigateToSearchWithScopeParameter(scopeString);
page.waitUntilNotLoading();
page.getCurrentScope()
.then((s: string) => {
expect<string>(s).toEqual(scopeString);
});
});
});
it('should redirect to the correct url when scope was set and submit button was triggered', () => {
page.navigateToSearch()
.then(() => page.getRandomScopeOption())
.then((scopeString: string) => {
page.setCurrentScope(scopeString)
.then(() => page.submitSearchForm())
.then(() => page.waitUntilNotLoading())
.then(() => () => {
browser.wait(() => {
return browser.getCurrentUrl().then((url: string) => {
return url.indexOf('scope=' + encodeURI(scopeString)) !== -1;
});
});
});
});
});
it('should redirect to the correct url when query was set and submit button was triggered', () => {
const queryString = 'Another interesting query string';
page.setCurrentQuery(queryString);
page.submitSearchForm();
browser.wait(() => {
return browser.getCurrentUrl().then((url: string) => {
return url.indexOf('query=' + encodeURI(queryString)) !== -1;
});
});
});
});

View File

@@ -1,55 +0,0 @@
import { browser, by, element, protractor } from 'protractor';
import { promise } from 'selenium-webdriver';
export class ProtractorPage {
SEARCH = '/search';
navigateToSearch() {
return browser.get(this.SEARCH);
}
navigateToSearchWithQueryParameter(query: string) {
return browser.get(this.SEARCH + '?query=' + query);
}
navigateToSearchWithScopeParameter(scope: string) {
return browser.get(this.SEARCH + '?scope=' + scope);
}
getCurrentScope(): promise.Promise<string> {
const scopeSelect = element(by.css('#search-form select'));
browser.wait(protractor.ExpectedConditions.presenceOf(scopeSelect), 10000);
return scopeSelect.getAttribute('value');
}
getCurrentQuery(): promise.Promise<string> {
return element(by.css('#search-form input')).getAttribute('value');
}
setCurrentScope(scope: string) {
return element(by.css('#search-form option[value="' + scope + '"]')).click();
}
setCurrentQuery(query: string) {
element(by.css('#search-form input[name="query"]')).sendKeys(query);
}
submitSearchForm() {
return element(by.css('#search-form button.search-button')).click();
}
getRandomScopeOption(): promise.Promise<string> {
const options = element(by.css('select[name="scope"]')).all(by.tagName('option'));
return options.count().then((c: number) => {
const index: number = Math.floor(Math.random() * (c - 1));
return options.get(index + 1).getAttribute('value');
});
}
waitUntilNotLoading(): promise.Promise<unknown> {
const loading = element(by.css('.loader'));
const EC = protractor.ExpectedConditions;
const notLoading = EC.not(EC.presenceOf(loading));
return browser.wait(notLoading, 10000);
}
}

View File

@@ -1,16 +0,0 @@
{
"compileOnSave": false,
"compilerOptions": {
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "commonjs",
"moduleResolution": "node",
"outDir": "../dist/out-tsc-e2e",
"sourceMap": true,
"target": "es2018",
"typeRoots": [
"../node_modules/@types"
]
}
}

View File

@@ -8,6 +8,7 @@
"config:test": "ts-node --project ./tsconfig.ts-node.json scripts/set-mock-env.ts",
"config:test:watch": "nodemon --config mock-nodemon.json",
"config:dev:watch": "nodemon",
"config:check:rest": "yarn run config:prod && ts-node --project ./tsconfig.ts-node.json scripts/test-rest.ts",
"prestart:dev": "yarn run config:dev",
"prebuild": "yarn run config:dev",
"pretest": "yarn run config:test",
@@ -15,11 +16,11 @@
"pretest:headless": "yarn run config:test",
"prebuild:prod": "yarn run config:prod",
"pree2e": "yarn run config:prod",
"pree2e:ci": "yarn run config:prod",
"start": "yarn run start:prod",
"serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts",
"start:dev": "npm-run-all --parallel config:dev:watch serve",
"start:prod": "yarn run build:prod && yarn run serve:ssr",
"start:mirador:prod": "yarn run build:mirador && yarn run start:prod",
"analyze": "webpack-bundle-analyzer dist/browser/stats.json",
"build": "ng build",
"build:stats": "ng build --stats-json",
@@ -32,7 +33,6 @@
"lint": "ng lint",
"lint-fix": "ng lint --fix=true",
"e2e": "ng e2e",
"e2e:ci": "ng e2e --webdriver-update=false --protractor-config=./e2e/protractor-ci.conf.js",
"compile:server": "webpack --config webpack.server.config.js --progress --color",
"serve:ssr": "node dist/server",
"clean:coverage": "rimraf coverage",
@@ -46,7 +46,11 @@
"clean": "yarn run clean:prod && yarn run clean:env && yarn run clean:node",
"clean:env": "rimraf src/environments/environment.ts",
"sync-i18n": "yarn run config:dev && ts-node --project ./tsconfig.ts-node.json scripts/sync-i18n-files.ts",
"postinstall": "ngcc"
"build:mirador": "webpack --config webpack/webpack.mirador.config.ts",
"merge-i18n": "yarn run config:dev && ts-node --project ./tsconfig.ts-node.json scripts/merge-i18n-files.ts",
"postinstall": "ngcc",
"cypress:open": "cypress open",
"cypress:run": "cypress run"
},
"browser": {
"fs": false,
@@ -56,7 +60,8 @@
},
"private": true,
"resolutions": {
"minimist": "^1.2.5"
"minimist": "^1.2.5",
"webdriver-manager": "^12.1.8"
},
"dependencies": {
"@angular/animations": "~10.2.3",
@@ -71,6 +76,7 @@
"@angular/platform-server": "~10.2.3",
"@angular/router": "~10.2.3",
"@angularclass/bootloader": "1.0.1",
"@kolkov/ngx-gallery": "^1.2.3",
"@ng-bootstrap/ng-bootstrap": "7.0.0",
"@ng-dynamic-forms/core": "^12.0.0",
"@ng-dynamic-forms/ui-ng-bootstrap": "^12.0.0",
@@ -103,6 +109,9 @@
"jsonschema": "1.4.0",
"jwt-decode": "^3.1.2",
"klaro": "^0.7.10",
"mirador": "^3.0.0",
"mirador-dl-plugin": "^0.13.0",
"mirador-share-plugin": "^0.10.0",
"moment": "^2.29.1",
"morgan": "^1.10.0",
"ng-mocks": "10.5.4",
@@ -115,6 +124,8 @@
"nouislider": "^14.6.3",
"pem": "1.14.4",
"postcss-cli": "^8.3.0",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^6.6.3",
"rxjs-spy": "^7.5.3",
@@ -122,8 +133,7 @@
"sortablejs": "1.13.0",
"tslib": "^2.0.0",
"webfontloader": "1.6.28",
"zone.js": "^0.10.3",
"@kolkov/ngx-gallery": "^1.2.3"
"zone.js": "^0.10.3"
},
"devDependencies": {
"@angular-builders/custom-webpack": "10.0.1",
@@ -131,6 +141,7 @@
"@angular/cli": "~10.2.0",
"@angular/compiler-cli": "~10.2.3",
"@angular/language-service": "~10.2.3",
"@cypress/schematic": "^1.5.0",
"@fortawesome/fontawesome-free": "^5.5.0",
"@ngrx/store-devtools": "^10.0.1",
"@ngtools/webpack": "10.2.0",
@@ -143,14 +154,18 @@
"@types/js-cookie": "2.2.6",
"@types/lodash": "^4.14.165",
"@types/node": "^14.14.9",
"axe-core": "^4.3.3",
"codelyzer": "^6.0.1",
"compression-webpack-plugin": "^3.0.1",
"copy-webpack-plugin": "^6.4.1",
"css-loader": "3.4.0",
"cssnano": "^4.1.10",
"cypress": "8.6.0",
"cypress-axe": "^0.13.0",
"deep-freeze": "0.0.1",
"dotenv": "^8.2.0",
"fork-ts-checker-webpack-plugin": "^6.0.3",
"html-loader": "^1.3.2",
"html-webpack-plugin": "^4.5.0",
"http-proxy-middleware": "^1.0.5",
"jasmine-core": "^3.6.0",

View File

@@ -0,0 +1,99 @@
import { projectRoot} from '../webpack/helpers';
const commander = require('commander');
const fs = require('fs');
const JSON5 = require('json5');
const _cliProgress = require('cli-progress');
const _ = require('lodash');
const program = new commander.Command();
program.version('1.0.0', '-v, --version');
const LANGUAGE_FILES_LOCATION = 'src/assets/i18n';
parseCliInput();
/**
* Purpose: Allows customization of i18n labels from within themes
* e.g. Customize the label "menu.section.browse_global" to display "Browse DSpace" rather than "All of DSpace"
*
* This script uses the i18n files found in a source directory to override settings in files with the same
* name in a destination directory. Only the i18n labels to be overridden need be in the source files.
*
* Execution (using custom theme):
* ```
* yarn merge-i18n -s src/themes/custom/assets/i18n
* ```
*
* Input parameters:
* * Output directory: The directory in which the original i18n files are stored
* - Defaults to src/assets/i18n (the default i18n file location)
* - This is where the final output files will be written
* * Source directory: The directory with override files
* - Required
* - Recommended to place override files in the theme directory under assets/i18n (but this is not required)
* - Files must have matching names in both source and destination directories, for example:
* en.json5 in the source directory will be merged with en.json5 in the destination directory
* fr.json5 in the source directory will be merged with fr.json5 in the destination directory
*/
function parseCliInput() {
program
.option('-d, --output-dir <output-dir>', 'output dir when running script on all language files', projectRoot(LANGUAGE_FILES_LOCATION))
.option('-s, --source-dir <source-dir>', 'source dir of transalations to be merged')
.usage('(-s <source-dir> [-d <output-dir>])')
.parse(process.argv);
if (program.outputDir && program.sourceDir) {
if (!fs.existsSync(program.outputDir) && !fs.lstatSync(program.outputDir).isDirectory() ) {
console.error('Output does not exist or is not a directory.');
console.log(program.outputHelp());
process.exit(1);
}
if (!fs.existsSync(program.sourceDir) && !fs.lstatSync(program.sourceDir).isDirectory() ) {
console.error('Source does not exist or is not a directory.');
console.log(program.outputHelp());
process.exit(1);
}
fs.readdirSync(projectRoot(program.sourceDir)).forEach(file => {
if (fs.existsSync(program.outputDir + '/' + file) ) {
console.log('Merging: ' + program.outputDir + '/' + file + ' with ' + program.sourceDir + '/' + file);
mergeFileWithSource(program.sourceDir + '/' + file, program.outputDir + '/' + file);
}
});
} else {
console.error('Source or Output parameter is missing.');
console.log(program.outputHelp());
process.exit(1);
}
}
/**
* Reads source file and output file to merge the contents
* > Iterates over the source file keys
* > Updates values for each key and adds new keys as needed
* > Updates the output file with the new merged json
* @param pathToSourceFile Valid path to source file to merge from
* @param pathToOutputFile Valid path to merge and write output
*/
function mergeFileWithSource(pathToSourceFile, pathToOutputFile) {
const progressBar = new _cliProgress.SingleBar({}, _cliProgress.Presets.shades_classic);
progressBar.start(100, 0);
const sourceFile = fs.readFileSync(pathToSourceFile, 'utf8');
progressBar.update(10);
const outputFile = fs.readFileSync(pathToOutputFile, 'utf8');
progressBar.update(20);
const parsedSource = JSON5.parse(sourceFile);
progressBar.update(30);
const parsedOutput = JSON5.parse(outputFile);
progressBar.update(40);
for (const key of Object.keys(parsedSource)) {
parsedOutput[key] = parsedSource[key];
}
progressBar.update(80);
fs.writeFileSync(pathToOutputFile,JSON5.stringify(parsedOutput,{ space:'\n ', quote: '"' }), { encoding:'utf8' });
progressBar.update(100);
progressBar.stop();
}

66
scripts/test-rest.ts Normal file
View File

@@ -0,0 +1,66 @@
import * as http from 'http';
import * as https from 'https';
import { environment } from '../src/environments/environment';
/**
* Script to test the connection with the configured REST API (in the 'rest' settings of your environment.*.ts)
*
* This script is useful to test for any Node.js connection issues with your REST API.
*
* Usage (see package.json): yarn test:rest-api
*/
// Get root URL of configured REST API
const restUrl = environment.rest.baseUrl + '/api';
console.log(`...Testing connection to REST API at ${restUrl}...\n`);
// If SSL enabled, test via HTTPS, else via HTTP
if (environment.rest.ssl) {
const req = https.request(restUrl, (res) => {
console.log(`RESPONSE: ${res.statusCode} ${res.statusMessage} \n`);
res.on('data', (data) => {
checkJSONResponse(data);
});
});
req.on('error', error => {
console.error('ERROR connecting to REST API\n' + error);
});
req.end();
} else {
const req = http.request(restUrl, (res) => {
console.log(`RESPONSE: ${res.statusCode} ${res.statusMessage} \n`);
res.on('data', (data) => {
checkJSONResponse(data);
});
});
req.on('error', error => {
console.error('ERROR connecting to REST API\n' + error);
});
req.end();
}
/**
* Check JSON response from REST API to see if it looks valid. Log useful information
* @param responseData response data
*/
function checkJSONResponse(responseData: any): any {
let parsedData;
try {
parsedData = JSON.parse(responseData);
console.log('Checking JSON returned for validity...');
console.log(`\t"dspaceVersion" = ${parsedData.dspaceVersion}`);
console.log(`\t"dspaceUI" = ${parsedData.dspaceUI}`);
console.log(`\t"dspaceServer" = ${parsedData.dspaceServer}`);
console.log(`\t"dspaceServer" property matches UI's "rest" config? ${(parsedData.dspaceServer === environment.rest.baseUrl)}`);
// Check for "authn" and "sites" in "_links" section as they should always exist (even if no data)!
const linksFound: string[] = Object.keys(parsedData._links);
console.log(`\tDoes "/api" endpoint have HAL links ("_links" section)? ${linksFound.includes('authn') && linksFound.includes('sites')}`);
} catch (err) {
console.error('ERROR: INVALID DSPACE REST API! Response is not valid JSON!');
console.error(`Response returned:\n${responseData}`);
}
}

View File

@@ -41,6 +41,8 @@ import { UIServerConfig } from './src/config/ui-server-config.interface';
* Set path for the browser application's dist folder
*/
const DIST_FOLDER = join(process.cwd(), 'dist/browser');
// Set path fir IIIF viewer.
const IIIF_VIEWER = join(process.cwd(), 'dist/iiif');
const indexHtml = existsSync(join(DIST_FOLDER, 'index.html')) ? 'index.html' : 'index';
@@ -135,6 +137,10 @@ export function app() {
* Serve static resources (images, i18n messages, …)
*/
server.get('*.*', cacheControl, express.static(DIST_FOLDER, { index: false }));
/*
* Fallthrough to the IIIF viewer (must be included in the build).
*/
server.use('/iiif', express.static(IIIF_VIEWER, {index:false}));
// Register the ngApp callback function to handle incoming requests
server.get('*', ngApp);

View File

@@ -1,11 +0,0 @@
<li class="sidebar-section">
<a class="nav-item nav-link shortcut-icon" attr.aria-labelledby="sidebarName-{{section.id}}" [title]="('menu.section.icon.' + section.id) | translate" [routerLink]="itemModel.link">
<i class="fas fa-{{section.icon}} fa-fw"></i>
</a>
<div class="sidebar-collapsible">
<span id="sidebarName-{{section.id}}" class="section-header-text">
<ng-container
*ngComponentOutlet="(sectionMap$ | async).get(section.id).component; injector: (sectionMap$ | async).get(section.id).injector;"></ng-container>
</span>
</div>
</li>

View File

@@ -1,27 +0,0 @@
<li class="sidebar-section" [ngClass]="{'expanded': (expanded | async)}"
[@bgColor]="{
value: ((expanded | async) ? 'endBackground' : 'startBackground'),
params: {endColor: (sidebarActiveBg | async)}}">
<div class="icon-wrapper">
<a class="nav-item nav-link shortcut-icon" attr.aria.labelledby="sidebarName-{{section.id}}" [title]="('menu.section.icon.' + section.id) | translate" (click)="toggleSection($event)" href="#">
<i class="fas fa-{{section.icon}} fa-fw"></i>
</a>
</div>
<div class="sidebar-collapsible">
<a class="nav-item nav-link" href="#"
(click)="toggleSection($event)">
<span id="sidebarName-{{section.id}}" class="section-header-text">
<ng-container
*ngComponentOutlet="(sectionMap$ | async).get(section.id).component; injector: (sectionMap$ | async).get(section.id).injector;"></ng-container>
</span>
<i class="fas fa-chevron-right fa-pull-right"
[@rotate]="(expanded | async) ? 'expanded' : 'collapsed'" [title]="('menu.section.toggle.' + section.id) | translate"></i>
</a>
<ul class="sidebar-sub-level-items list-unstyled" @slide *ngIf="(expanded | async)">
<li *ngFor="let subSection of (subSections$ | async)">
<ng-container
*ngComponentOutlet="(sectionMap$ | async).get(subSection.id).component; injector: (sectionMap$ | async).get(subSection.id).injector;"></ng-container>
</li>
</ul>
</div>
</li>

View File

@@ -1,38 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { EditBitstreamPageComponent } from './edit-bitstream-page/edit-bitstream-page.component';
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
import { BitstreamPageResolver } from './bitstream-page.resolver';
import { BitstreamDownloadPageComponent } from '../shared/bitstream-download-page/bitstream-download-page.component';
const EDIT_BITSTREAM_PATH = ':id/edit';
/**
* Routing module to help navigate Bitstream pages
*/
@NgModule({
imports: [
RouterModule.forChild([
{
path:':id/download',
component: BitstreamDownloadPageComponent,
resolve: {
bitstream: BitstreamPageResolver
},
},
{
path: EDIT_BITSTREAM_PATH,
component: EditBitstreamPageComponent,
resolve: {
bitstream: BitstreamPageResolver
},
canActivate: [AuthenticatedGuard]
}
])
],
providers: [
BitstreamPageResolver,
]
})
export class BitstreamPageRoutingModule {
}

View File

@@ -1,87 +0,0 @@
import { Component, Input } from '@angular/core';
import {
DynamicFormControlModel,
DynamicFormService,
DynamicInputModel,
DynamicTextAreaModel
} from '@ng-dynamic-forms/core';
import { Collection } from '../../core/shared/collection.model';
import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component';
import { TranslateService } from '@ngx-translate/core';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { CommunityDataService } from '../../core/data/community-data.service';
import { AuthService } from '../../core/auth/auth.service';
import { RequestService } from '../../core/data/request.service';
import { ObjectCacheService } from '../../core/cache/object-cache.service';
/**
* Form used for creating and editing collections
*/
@Component({
selector: 'ds-collection-form',
styleUrls: ['../../shared/comcol-forms/comcol-form/comcol-form.component.scss'],
templateUrl: '../../shared/comcol-forms/comcol-form/comcol-form.component.html'
})
export class CollectionFormComponent extends ComColFormComponent<Collection> {
/**
* @type {Collection} A new collection when a collection is being created, an existing Input collection when a collection is being edited
*/
@Input() dso: Collection = new Collection();
/**
* @type {Collection.type} This is a collection-type form
*/
type = Collection.type;
/**
* The dynamic form fields used for creating/editing a collection
* @type {(DynamicInputModel | DynamicTextAreaModel)[]}
*/
formModel: DynamicFormControlModel[] = [
new DynamicInputModel({
id: 'title',
name: 'dc.title',
required: true,
validators: {
required: null
},
errorMessages: {
required: 'Please enter a name for this title'
},
}),
new DynamicTextAreaModel({
id: 'description',
name: 'dc.description',
}),
new DynamicTextAreaModel({
id: 'abstract',
name: 'dc.description.abstract',
}),
new DynamicTextAreaModel({
id: 'rights',
name: 'dc.rights',
}),
new DynamicTextAreaModel({
id: 'tableofcontents',
name: 'dc.description.tableofcontents',
}),
new DynamicTextAreaModel({
id: 'license',
name: 'dc.rights.license',
}),
new DynamicTextAreaModel({
id: 'provenance',
name: 'dc.description.provenance',
}),
];
public constructor(protected formService: DynamicFormService,
protected translate: TranslateService,
protected notificationsService: NotificationsService,
protected authService: AuthService,
protected dsoService: CommunityDataService,
protected requestService: RequestService,
protected objectCache: ObjectCacheService) {
super(formService, translate, notificationsService, authService, requestService, objectCache);
}
}

View File

@@ -1,22 +0,0 @@
<div class="container">
<div class="row">
<ng-container *ngVar="(dsoRD$ | async)?.payload as dso">
<div class="col-12 pb-4">
<h2 id="header" class="border-bottom pb-2">{{ 'community.delete.head' | translate}}</h2>
<p class="pb-2">{{ 'community.delete.text' | translate:{ dso: dso.name } }}</p>
<div class="form-group row">
<div class="col text-right">
<button class="btn btn-outline-secondary" (click)="onCancel(dso)">
<i class="fas fa-times"></i> {{'community.delete.cancel' | translate}}
</button>
<button class="btn btn-danger mr-2" (click)="onConfirm(dso)">
<i class="fas fa-trash"></i> {{'community.delete.confirm' | translate}}
</button>
</div>
</div>
</div>
</ng-container>
</div>
</div>

View File

@@ -1,57 +0,0 @@
<div class="container-fluid">
<div class="d-inline-block float-right">
<button class=" btn btn-danger" *ngIf="!(isReinstatable() | async)"
[disabled]="!(hasChanges() | async)"
(click)="discard()"><i
class="fas fa-times"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.discard-button" | translate}}</span>
</button>
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
(click)="reinstate()"><i
class="fas fa-undo-alt"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.reinstate-button" | translate}}</span>
</button>
<button class="btn btn-primary" [disabled]="!(hasChanges() | async) || !isValid() || (initialHarvestType === harvestTypeNone && contentSource.harvestType === initialHarvestType)"
(click)="onSubmit()"><i
class="fas fa-save"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span>
</button>
</div>
<h4>{{ 'collection.edit.tabs.source.head' | translate }}</h4>
<div *ngIf="contentSource" class="form-check mb-4">
<input type="checkbox" class="form-check-input" id="externalSourceCheck" [checked]="(contentSource?.harvestType !== harvestTypeNone)" (change)="changeExternalSource()">
<label class="form-check-label" for="externalSourceCheck">{{ 'collection.edit.tabs.source.external' | translate }}</label>
</div>
<ds-loading *ngIf="!contentSource" [message]="'loading.content-source' | translate"></ds-loading>
<h4 *ngIf="contentSource && (contentSource?.harvestType !== harvestTypeNone)">{{ 'collection.edit.tabs.source.form.head' | translate }}</h4>
</div>
<ds-form *ngIf="formGroup && contentSource && (contentSource?.harvestType !== harvestTypeNone)"
[formId]="'collection-source-form-id'"
[formGroup]="formGroup"
[formModel]="formModel"
[formLayout]="formLayout"
[displaySubmit]="false"
[displayCancel]="false"
(dfChange)="onChange($event)"
(submitForm)="onSubmit()"
(cancel)="onCancel()"></ds-form>
<div class="container-fluid" *ngIf="(contentSource?.harvestType !== harvestTypeNone)">
<div class="d-inline-block float-right">
<button class=" btn btn-danger" *ngIf="!(isReinstatable() | async)"
[disabled]="!(hasChanges() | async)"
(click)="discard()"><i
class="fas fa-times"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.discard-button" | translate}}</span>
</button>
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
(click)="reinstate()"><i
class="fas fa-undo-alt"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.reinstate-button" | translate}}</span>
</button>
<button class="btn btn-primary" [disabled]="!(hasChanges() | async) || !isValid() || (initialHarvestType === harvestTypeNone && contentSource.harvestType === initialHarvestType)"
(click)="onSubmit()"><i
class="fas fa-save"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span>
</button>
</div>
</div>

View File

@@ -1,66 +0,0 @@
<div class="item-metadata">
<div class="button-row top d-flex mb-2">
<button class="mr-auto btn btn-success"
(click)="add()"><i
class="fas fa-plus"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.add-button" | translate}}</span>
</button>
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
(click)="reinstate()"><i
class="fas fa-undo-alt"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.reinstate-button" | translate}}</span>
</button>
<button class="btn btn-primary" [disabled]="!(hasChanges() | async) || !(isValid() | async)"
(click)="submit()"><i
class="fas fa-save"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span>
</button>
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
[disabled]="!(hasChanges() | async)"
(click)="discard()"><i
class="fas fa-times"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.discard-button" | translate}}</span>
</button>
</div>
<table class="table table-responsive table-striped table-bordered" *ngIf="((updates$ | async)| dsObjectValues).length > 0">
<tbody>
<tr>
<th>{{'item.edit.metadata.headers.field' | translate}}</th>
<th>{{'item.edit.metadata.headers.value' | translate}}</th>
<th class="text-center">{{'item.edit.metadata.headers.language' | translate}}</th>
<th class="text-center">{{'item.edit.metadata.headers.edit' | translate}}</th>
</tr>
<tr *ngFor="let updateValue of ((updates$ | async)| dsObjectValues); trackBy: trackUpdate"
ds-edit-in-place-field
[fieldUpdate]="updateValue || {}"
[url]="url"
[ngClass]="{
'table-warning': updateValue.changeType === 0,
'table-danger': updateValue.changeType === 2,
'table-success': updateValue.changeType === 1
}">
</tr>
</tbody>
</table>
<div *ngIf="((updates$ | async)| dsObjectValues).length == 0">
<ds-alert [content]="'item.edit.metadata.empty'" [type]="AlertTypeEnum.Info"></ds-alert>
</div>
<div class="button-row bottom">
<div class="mt-2 float-right">
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
(click)="reinstate()"><i
class="fas fa-undo-alt"></i> {{"item.edit.metadata.reinstate-button" | translate}}
</button>
<button class="btn btn-primary mr-0" [disabled]="!(hasChanges() | async)"
(click)="submit()"><i
class="fas fa-save"></i> {{"item.edit.metadata.save-button" | translate}}
</button>
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
[disabled]="!(hasChanges() | async)"
(click)="discard()"><i
class="fas fa-times"></i> {{"item.edit.metadata.discard-button" | translate}}
</button>
</div>
</div>
</div>

View File

@@ -1,48 +0,0 @@
<div class="container">
<div class="row">
<div class="col-12">
<h2>{{'item.edit.move.head' | translate: {id: (itemRD$ | async)?.payload?.handle} }}</h2>
<p>{{'item.edit.move.description' | translate}}</p>
<div class="row">
<div class="col-12">
<ds-dso-input-suggestions #f id="search-form"
[suggestions]="(collectionSearchResults | async)"
[placeholder]="'item.edit.move.search.placeholder'| translate"
[action]="getCurrentUrl()"
[name]="'item-move'"
[(ngModel)]="selectedCollectionName"
(clickSuggestion)="onClick($event)"
(typeSuggestion)="resetCollection($event)"
(findSuggestions)="findSuggestions($event)"
(click)="f.open()"
ngDefaultControl>
</ds-dso-input-suggestions>
</div>
</div>
<div class="row">
<div class="col-12">
<p>
<input type="checkbox" name="tc" [(ngModel)]="inheritPolicies" id="inheritPoliciesCheckbox">
<label for="inheritPoliciesCheckbox">{{'item.edit.move.inheritpolicies.checkbox' |
translate}}</label>
</p>
<p>
{{'item.edit.move.inheritpolicies.description' | translate}}
</p>
</div>
</div>
<button (click)="moveCollection()" class="btn btn-primary" [disabled]=!canSubmit>
<span *ngIf="!processing"> {{'item.edit.move.move' | translate}}</span>
<span *ngIf="processing"><i class='fas fa-circle-notch fa-spin'></i>
{{'item.edit.move.processing' | translate}}
</span>
</button>
<button [routerLink]="[(itemPageRoute$ | async), 'edit']"
class="btn btn-outline-secondary">
{{'item.edit.move.cancel' | translate}}
</button>
</div>
</div>
</div>

View File

@@ -1,15 +0,0 @@
<div class="col-3 float-left d-flex h-100 action-label">
<span class="justify-content-center align-self-center">
{{'item.edit.tabs.status.buttons.' + operation.operationKey + '.label' | translate}}
</span>
</div>
<div *ngIf="!operation.disabled" class="col-9 float-left action-button">
<a class="btn btn-outline-primary" [routerLink]="operation.operationUrl">
{{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}}
</a>
</div>
<div *ngIf="operation.disabled" class="col-9 float-left action-button">
<span class="btn btn-danger">
{{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}}
</span>
</div>

View File

@@ -1,26 +0,0 @@
<h5>
{{getRelationshipMessageKey() | async | translate}}
<button class="ml-2 btn btn-success" (click)="openLookup()">
<i class="fas fa-plus"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.relationships.edit.buttons.add" | translate}}</span>
</button>
</h5>
<ng-container *ngVar="updates$ | async as updates">
<ng-container *ngIf="updates">
<ng-container *ngVar="updates | dsObjectValues as updateValues">
<ds-edit-relationship *ngFor="let updateValue of updateValues; trackBy: trackUpdate"
class="relationship-row d-block alert"
[fieldUpdate]="updateValue || {}"
[url]="url"
[editItem]="item"
[ngClass]="{
'alert-success': updateValue.changeType === 1,
'alert-warning': updateValue.changeType === 0,
'alert-danger': updateValue.changeType === 2
}">
</ds-edit-relationship>
<div *ngIf="updateValues.length === 0">{{"item.edit.relationships.no-relationships" | translate}}</div>
</ng-container>
</ng-container>
<ds-loading *ngIf="!updates"></ds-loading>
</ng-container>

View File

@@ -1,344 +0,0 @@
import { Component, Input, OnInit } from '@angular/core';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { LinkService } from '../../../../core/cache/builders/link.service';
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
import { combineLatest as observableCombineLatest, Observable, of } from 'rxjs';
import {
FieldUpdate,
FieldUpdates,
RelationshipIdentifiable
} from '../../../../core/data/object-updates/object-updates.reducer';
import { RelationshipService } from '../../../../core/data/relationship.service';
import { Item } from '../../../../core/shared/item.model';
import { defaultIfEmpty, map, mergeMap, switchMap, take, startWith } from 'rxjs/operators';
import { hasValue, hasValueOperator } from '../../../../shared/empty.util';
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
import {
getAllSucceededRemoteData,
getRemoteDataPayload,
getFirstSucceededRemoteData,
} from '../../../../core/shared/operators';
import { ItemType } from '../../../../core/shared/item-relationships/item-type.model';
import { DsDynamicLookupRelationModalComponent } from '../../../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component';
import { RelationshipOptions } from '../../../../shared/form/builder/models/relationship-options.model';
import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model';
import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service';
import { SearchResult } from '../../../../shared/search/search-result.model';
import { followLink } from '../../../../shared/utils/follow-link-config.model';
import { PaginatedList } from '../../../../core/data/paginated-list.model';
import { RemoteData } from '../../../../core/data/remote-data';
@Component({
selector: 'ds-edit-relationship-list',
styleUrls: ['./edit-relationship-list.component.scss'],
templateUrl: './edit-relationship-list.component.html',
})
/**
* A component creating a list of editable relationships of a certain type
* The relationships are rendered as a list of related items
*/
export class EditRelationshipListComponent implements OnInit {
/**
* The item to display related items for
*/
@Input() item: Item;
@Input() itemType: ItemType;
/**
* The URL to the current page
* Used to fetch updates for the current item from the store
*/
@Input() url: string;
/**
* The label of the relationship-type we're rendering a list for
*/
@Input() relationshipType: RelationshipType;
private relatedEntityType$: Observable<ItemType>;
/**
* The list ID to save selected entities under
*/
listId: string;
/**
* The FieldUpdates for the relationships in question
*/
updates$: Observable<FieldUpdates>;
/**
* A reference to the lookup window
*/
modalRef: NgbModalRef;
constructor(
protected objectUpdatesService: ObjectUpdatesService,
protected linkService: LinkService,
protected relationshipService: RelationshipService,
protected modalService: NgbModal,
protected selectableListService: SelectableListService,
) {
}
/**
* Get the i18n message key for this relationship type
*/
public getRelationshipMessageKey(): Observable<string> {
return observableCombineLatest(
this.getLabel(),
this.relatedEntityType$,
).pipe(
map(([label, relatedEntityType]) => {
if (hasValue(label) && label.indexOf('is') > -1 && label.indexOf('Of') > -1) {
const relationshipLabel = `${label.substring(2, label.indexOf('Of'))}`;
if (relationshipLabel !== relatedEntityType.label) {
return `relationships.is${relationshipLabel}Of.${relatedEntityType.label}`;
} else {
return `relationships.is${relationshipLabel}Of`;
}
} else {
return label;
}
}),
);
}
/**
* Get the relevant label for this relationship type
*/
private getLabel(): Observable<string> {
return observableCombineLatest([
this.relationshipType.leftType,
this.relationshipType.rightType,
].map((itemTypeRD) => itemTypeRD.pipe(
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
))).pipe(
map((itemTypes: ItemType[]) => [
this.relationshipType.leftwardType,
this.relationshipType.rightwardType,
][itemTypes.findIndex((itemType) => itemType.id === this.itemType.id)]),
);
}
/**
* Prevent unnecessary rerendering so fields don't lose focus
*/
trackUpdate(index, update: FieldUpdate) {
return update && update.field ? update.field.uuid : undefined;
}
/**
* Open the dynamic lookup modal to search for items to add as relationships
*/
openLookup() {
this.modalRef = this.modalService.open(DsDynamicLookupRelationModalComponent, {
size: 'lg'
});
const modalComp: DsDynamicLookupRelationModalComponent = this.modalRef.componentInstance;
modalComp.repeatable = true;
modalComp.listId = this.listId;
modalComp.item = this.item;
modalComp.select = (...selectableObjects: SearchResult<Item>[]) => {
selectableObjects.forEach((searchResult) => {
const relatedItem: Item = searchResult.indexableObject;
this.getFieldUpdatesForRelatedItem(relatedItem)
.subscribe((identifiables) => {
identifiables.forEach((identifiable) =>
this.objectUpdatesService.removeSingleFieldUpdate(this.url, identifiable.uuid)
);
if (identifiables.length === 0) {
this.relationshipService.getNameVariant(this.listId, relatedItem.uuid)
.subscribe((nameVariant) => {
const update = {
uuid: this.relationshipType.id + '-' + relatedItem.uuid,
nameVariant,
type: this.relationshipType,
relatedItem,
} as RelationshipIdentifiable;
this.objectUpdatesService.saveAddFieldUpdate(this.url, update);
});
}
});
});
};
modalComp.deselect = (...selectableObjects: SearchResult<Item>[]) => {
selectableObjects.forEach((searchResult) => {
const relatedItem: Item = searchResult.indexableObject;
this.objectUpdatesService.removeSingleFieldUpdate(this.url, this.relationshipType.id + '-' + relatedItem.uuid);
this.getFieldUpdatesForRelatedItem(relatedItem)
.subscribe((identifiables) =>
identifiables.forEach((identifiable) =>
this.objectUpdatesService.saveRemoveFieldUpdate(this.url, identifiable)
)
);
});
};
this.relatedEntityType$
.pipe(take(1))
.subscribe((relatedEntityType) => {
modalComp.relationshipOptions = Object.assign(
new RelationshipOptions(), {
relationshipType: relatedEntityType.label,
// filter: this.getRelationshipMessageKey(),
searchConfiguration: relatedEntityType.label.toLowerCase(),
nameVariants: true,
}
);
});
this.selectableListService.deselectAll(this.listId);
this.updates$.pipe(
switchMap((updates) =>
Object.values(updates).length > 0 ?
observableCombineLatest(
Object.values(updates)
.filter((update) => update.changeType !== FieldChangeType.REMOVE)
.map((update) => {
const field = update.field as RelationshipIdentifiable;
if (field.relationship) {
return this.getRelatedItem(field.relationship);
} else {
return of(field.relatedItem);
}
})
) : of([])
),
take(1),
map((items) => items.map((item) => {
const searchResult = new ItemSearchResult();
searchResult.indexableObject = item;
searchResult.hitHighlights = {};
return searchResult;
})),
).subscribe((items) => {
this.selectableListService.select(this.listId, items);
});
}
/**
* Get the existing field updates regarding a relationship with a given item
* @param relatedItem The item for which to get the existing field updates
*/
private getFieldUpdatesForRelatedItem(relatedItem: Item): Observable<RelationshipIdentifiable[]> {
return this.updates$.pipe(
take(1),
map((updates) => Object.values(updates)
.map((update) => update.field as RelationshipIdentifiable)
.filter((field) => field.relationship)
),
mergeMap((identifiables) =>
observableCombineLatest(
identifiables.map((identifiable) => this.getRelatedItem(identifiable.relationship))
).pipe(
defaultIfEmpty([]),
map((relatedItems) =>
identifiables.filter((identifiable, index) => relatedItems[index].uuid === relatedItem.uuid)
),
)
),
);
}
/**
* Get the related item for a given relationship
* @param relationship The relationship for which to get the related item
*/
private getRelatedItem(relationship: Relationship): Observable<Item> {
return this.relationshipService.isLeftItem(relationship, this.item).pipe(
switchMap((isLeftItem) => isLeftItem ? relationship.rightItem : relationship.leftItem),
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
) as Observable<Item>;
}
ngOnInit(): void {
this.relatedEntityType$ =
observableCombineLatest([
this.relationshipType.leftType,
this.relationshipType.rightType,
].map((type) => type.pipe(
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
))).pipe(
map((relatedTypes: ItemType[]) => relatedTypes.find((relatedType) => relatedType.uuid !== this.itemType.uuid)),
hasValueOperator()
);
this.relatedEntityType$.pipe(
take(1)
).subscribe(
(relatedEntityType) => this.listId = `edit-relationship-${this.itemType.id}-${relatedEntityType.id}`
);
this.updates$ = this.getItemRelationships().pipe(
switchMap((relationships) =>
observableCombineLatest(
relationships.map((relationship) => this.relationshipService.isLeftItem(relationship, this.item))
).pipe(
defaultIfEmpty([]),
map((isLeftItemArray) => isLeftItemArray.map((isLeftItem, index) => {
const relationship = relationships[index];
const nameVariant = isLeftItem ? relationship.rightwardValue : relationship.leftwardValue;
return {
uuid: relationship.id,
type: this.relationshipType,
relationship,
nameVariant,
} as RelationshipIdentifiable;
})),
)),
switchMap((initialFields) => this.objectUpdatesService.getFieldUpdates(this.url, initialFields).pipe(
map((fieldUpdates) => {
const fieldUpdatesFiltered: FieldUpdates = {};
Object.keys(fieldUpdates).forEach((uuid) => {
if (hasValue(fieldUpdates[uuid])) {
const field = fieldUpdates[uuid].field;
if ((field as RelationshipIdentifiable).type.id === this.relationshipType.id) {
fieldUpdatesFiltered[uuid] = fieldUpdates[uuid];
}
}
});
return fieldUpdatesFiltered;
}),
)),
startWith({}),
);
}
private getItemRelationships() {
this.linkService.resolveLink(this.item,
followLink('relationships', undefined, true, true, true,
followLink('relationshipType'),
followLink('leftItem'),
followLink('rightItem'),
));
return this.item.relationships.pipe(
getAllSucceededRemoteData(),
map((relationships: RemoteData<PaginatedList<Relationship>>) => relationships.payload.page.filter((relationship: Relationship) => hasValue(relationship))),
switchMap((itemRelationships: Relationship[]) =>
observableCombineLatest(
itemRelationships
.map((relationship) => relationship.relationshipType.pipe(
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
))
).pipe(
defaultIfEmpty([]),
map((relationshipTypes) => itemRelationships.filter(
(relationship, index) => relationshipTypes[index].id === this.relationshipType.id)
),
)
),
);
}
}

View File

@@ -1,6 +0,0 @@
<div class="mt-4">
<ds-alert [content]="'item.edit.tabs.versionhistory.under-construction'" [type]="AlertTypeEnum.Warning"></ds-alert>
</div>
<div class="mt-2" *ngVar="(itemRD$ | async)?.payload as item">
<ds-item-versions *ngIf="item" [item]="item" [displayWhenEmpty]="true" [displayTitle]="false"></ds-item-versions>
</div>

View File

@@ -1,7 +0,0 @@
<ds-metadata-field-wrapper *ngIf="(this.collectionsRD$ | async)?.hasSucceeded" [label]="label | translate">
<div class="collections">
<a *ngFor="let collection of (this.collectionsRD$ | async)?.payload?.page; let last=last;" [routerLink]="['/collections', collection.id]">
<span>{{collection?.name}}</span><span *ngIf="!last" [innerHTML]="separator"></span>
</a>
</div>
</ds-metadata-field-wrapper>

View File

@@ -1,91 +0,0 @@
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { TranslateModule } from '@ngx-translate/core';
import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
import { CollectionDataService } from '../../../core/data/collection-data.service';
import { Collection } from '../../../core/shared/collection.model';
import { Item } from '../../../core/shared/item.model';
import { getMockRemoteDataBuildService } from '../../../shared/mocks/remote-data-build.service.mock';
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
import { CollectionsComponent } from './collections.component';
let collectionsComponent: CollectionsComponent;
let fixture: ComponentFixture<CollectionsComponent>;
let collectionDataServiceStub;
const mockCollection1: Collection = Object.assign(new Collection(), {
metadata: {
'dc.description.abstract': [
{
language: 'en_US',
value: 'Short description'
}
]
},
_links: {
self: { href: 'collection-selflink' }
}
});
const succeededMockItem: Item = Object.assign(new Item(), {owningCollection: createSuccessfulRemoteDataObject$(mockCollection1)});
const failedMockItem: Item = Object.assign(new Item(), {owningCollection: createFailedRemoteDataObject$('error', 500)});
describe('CollectionsComponent', () => {
collectionDataServiceStub = {
findOwningCollectionFor(item: Item) {
if (item === succeededMockItem) {
return createSuccessfulRemoteDataObject$(mockCollection1);
} else {
return createFailedRemoteDataObject$('error', 500);
}
}
};
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [ CollectionsComponent ],
providers: [
{ provide: RemoteDataBuildService, useValue: getMockRemoteDataBuildService()},
{ provide: CollectionDataService, useValue: collectionDataServiceStub },
],
schemas: [ NO_ERRORS_SCHEMA ]
}).overrideComponent(CollectionsComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(CollectionsComponent);
collectionsComponent = fixture.componentInstance;
collectionsComponent.label = 'test.test';
collectionsComponent.separator = '<br/>';
}));
describe('When the requested item request has succeeded', () => {
beforeEach(() => {
collectionsComponent.item = succeededMockItem;
fixture.detectChanges();
});
it('should show the collection', () => {
const collectionField = fixture.debugElement.query(By.css('ds-metadata-field-wrapper div.collections'));
expect(collectionField).not.toBeNull();
});
});
describe('When the requested item request has failed', () => {
beforeEach(() => {
collectionsComponent.item = failedMockItem;
fixture.detectChanges();
});
it('should not show the collection', () => {
const collectionField = fixture.debugElement.query(By.css('ds-metadata-field-wrapper div.collections'));
expect(collectionField).toBeNull();
});
});
});

View File

@@ -1,68 +0,0 @@
import { Component, Input, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { CollectionDataService } from '../../../core/data/collection-data.service';
import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model';
import { RemoteData } from '../../../core/data/remote-data';
import { Collection } from '../../../core/shared/collection.model';
import { Item } from '../../../core/shared/item.model';
import { PageInfo } from '../../../core/shared/page-info.model';
import { hasValue } from '../../../shared/empty.util';
/**
* This component renders the parent collections section of the item
* inside a 'ds-metadata-field-wrapper' component.
*/
@Component({
selector: 'ds-item-page-collections',
templateUrl: './collections.component.html'
})
export class CollectionsComponent implements OnInit {
@Input() item: Item;
label = 'item.page.collections';
separator = '<br/>';
collectionsRD$: Observable<RemoteData<PaginatedList<Collection>>>;
constructor(private cds: CollectionDataService) {
}
ngOnInit(): void {
// this.collections = this.item.parents.payload;
// TODO: this should use parents, but the collections
// for an Item aren't returned by the REST API yet,
// only the owning collection
this.collectionsRD$ = this.cds.findOwningCollectionFor(this.item).pipe(
map((rd: RemoteData<Collection>) => {
if (hasValue(rd.payload)) {
return new RemoteData(
rd.timeCompleted,
rd.msToLive,
rd.lastUpdated,
rd.state,
rd.errorMessage,
buildPaginatedList({
elementsPerPage: 10,
totalPages: 1,
currentPage: 1,
totalElements: 1,
_links: {
self: rd.payload._links.self
}
} as PageInfo, [rd.payload]),
rd.statusCode
);
} else {
return rd as any;
}
})
);
}
}

View File

@@ -1,95 +0,0 @@
import { Component } from '@angular/core';
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { MetadataFieldWrapperComponent } from './metadata-field-wrapper.component';
/* tslint:disable:max-classes-per-file */
@Component({
selector: 'ds-component-without-content',
template: '<ds-metadata-field-wrapper [label]="\'test label\'">\n' +
'</ds-metadata-field-wrapper>'
})
class NoContentComponent {}
@Component({
selector: 'ds-component-with-empty-spans',
template: '<ds-metadata-field-wrapper [label]="\'test label\'">\n' +
' <span></span>\n' +
' <span></span>\n' +
'</ds-metadata-field-wrapper>'
})
class SpanContentComponent {}
@Component({
selector: 'ds-component-with-text',
template: '<ds-metadata-field-wrapper [label]="\'test label\'">\n' +
' <span>The quick brown fox jumps over the lazy dog</span>\n' +
'</ds-metadata-field-wrapper>'
})
class TextContentComponent {}
@Component({
selector: 'ds-component-with-image',
template: '<ds-metadata-field-wrapper [label]="\'test label\'">\n' +
' <img src="https://some/image.png" alt="an alt text">\n' +
'</ds-metadata-field-wrapper>'
})
class ImgContentComponent {}
/* tslint:enable:max-classes-per-file */
describe('MetadataFieldWrapperComponent', () => {
let component: MetadataFieldWrapperComponent;
let fixture: ComponentFixture<MetadataFieldWrapperComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [MetadataFieldWrapperComponent, NoContentComponent, SpanContentComponent, TextContentComponent, ImgContentComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MetadataFieldWrapperComponent);
component = fixture.componentInstance;
});
const wrapperSelector = '.simple-view-element';
it('should create', () => {
expect(component).toBeDefined();
});
it('should not show the component when there is no content', () => {
const parentFixture = TestBed.createComponent(NoContentComponent);
parentFixture.detectChanges();
const parentNative = parentFixture.nativeElement;
const nativeWrapper = parentNative.querySelector(wrapperSelector);
expect(nativeWrapper.classList.contains('d-none')).toBe(true);
});
it('should not show the component when there is DOM content but not text or an image', () => {
const parentFixture = TestBed.createComponent(SpanContentComponent);
parentFixture.detectChanges();
const parentNative = parentFixture.nativeElement;
const nativeWrapper = parentNative.querySelector(wrapperSelector);
expect(nativeWrapper.classList.contains('d-none')).toBe(true);
});
it('should show the component when there is text content', () => {
const parentFixture = TestBed.createComponent(TextContentComponent);
parentFixture.detectChanges();
const parentNative = parentFixture.nativeElement;
const nativeWrapper = parentNative.querySelector(wrapperSelector);
parentFixture.detectChanges();
expect(nativeWrapper.classList.contains('d-none')).toBe(false);
});
it('should show the component when there is img content', () => {
const parentFixture = TestBed.createComponent(ImgContentComponent);
parentFixture.detectChanges();
const parentNative = parentFixture.nativeElement;
const nativeWrapper = parentNative.querySelector(wrapperSelector);
parentFixture.detectChanges();
expect(nativeWrapper.classList.contains('d-none')).toBe(false);
});
});

View File

@@ -1,39 +0,0 @@
import { Component, Input, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { environment } from '../../../../../environments/environment';
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
import { Bitstream } from '../../../../core/shared/bitstream.model';
import { Item } from '../../../../core/shared/item.model';
import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators';
import { getItemPageRoute } from '../../../item-page-routing-paths';
@Component({
selector: 'ds-item',
template: ''
})
/**
* A generic component for displaying metadata and relations of an item
*/
export class ItemComponent implements OnInit {
@Input() object: Item;
/**
* Route to the item page
*/
itemPageRoute: string;
mediaViewer = environment.mediaViewer;
constructor(protected bitstreamDataService: BitstreamDataService) {
}
ngOnInit(): void {
this.itemPageRoute = getItemPageRoute(this.object);
}
// TODO refactor to return RemoteData, and thumbnail template to deal with loading
getThumbnail(): Observable<Bitstream> {
return this.bitstreamDataService.getThumbnailFor(this.object).pipe(
getFirstSucceededRemoteDataPayload()
);
}
}

View File

@@ -1,112 +0,0 @@
import { HttpClient } from '@angular/common/http';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Store } from '@ngrx/store';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { Observable } from 'rxjs/internal/Observable';
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
import { CommunityDataService } from '../../../../core/data/community-data.service';
import { DefaultChangeAnalyzer } from '../../../../core/data/default-change-analyzer.service';
import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.service';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { buildPaginatedList } from '../../../../core/data/paginated-list.model';
import { RelationshipService } from '../../../../core/data/relationship.service';
import { RemoteData } from '../../../../core/data/remote-data';
import { Bitstream } from '../../../../core/shared/bitstream.model';
import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service';
import { Item } from '../../../../core/shared/item.model';
import { MetadataMap } from '../../../../core/shared/metadata.models';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { UUIDService } from '../../../../core/shared/uuid.service';
import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
import { createRelationshipsObservable } from '../shared/item.component.spec';
import { UntypedItemComponent } from './untyped-item.component';
const mockItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])),
metadata: new MetadataMap(),
relationships: createRelationshipsObservable()
});
describe('UntypedItemComponent', () => {
let comp: UntypedItemComponent;
let fixture: ComponentFixture<UntypedItemComponent>;
beforeEach(waitForAsync(() => {
const mockBitstreamDataService = {
getThumbnailFor(item: Item): Observable<RemoteData<Bitstream>> {
return createSuccessfulRemoteDataObject$(new Bitstream());
}
};
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderMock
}
})],
declarations: [UntypedItemComponent, GenericItemPageFieldComponent, TruncatePipe],
providers: [
{ provide: ItemDataService, useValue: {} },
{ provide: TruncatableService, useValue: {} },
{ provide: RelationshipService, useValue: {} },
{ provide: ObjectCacheService, useValue: {} },
{ provide: UUIDService, useValue: {} },
{ provide: Store, useValue: {} },
{ provide: RemoteDataBuildService, useValue: {} },
{ provide: CommunityDataService, useValue: {} },
{ provide: HALEndpointService, useValue: {} },
{ provide: NotificationsService, useValue: {} },
{ provide: HttpClient, useValue: {} },
{ provide: DSOChangeAnalyzer, useValue: {} },
{ provide: DefaultChangeAnalyzer, useValue: {} },
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(UntypedItemComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(UntypedItemComponent);
comp = fixture.componentInstance;
comp.object = mockItem;
fixture.detectChanges();
}));
it('should contain a component to display the date', () => {
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-date-field'));
expect(fields.length).toBeGreaterThanOrEqual(1);
});
it('should contain a component to display the author', () => {
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-author-field'));
expect(fields.length).toBeGreaterThanOrEqual(1);
});
it('should contain a component to display the abstract', () => {
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-abstract-field'));
expect(fields.length).toBeGreaterThanOrEqual(1);
});
it('should contain a component to display the uri', () => {
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-uri-field'));
expect(fields.length).toBeGreaterThanOrEqual(1);
});
it('should contain a component to display the collections', () => {
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-collections'));
expect(fields.length).toBeGreaterThanOrEqual(1);
});
});

View File

@@ -1 +0,0 @@
@import '../+login-page/login-page.component.scss';

View File

@@ -1,21 +0,0 @@
<div class="parent mb-3">
<div class="upload">
<ds-uploader *ngIf="uploadFilesOptions.url !== ''"
[uploadFilesOptions]="uploadFilesOptions"
(onCompleteItem)="onCompleteItem($event)"
(onUploadError)="onUploadError($event)"
(onFileSelected)="afterFileLoaded($event)"></ds-uploader>
</div>
<div class="add">
<button class="btn btn-lg btn-primary mt-1 ml-2" (click)="openDialog()" attr.aria-label="'mydspace.new-submission' | translate" title="{{'mydspace.new-submission' | translate}}">
<i class="fa fa-plus" aria-hidden="true"></i>
</button>
</div>
<div class="add">
<a class="btn btn-lg btn-outline-primary mt-1 ml-2" [routerLink]="['/import-external']" role="button" attr.aria-label="{{'mydspace.new-submission-external' | translate}}" title="{{'mydspace.new-submission-external' | translate}}">
<i class="fa fa-file-import" aria-hidden="true"></i>
</a>
</div>
</div>

View File

@@ -1 +0,0 @@
@import '../+search-page/search.component.scss';

View File

@@ -3,6 +3,10 @@ import { getAccessControlModuleRoute } from '../app-routing-paths';
export const GROUP_EDIT_PATH = 'groups';
export function getGroupsRoute() {
return new URLCombiner(getAccessControlModuleRoute(), GROUP_EDIT_PATH).toString();
}
export function getGroupEditRoute(id: string) {
return new URLCombiner(getAccessControlModuleRoute(), GROUP_EDIT_PATH, id).toString();
}

View File

@@ -5,6 +5,9 @@ import { GroupFormComponent } from './group-registry/group-form/group-form.compo
import { GroupsRegistryComponent } from './group-registry/groups-registry.component';
import { GROUP_EDIT_PATH } from './access-control-routing-paths';
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
import { GroupPageGuard } from './group-registry/group-page.guard';
import { GroupAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/group-administrator.guard';
import { SiteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
@NgModule({
imports: [
@@ -15,7 +18,8 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso
resolve: {
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'admin.access-control.epeople.title', breadcrumbKey: 'admin.access-control.epeople' }
data: { title: 'admin.access-control.epeople.title', breadcrumbKey: 'admin.access-control.epeople' },
canActivate: [SiteAdministratorGuard]
},
{
path: GROUP_EDIT_PATH,
@@ -23,7 +27,8 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso
resolve: {
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'admin.access-control.groups.title', breadcrumbKey: 'admin.access-control.groups' }
data: { title: 'admin.access-control.groups.title', breadcrumbKey: 'admin.access-control.groups' },
canActivate: [GroupAdministratorGuard]
},
{
path: `${GROUP_EDIT_PATH}/newGroup`,
@@ -31,7 +36,8 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso
resolve: {
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'admin.access-control.groups.title.addGroup', breadcrumbKey: 'admin.access-control.groups.addGroup' }
data: { title: 'admin.access-control.groups.title.addGroup', breadcrumbKey: 'admin.access-control.groups.addGroup' },
canActivate: [GroupAdministratorGuard]
},
{
path: `${GROUP_EDIT_PATH}/:groupId`,
@@ -39,7 +45,8 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso
resolve: {
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'admin.access-control.groups.title.singleGroup', breadcrumbKey: 'admin.access-control.groups.singleGroup' }
data: { title: 'admin.access-control.groups.title.singleGroup', breadcrumbKey: 'admin.access-control.groups.singleGroup' },
canActivate: [GroupPageGuard]
}
])
]

View File

@@ -52,15 +52,17 @@
<table id="groups" class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th scope="col">{{messagePrefix + '.table.id' | translate}}</th>
<th scope="col">{{messagePrefix + '.table.name' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.collectionOrCommunity' | translate}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let group of (groups | async)?.payload?.page">
<td>{{group.id}}</td>
<td><a (click)="groupsDataService.startEditingNewGroup(group)"
<td class="align-middle">{{group.id}}</td>
<td class="align-middle"><a (click)="groupsDataService.startEditingNewGroup(group)"
[routerLink]="[groupsDataService.getGroupEditPageRouterLink(group)]">{{group.name}}</a></td>
<td class="align-middle">{{(group.object | async)?.payload?.name}}</td>
</tr>
</tbody>
</table>

View File

@@ -28,6 +28,9 @@ import { createPaginatedList } from '../../../shared/testing/utils.test';
import { RequestService } from '../../../core/data/request.service';
import { PaginationService } from '../../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
import { FormArray, FormControl, FormGroup,Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS } from '@angular/forms';
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
describe('EPersonFormComponent', () => {
let component: EPersonFormComponent;
@@ -99,12 +102,78 @@ describe('EPersonFormComponent', () => {
}
});
return createSuccessfulRemoteDataObject$(ePerson);
},
getEPersonByEmail(email): Observable<RemoteData<EPerson>> {
return createSuccessfulRemoteDataObject$(null);
}
};
builderService = getMockFormBuilderService();
builderService = Object.assign(getMockFormBuilderService(),{
createFormGroup(formModel, options = null) {
const controls = {};
formModel.forEach( model => {
model.parent = parent;
const controlModel = model;
const controlState = { value: controlModel.value, disabled: controlModel.disabled };
const controlOptions = this.createAbstractControlOptions(controlModel.validators, controlModel.asyncValidators, controlModel.updateOn);
controls[model.id] = new FormControl(controlState, controlOptions);
});
return new FormGroup(controls, options);
},
createAbstractControlOptions(validatorsConfig = null, asyncValidatorsConfig = null, updateOn = null) {
return {
validators: validatorsConfig !== null ? this.getValidators(validatorsConfig) : null,
};
},
getValidators(validatorsConfig) {
return this.getValidatorFns(validatorsConfig);
},
getValidatorFns(validatorsConfig, validatorsToken = this._NG_VALIDATORS) {
let validatorFns = [];
if (this.isObject(validatorsConfig)) {
validatorFns = Object.keys(validatorsConfig).map(validatorConfigKey => {
const validatorConfigValue = validatorsConfig[validatorConfigKey];
if (this.isValidatorDescriptor(validatorConfigValue)) {
const descriptor = validatorConfigValue;
return this.getValidatorFn(descriptor.name, descriptor.args, validatorsToken);
}
return this.getValidatorFn(validatorConfigKey, validatorConfigValue, validatorsToken);
});
}
return validatorFns;
},
getValidatorFn(validatorName, validatorArgs = null, validatorsToken = this._NG_VALIDATORS) {
let validatorFn;
if (Validators.hasOwnProperty(validatorName)) { // Built-in Angular Validators
validatorFn = Validators[validatorName];
} else { // Custom Validators
if (this._DYNAMIC_VALIDATORS && this._DYNAMIC_VALIDATORS.has(validatorName)) {
validatorFn = this._DYNAMIC_VALIDATORS.get(validatorName);
} else if (validatorsToken) {
validatorFn = validatorsToken.find(validator => validator.name === validatorName);
}
}
if (validatorFn === undefined) { // throw when no validator could be resolved
throw new Error(`validator '${validatorName}' is not provided via NG_VALIDATORS, NG_ASYNC_VALIDATORS or DYNAMIC_FORM_VALIDATORS`);
}
if (validatorArgs !== null) {
return validatorFn(validatorArgs);
}
return validatorFn;
},
isValidatorDescriptor(value) {
if (this.isObject(value)) {
return value.hasOwnProperty('name') && value.hasOwnProperty('args');
}
return false;
},
isObject(value) {
return typeof value === 'object' && value !== null;
}
});
authService = new AuthServiceStub();
authorizationService = jasmine.createSpyObj('authorizationService', {
isAuthorized: observableOf(true)
isAuthorized: observableOf(true),
});
groupsDataService = jasmine.createSpyObj('groupsDataService', {
findAllByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])),
@@ -146,6 +215,131 @@ describe('EPersonFormComponent', () => {
expect(component).toBeDefined();
});
describe('check form validation', () => {
let firstName;
let lastName;
let email;
let canLogIn;
let requireCertificate;
let expected;
beforeEach(() => {
firstName = 'testName';
lastName = 'testLastName';
email = 'testEmail@test.com';
canLogIn = false;
requireCertificate = false;
expected = Object.assign(new EPerson(), {
metadata: {
'eperson.firstname': [
{
value: firstName
}
],
'eperson.lastname': [
{
value: lastName
},
],
},
email: email,
canLogIn: canLogIn,
requireCertificate: requireCertificate,
});
spyOn(component.submitForm, 'emit');
component.canLogIn.value = canLogIn;
component.requireCertificate.value = requireCertificate;
fixture.detectChanges();
component.initialisePage();
fixture.detectChanges();
});
describe('firstName, lastName and email should be required', () => {
it('form should be invalid because the firstName is required', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.firstName.valid).toBeFalse();
expect(component.formGroup.controls.firstName.errors.required).toBeTrue();
});
}));
it('form should be invalid because the lastName is required', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.lastName.valid).toBeFalse();
expect(component.formGroup.controls.lastName.errors.required).toBeTrue();
});
}));
it('form should be invalid because the email is required', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.email.valid).toBeFalse();
expect(component.formGroup.controls.email.errors.required).toBeTrue();
});
}));
});
describe('after inserting information firstName,lastName and email not required', () => {
beforeEach(() => {
component.formGroup.controls.firstName.setValue('test');
component.formGroup.controls.lastName.setValue('test');
component.formGroup.controls.email.setValue('test@test.com');
fixture.detectChanges();
});
it('firstName should be valid because the firstName is set', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.firstName.valid).toBeTrue();
expect(component.formGroup.controls.firstName.errors).toBeNull();
});
}));
it('lastName should be valid because the lastName is set', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.lastName.valid).toBeTrue();
expect(component.formGroup.controls.lastName.errors).toBeNull();
});
}));
it('email should be valid because the email is set', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.email.valid).toBeTrue();
expect(component.formGroup.controls.email.errors).toBeNull();
});
}));
});
describe('after inserting email wrong should show pattern validation error', () => {
beforeEach(() => {
component.formGroup.controls.email.setValue('test@test');
fixture.detectChanges();
});
it('email should not be valid because the email pattern', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.email.valid).toBeFalse();
expect(component.formGroup.controls.email.errors.pattern).toBeTruthy();
});
}));
});
describe('after already utilized email', () => {
beforeEach(() => {
const ePersonServiceWithEperson = Object.assign(ePersonDataServiceStub,{
getEPersonByEmail(): Observable<RemoteData<EPerson>> {
return createSuccessfulRemoteDataObject$(EPersonMock);
}
});
component.formGroup.controls.email.setValue('test@test.com');
component.formGroup.controls.email.setAsyncValidators(ValidateEmailNotTaken.createValidator(ePersonServiceWithEperson));
fixture.detectChanges();
});
it('email should not be valid because email is already taken', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.email.valid).toBeFalse();
expect(component.formGroup.controls.email.errors.emailTaken).toBeTruthy();
});
}));
});
});
describe('when submitting the form', () => {
let firstName;
let lastName;

View File

@@ -1,4 +1,4 @@
import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
import { ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
import { FormGroup } from '@angular/forms';
import {
DynamicCheckboxModel,
@@ -8,7 +8,7 @@ import {
} from '@ng-dynamic-forms/core';
import { TranslateService } from '@ngx-translate/core';
import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
import { switchMap, take } from 'rxjs/operators';
import { debounceTime, switchMap, take } from 'rxjs/operators';
import { PaginatedList } from '../../../core/data/paginated-list.model';
import { RemoteData } from '../../../core/data/remote-data';
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
@@ -32,10 +32,12 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { RequestService } from '../../../core/data/request.service';
import { NoContent } from '../../../core/shared/NoContent.model';
import { PaginationService } from '../../../core/pagination/pagination.service';
import { followLink } from '../../../shared/utils/follow-link-config.model';
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
@Component({
selector: 'ds-eperson-form',
templateUrl: './eperson-form.component.html'
templateUrl: './eperson-form.component.html',
})
/**
* A form used for creating and editing EPeople
@@ -160,7 +162,13 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
*/
isImpersonated = false;
constructor(public epersonService: EPersonDataService,
/**
* Subscription to email field value change
*/
emailValueChangeSubscribe: Subscription;
constructor(protected changeDetectorRef: ChangeDetectorRef,
public epersonService: EPersonDataService,
public groupsDataService: GroupDataService,
private formBuilderService: FormBuilderService,
private translateService: TranslateService,
@@ -186,6 +194,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
* This method will initialise the page
*/
initialisePage() {
observableCombineLatest(
this.translateService.get(`${this.messagePrefix}.firstName`),
this.translateService.get(`${this.messagePrefix}.lastName`),
@@ -218,9 +227,13 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
name: 'email',
validators: {
required: null,
pattern: '^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$'
pattern: '^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$',
},
required: true,
errorMessages: {
emailTaken: 'error.validation.emailTaken',
pattern: 'error.validation.NotValidEmail'
},
hint: emailHint
});
this.canLogIn = new DynamicCheckboxModel(
@@ -259,6 +272,13 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
canLogIn: eperson != null ? eperson.canLogIn : true,
requireCertificate: eperson != null ? eperson.requireCertificate : false
});
if (eperson === null && !!this.formGroup.controls.email) {
this.formGroup.controls.email.setAsyncValidators(ValidateEmailNotTaken.createValidator(this.epersonService));
this.emailValueChangeSubscribe = this.email.valueChanges.pipe(debounceTime(300)).subscribe(() => {
this.changeDetectorRef.detectChanges();
});
}
}));
const activeEPerson$ = this.epersonService.getActiveEPerson();
@@ -272,14 +292,20 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
}),
switchMap(([eperson, findListOptions]) => {
if (eperson != null) {
return this.groupsDataService.findAllByHref(eperson._links.groups.href, findListOptions);
return this.groupsDataService.findAllByHref(eperson._links.groups.href, findListOptions, true, true, followLink('object'));
}
return observableOf(undefined);
})
);
this.canImpersonate$ = activeEPerson$.pipe(
switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, hasValue(eperson) ? eperson.self : undefined))
switchMap((eperson) => {
if (hasValue(eperson)) {
return this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, eperson.self);
} else {
return observableOf(false);
}
})
);
this.canDelete$ = activeEPerson$.pipe(
switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined))
@@ -342,10 +368,10 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
getFirstCompletedRemoteData()
).subscribe((rd: RemoteData<EPerson>) => {
if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', {name: ePersonToCreate.name}));
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', { name: ePersonToCreate.name }));
this.submitForm.emit(ePersonToCreate);
} else {
this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.created.failure', {name: ePersonToCreate.name}));
this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.created.failure', { name: ePersonToCreate.name }));
this.cancelForm.emit();
}
});
@@ -381,10 +407,10 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
const response = this.epersonService.updateEPerson(editedEperson);
response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData<EPerson>) => {
if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', {name: editedEperson.name}));
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', { name: editedEperson.name }));
this.submitForm.emit(editedEperson);
} else {
this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.edited.failure', {name: editedEperson.name}));
this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.edited.failure', { name: editedEperson.name }));
this.cancelForm.emit();
}
});
@@ -394,28 +420,6 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
}
}
/**
* Checks for the given ePerson if there is already an ePerson in the system with that email
* and shows notification if this is the case
* @param ePerson ePerson values to check
* @param notificationSection whether in create or edit
*/
private showNotificationIfEmailInUse(ePerson: EPerson, notificationSection: string) {
// Relevant message for email in use
this.subs.push(this.epersonService.searchByScope('email', ePerson.email, {
currentPage: 1,
elementsPerPage: 0
}).pipe(getFirstSucceededRemoteData(), getRemoteDataPayload())
.subscribe((list: PaginatedList<EPerson>) => {
if (list.totalElements > 0) {
this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.' + notificationSection + '.failure.emailInUse', {
name: ePerson.name,
email: ePerson.email
}));
}
}));
}
/**
* Event triggered when the user changes page
* @param event
@@ -427,15 +431,6 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
});
}
/**
* Update the list of groups by fetching it from the rest api or cache
*/
private updateGroups(options) {
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
this.groups = this.groupsDataService.findAllByHref(eperson._links.groups.href, options);
}));
}
/**
* Start impersonating the EPerson
*/
@@ -470,7 +465,8 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
}
this.cancelForm.emit();
});
}}
}
}
});
});
}
@@ -490,8 +486,10 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
this.onCancel();
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
this.paginationService.clearPagination(this.config.id);
if (hasValue(this.emailValueChangeSubscribe)) {
this.emailValueChangeSubscribe.unsubscribe();
}
}
/**
* This method will ensure that the page gets reset and that the cache is cleared
@@ -502,4 +500,35 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
});
this.initialisePage();
}
/**
* Checks for the given ePerson if there is already an ePerson in the system with that email
* and shows notification if this is the case
* @param ePerson ePerson values to check
* @param notificationSection whether in create or edit
*/
private showNotificationIfEmailInUse(ePerson: EPerson, notificationSection: string) {
// Relevant message for email in use
this.subs.push(this.epersonService.searchByScope('email', ePerson.email, {
currentPage: 1,
elementsPerPage: 0
}).pipe(getFirstSucceededRemoteData(), getRemoteDataPayload())
.subscribe((list: PaginatedList<EPerson>) => {
if (list.totalElements > 0) {
this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.' + notificationSection + '.failure.emailInUse', {
name: ePerson.name,
email: ePerson.email
}));
}
}));
}
/**
* Update the list of groups by fetching it from the rest api or cache
*/
private updateGroups(options) {
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
this.groups = this.groupsDataService.findAllByHref(eperson._links.groups.href, options);
}));
}
}

View File

@@ -0,0 +1,25 @@
import { AbstractControl, ValidationErrors } from '@angular/forms';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
import { getFirstSucceededRemoteData, } from '../../../../core/shared/operators';
export class ValidateEmailNotTaken {
/**
* This method will create the validator with the ePersonDataService requested from component
* @param ePersonDataService the service with DI in the component that this validator is being utilized.
*/
static createValidator(ePersonDataService: EPersonDataService) {
return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
return ePersonDataService.getEPersonByEmail(control.value)
.pipe(
getFirstSucceededRemoteData(),
map(res => {
return !!res.payload ? { emailTaken: true } : null;
})
);
};
}
}

View File

@@ -210,4 +210,11 @@ describe('GroupFormComponent', () => {
});
});
describe('ngOnDestroy', () => {
it('does NOT call router.navigate', () => {
component.ngOnDestroy();
expect(router.navigate).toHaveBeenCalledTimes(0);
});
});
});

View File

@@ -14,11 +14,11 @@ import {
combineLatest as observableCombineLatest,
Observable,
of as observableOf,
Subscription
Subscription,
} from 'rxjs';
import { catchError, map, switchMap, take } from 'rxjs/operators';
import { getCollectionEditRolesRoute } from '../../../+collection-page/collection-page-routing-paths';
import { getCommunityEditRolesRoute } from '../../../+community-page/community-page-routing-paths';
import { catchError, map, switchMap, take, filter } from 'rxjs/operators';
import { getCollectionEditRolesRoute } from '../../../collection-page/collection-page-routing-paths';
import { getCommunityEditRolesRoute } from '../../../community-page/community-page-routing-paths';
import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
@@ -34,7 +34,8 @@ import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import {
getRemoteDataPayload,
getFirstSucceededRemoteData,
getFirstCompletedRemoteData
getFirstCompletedRemoteData,
getFirstSucceededRemoteDataPayload
} from '../../../core/shared/operators';
import { AlertType } from '../../../shared/alert/aletr-type';
import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component';
@@ -65,6 +66,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
* Dynamic models for the inputs of form
*/
groupName: DynamicInputModel;
groupCommunity: DynamicInputModel;
groupDescription: DynamicTextAreaModel;
/**
@@ -160,8 +162,9 @@ export class GroupFormComponent implements OnInit, OnDestroy {
);
observableCombineLatest(
this.translateService.get(`${this.messagePrefix}.groupName`),
this.translateService.get(`${this.messagePrefix}.groupCommunity`),
this.translateService.get(`${this.messagePrefix}.groupDescription`)
).subscribe(([groupName, groupDescription]) => {
).subscribe(([groupName, groupCommunity, groupDescription]) => {
this.groupName = new DynamicInputModel({
id: 'groupName',
label: groupName,
@@ -171,6 +174,13 @@ export class GroupFormComponent implements OnInit, OnDestroy {
},
required: true,
});
this.groupCommunity = new DynamicInputModel({
id: 'groupCommunity',
label: groupCommunity,
name: 'groupCommunity',
required: false,
readOnly: true,
});
this.groupDescription = new DynamicTextAreaModel({
id: 'groupDescription',
label: groupDescription,
@@ -185,17 +195,36 @@ export class GroupFormComponent implements OnInit, OnDestroy {
this.subs.push(
observableCombineLatest(
this.groupDataService.getActiveGroup(),
this.canEdit$
).subscribe(([activeGroup, canEdit]) => {
this.canEdit$,
this.groupDataService.getActiveGroup()
.pipe(filter((activeGroup) => hasValue(activeGroup)),switchMap((activeGroup) => this.getLinkedDSO(activeGroup).pipe(getFirstSucceededRemoteDataPayload())))
).subscribe(([activeGroup, canEdit, linkedObject]) => {
if (activeGroup != null) {
this.groupBeingEdited = activeGroup;
if (linkedObject?.name) {
this.formBuilderService.insertFormGroupControl(1, this.formGroup, this.formModel, this.groupCommunity);
this.formGroup.patchValue({
groupName: activeGroup != null ? activeGroup.name : '',
groupDescription: activeGroup != null ? activeGroup.firstMetadataValue('dc.description') : '',
groupName: activeGroup.name,
groupCommunity: linkedObject?.name ?? '',
groupDescription: activeGroup.firstMetadataValue('dc.description'),
});
} else {
this.formModel = [
this.groupName,
this.groupDescription,
];
this.formGroup.patchValue({
groupName: activeGroup.name,
groupDescription: activeGroup.firstMetadataValue('dc.description'),
});
}
setTimeout(() => {
if (!canEdit || activeGroup.permanent) {
this.formGroup.disable();
}
}, 200);
}
})
);
@@ -405,7 +434,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
*/
@HostListener('window:beforeunload')
ngOnDestroy(): void {
this.onCancel();
this.groupDataService.cancelEditGroup();
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
}
@@ -417,11 +446,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
if (hasValue(group) && hasValue(group._links.object.href)) {
return this.getLinkedDSO(group).pipe(
map((rd: RemoteData<DSpaceObject>) => {
if (hasValue(rd) && hasValue(rd.payload)) {
return true;
} else {
return false;
}
return hasValue(rd) && hasValue(rd.payload);
}),
catchError(() => observableOf(false)),
);

View File

@@ -38,17 +38,22 @@
<table id="epersonsSearch" class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th scope="col">{{messagePrefix + '.table.id' | translate}}</th>
<th scope="col">{{messagePrefix + '.table.name' | translate}}</th>
<th>{{messagePrefix + '.table.edit' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.identity' | translate}}</th>
<th class="align-middle">{{messagePrefix + '.table.edit' | translate}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let ePerson of (ePeopleSearchDtos | async)?.page">
<td>{{ePerson.eperson.id}}</td>
<td><a (click)="ePersonDataService.startEditingNewEPerson(ePerson.eperson)"
<td class="align-middle">{{ePerson.eperson.id}}</td>
<td class="align-middle"><a (click)="ePersonDataService.startEditingNewEPerson(ePerson.eperson)"
[routerLink]="[ePersonDataService.getEPeoplePageRouterLink()]">{{ePerson.eperson.name}}</a></td>
<td>
<td class="align-middle">
{{messagePrefix + '.table.email' | translate}}: {{ ePerson.eperson.email ? ePerson.eperson.email : '-' }}<br/>
{{messagePrefix + '.table.netid' | translate}}: {{ ePerson.eperson.netid ? ePerson.eperson.netid : '-' }}
</td>
<td class="align-middle">
<div class="btn-group edit-field">
<button *ngIf="(ePerson.memberOfGroup)"
(click)="deleteMemberFromGroup(ePerson)"
@@ -91,17 +96,22 @@
<table id="ePeopleMembersOfGroup" class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th scope="col">{{messagePrefix + '.table.id' | translate}}</th>
<th scope="col">{{messagePrefix + '.table.name' | translate}}</th>
<th>{{messagePrefix + '.table.edit' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.identity' | translate}}</th>
<th class="align-middle">{{messagePrefix + '.table.edit' | translate}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let ePerson of (ePeopleMembersOfGroupDtos | async)?.page">
<td>{{ePerson.eperson.id}}</td>
<td><a (click)="ePersonDataService.startEditingNewEPerson(ePerson.eperson)"
<td class="align-middle">{{ePerson.eperson.id}}</td>
<td class="align-middle"><a (click)="ePersonDataService.startEditingNewEPerson(ePerson.eperson)"
[routerLink]="[ePersonDataService.getEPeoplePageRouterLink()]">{{ePerson.eperson.name}}</a></td>
<td>
<td class="align-middle">
{{messagePrefix + '.table.email' | translate}}: {{ ePerson.eperson.email ? ePerson.eperson.email : '-' }}<br/>
{{messagePrefix + '.table.netid' | translate}}: {{ ePerson.eperson.netid ? ePerson.eperson.netid : '-' }}
</td>
<td class="align-middle">
<div class="btn-group edit-field">
<button (click)="deleteMemberFromGroup(ePerson)"
class="btn btn-outline-danger btn-sm"

View File

@@ -35,17 +35,19 @@
<table id="groupsSearch" class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th scope="col">{{messagePrefix + '.table.id' | translate}}</th>
<th scope="col">{{messagePrefix + '.table.name' | translate}}</th>
<th>{{messagePrefix + '.table.edit' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.collectionOrCommunity' | translate}}</th>
<th class="align-middle">{{messagePrefix + '.table.edit' | translate}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let group of (searchResults$ | async)?.payload?.page">
<td>{{group.id}}</td>
<td><a (click)="groupDataService.startEditingNewGroup(group)"
<td class="align-middle">{{group.id}}</td>
<td class="align-middle"><a (click)="groupDataService.startEditingNewGroup(group)"
[routerLink]="[groupDataService.getGroupEditPageRouterLink(group)]">{{group.name}}</a></td>
<td>
<td class="align-middle">{{(group.object | async)?.payload?.name}}</td>
<td class="align-middle">
<div class="btn-group edit-field">
<button *ngIf="(isSubgroupOfGroup(group) | async) && !(isActiveGroup(group) | async)"
(click)="deleteSubgroupFromGroup(group)"
@@ -88,17 +90,19 @@
<table id="subgroupsOfGroup" class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th scope="col">{{messagePrefix + '.table.id' | translate}}</th>
<th scope="col">{{messagePrefix + '.table.name' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
<th scope="col" class="align-middle">{{messagePrefix + '.table.collectionOrCommunity' | translate}}</th>
<th>{{messagePrefix + '.table.edit' | translate}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let group of (subGroups$ | async)?.payload?.page">
<td>{{group.id}}</td>
<td><a (click)="groupDataService.startEditingNewGroup(group)"
<td class="align-middle">{{group.id}}</td>
<td class="align-middle"><a (click)="groupDataService.startEditingNewGroup(group)"
[routerLink]="[groupDataService.getGroupEditPageRouterLink(group)]">{{group.name}}</a></td>
<td>
<td class="align-middle">{{(group.object | async)?.payload?.name}}</td>
<td class="align-middle">
<div class="btn-group edit-field">
<button (click)="deleteSubgroupFromGroup(group)"
class="btn btn-outline-danger btn-sm deleteButton"

View File

@@ -17,6 +17,7 @@ import { NotificationsService } from '../../../../shared/notifications/notificat
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
import { NoContent } from '../../../../core/shared/NoContent.model';
import { PaginationService } from '../../../../core/pagination/pagination.service';
import { followLink } from '../../../../shared/utils/follow-link-config.model';
/**
* Keys to keep track of specific subscriptions
@@ -117,7 +118,10 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
switchMap((config) => this.groupDataService.findAllByHref(this.groupBeingEdited._links.subgroups.href, {
currentPage: config.currentPage,
elementsPerPage: config.pageSize
}
},
true,
true,
followLink('object')
))
).subscribe((rd: RemoteData<PaginatedList<Group>>) => {
this.subGroups$.next(rd);
@@ -217,7 +221,8 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
switchMap((config) => this.groupDataService.searchGroups(this.currentSearchQuery, {
currentPage: config.currentPage,
elementsPerPage: config.pageSize
}))
}, true, true, followLink('object')
))
).subscribe((rd: RemoteData<PaginatedList<Group>>) => {
this.searchResults$.next(rd);
}));

View File

@@ -0,0 +1,83 @@
import { GroupPageGuard } from './group-page.guard';
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { ActivatedRouteSnapshot, Router } from '@angular/router';
import { of as observableOf } from 'rxjs';
import { AuthService } from '../../core/auth/auth.service';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
describe('GroupPageGuard', () => {
const groupsEndpointUrl = 'https://test.org/api/eperson/groups';
const groupUuid = '0d6f89df-f95a-4829-943c-f21f434fb892';
const groupEndpointUrl = `${groupsEndpointUrl}/${groupUuid}`;
const routeSnapshotWithGroupId = {
params: {
groupId: groupUuid,
}
} as unknown as ActivatedRouteSnapshot;
let guard: GroupPageGuard;
let halEndpointService: HALEndpointService;
let authorizationService: AuthorizationDataService;
let router: Router;
let authService: AuthService;
beforeEach(() => {
halEndpointService = jasmine.createSpyObj(['getEndpoint']);
(halEndpointService as any).getEndpoint.and.returnValue(observableOf(groupsEndpointUrl));
authorizationService = jasmine.createSpyObj(['isAuthorized']);
// NOTE: value is set in beforeEach
router = jasmine.createSpyObj(['parseUrl']);
(router as any).parseUrl.and.returnValue = {};
authService = jasmine.createSpyObj(['isAuthenticated']);
(authService as any).isAuthenticated.and.returnValue(observableOf(true));
guard = new GroupPageGuard(halEndpointService, authorizationService, router, authService);
});
it('should be created', () => {
expect(guard).toBeTruthy();
});
describe('canActivate', () => {
describe('when the current user can manage the group', () => {
beforeEach(() => {
(authorizationService as any).isAuthorized.and.returnValue(observableOf(true));
});
it('should return true', (done) => {
guard.canActivate(
routeSnapshotWithGroupId, { url: 'current-url'} as any
).subscribe((result) => {
expect(authorizationService.isAuthorized).toHaveBeenCalledWith(
FeatureID.CanManageGroup, groupEndpointUrl, undefined
);
expect(result).toBeTrue();
done();
});
});
});
describe('when the current user can not manage the group', () => {
beforeEach(() => {
(authorizationService as any).isAuthorized.and.returnValue(observableOf(false));
});
it('should not return true', (done) => {
guard.canActivate(
routeSnapshotWithGroupId, { url: 'current-url'} as any
).subscribe((result) => {
expect(authorizationService.isAuthorized).toHaveBeenCalledWith(
FeatureID.CanManageGroup, groupEndpointUrl, undefined
);
expect(result).not.toBeTrue();
done();
});
});
});
});
});

View File

@@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { Observable, of as observableOf } from 'rxjs';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { AuthService } from '../../core/auth/auth.service';
import { SomeFeatureAuthorizationGuard } from '../../core/data/feature-authorization/feature-authorization-guard/some-feature-authorization.guard';
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class GroupPageGuard extends SomeFeatureAuthorizationGuard {
protected groupsEndpoint = 'groups';
constructor(protected halEndpointService: HALEndpointService,
protected authorizationService: AuthorizationDataService,
protected router: Router,
protected authService: AuthService) {
super(authorizationService, router, authService);
}
getFeatureIDs(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID[]> {
return observableOf([FeatureID.CanManageGroup]);
}
getObjectUrl(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<string> {
return this.halEndpointService.getEndpoint(this.groupsEndpoint).pipe(
map(groupsUrl => `${groupsUrl}/${route?.params?.groupId}`)
);
}
}

View File

@@ -33,9 +33,9 @@
</div>
</form>
<ds-loading *ngIf="searching$ | async"></ds-loading>
<ds-loading *ngIf="loading$ | async"></ds-loading>
<ds-pagination
*ngIf="(pageInfoState$ | async)?.totalElements > 0 && !(searching$ | async)"
*ngIf="(pageInfoState$ | async)?.totalElements > 0 && !(loading$ | async)"
[paginationOptions]="config"
[pageInfoState]="pageInfoState$"
[collectionSize]="(pageInfoState$ | async)?.totalElements"
@@ -48,6 +48,7 @@
<tr>
<th scope="col">{{messagePrefix + 'table.id' | translate}}</th>
<th scope="col">{{messagePrefix + 'table.name' | translate}}</th>
<th scope="col">{{messagePrefix + 'table.collectionOrCommunity' | translate}}</th>
<th scope="col">{{messagePrefix + 'table.members' | translate}}</th>
<th>{{messagePrefix + 'table.edit' | translate}}</th>
</tr>
@@ -56,14 +57,27 @@
<tr *ngFor="let groupDto of (groupsDto$ | async)?.page">
<td>{{groupDto.group.id}}</td>
<td>{{groupDto.group.name}}</td>
<td>{{(groupDto.group.object | async)?.payload?.name}}</td>
<td>{{groupDto.epersons?.totalElements + groupDto.subgroups?.totalElements}}</td>
<td>
<div class="btn-group edit-field">
<button [routerLink]="groupService.getGroupEditPageRouterLink(groupDto.group)"
class="btn btn-outline-primary btn-sm"
title="{{messagePrefix + 'table.edit.buttons.edit' | translate: {name: groupDto.group.name} }}">
<ng-container [ngSwitch]="groupDto.ableToEdit">
<button *ngSwitchCase="true"
[routerLink]="groupService.getGroupEditPageRouterLink(groupDto.group)"
class="btn btn-outline-primary btn-sm btn-edit"
title="{{messagePrefix + 'table.edit.buttons.edit' | translate: {name: groupDto.group.name} }}"
>
<i class="fas fa-edit fa-fw"></i>
</button>
<button *ngSwitchCase="false"
[disabled]="true"
class="btn btn-outline-primary btn-sm btn-edit"
placement="left"
[ngbTooltip]="'admin.access-control.epeople.table.edit.buttons.edit-disabled' | translate"
>
<i class="fas fa-edit fa-fw"></i>
</button>
</ng-container>
<button *ngIf="!groupDto.group?.permanent && groupDto.ableToDelete"
(click)="deleteGroup(groupDto)" class="btn btn-outline-danger btn-sm"
title="{{messagePrefix + 'table.edit.buttons.remove' | translate: {name: groupDto.group.name} }}">

View File

@@ -30,6 +30,7 @@ import { routeServiceStub } from '../../shared/testing/route-service.stub';
import { RouterMock } from '../../shared/mocks/router.mock';
import { PaginationService } from '../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
describe('GroupRegistryComponent', () => {
let component: GroupsRegistryComponent;
@@ -43,6 +44,26 @@ describe('GroupRegistryComponent', () => {
let mockEPeople;
let paginationService;
/**
* Set authorizationService.isAuthorized to return the following values.
* @param isAdmin whether or not the current user is an admin.
* @param canManageGroup whether or not the current user can manage all groups.
*/
const setIsAuthorized = (isAdmin: boolean, canManageGroup: boolean) => {
(authorizationService as any).isAuthorized.and.callFake((featureId?: FeatureID) => {
switch (featureId) {
case FeatureID.AdministratorOf:
return observableOf(isAdmin);
case FeatureID.CanManageGroup:
return observableOf(canManageGroup);
case FeatureID.CanDelete:
return observableOf(true);
default:
throw new Error(`setIsAuthorized: this fake implementation does not support ${featureId}.`);
}
});
};
beforeEach(waitForAsync(() => {
mockGroups = [GroupMock, GroupMock2];
mockEPeople = [EPersonMock, EPersonMock2];
@@ -131,9 +152,9 @@ describe('GroupRegistryComponent', () => {
return createSuccessfulRemoteDataObject$(undefined);
}
};
authorizationService = jasmine.createSpyObj('authorizationService', {
isAuthorized: observableOf(true)
});
authorizationService = jasmine.createSpyObj('authorizationService', ['isAuthorized']);
setIsAuthorized(true, true);
paginationService = new PaginationServiceStub();
TestBed.configureTestingModule({
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
@@ -180,6 +201,88 @@ describe('GroupRegistryComponent', () => {
});
});
it('should display community/collection name if present', () => {
const collectionNamesFound = fixture.debugElement.queryAll(By.css('#groups tr td:nth-child(3)'));
expect(collectionNamesFound.length).toEqual(2);
expect(collectionNamesFound[0].nativeElement.textContent).toEqual('');
expect(collectionNamesFound[1].nativeElement.textContent).toEqual('testgroupid2objectName');
});
describe('edit buttons', () => {
describe('when the user is a general admin', () => {
beforeEach(fakeAsync(() => {
// NOTE: setting canManageGroup to false should not matter, since isAdmin takes priority
setIsAuthorized(true, false);
// force rerender after setup changes
component.search({ query: '' });
tick();
fixture.detectChanges();
}));
it('should be active', () => {
const editButtonsFound = fixture.debugElement.queryAll(By.css('#groups tr td:nth-child(5) button.btn-edit'));
expect(editButtonsFound.length).toEqual(2);
editButtonsFound.forEach((editButtonFound) => {
expect(editButtonFound.nativeElement.disabled).toBeFalse();
});
});
it('should not check the canManageGroup permissions', () => {
expect(authorizationService.isAuthorized).not.toHaveBeenCalledWith(
FeatureID.CanManageGroup, mockGroups[0].self
);
expect(authorizationService.isAuthorized).not.toHaveBeenCalledWith(
FeatureID.CanManageGroup, mockGroups[0].self, undefined // treated differently
);
expect(authorizationService.isAuthorized).not.toHaveBeenCalledWith(
FeatureID.CanManageGroup, mockGroups[1].self
);
expect(authorizationService.isAuthorized).not.toHaveBeenCalledWith(
FeatureID.CanManageGroup, mockGroups[1].self, undefined // treated differently
);
});
});
describe('when the user can edit the groups', () => {
beforeEach(fakeAsync(() => {
setIsAuthorized(false, true);
// force rerender after setup changes
component.search({ query: '' });
tick();
fixture.detectChanges();
}));
it('should be active', () => {
const editButtonsFound = fixture.debugElement.queryAll(By.css('#groups tr td:nth-child(5) button.btn-edit'));
expect(editButtonsFound.length).toEqual(2);
editButtonsFound.forEach((editButtonFound) => {
expect(editButtonFound.nativeElement.disabled).toBeFalse();
});
});
});
describe('when the user can not edit the groups', () => {
beforeEach(fakeAsync(() => {
setIsAuthorized(false, false);
// force rerender after setup changes
component.search({ query: '' });
tick();
fixture.detectChanges();
}));
it('should not be active', () => {
const editButtonsFound = fixture.debugElement.queryAll(By.css('#groups tr td:nth-child(5) button.btn-edit'));
expect(editButtonsFound.length).toEqual(2);
editButtonsFound.forEach((editButtonFound) => {
expect(editButtonFound.nativeElement.disabled).toBeTrue();
});
});
});
});
describe('search', () => {
describe('when searching with query', () => {
let groupIdsFound;

View File

@@ -9,7 +9,7 @@ import {
of as observableOf,
Subscription
} from 'rxjs';
import { catchError, map, switchMap, take } from 'rxjs/operators';
import { catchError, map, switchMap, take, tap } from 'rxjs/operators';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
@@ -35,6 +35,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { NoContent } from '../../core/shared/NoContent.model';
import { PaginationService } from '../../core/pagination/pagination.service';
import { followLink } from '../../shared/utils/follow-link-config.model';
@Component({
selector: 'ds-groups-registry',
@@ -75,7 +76,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
/**
* A boolean representing if a search is pending
*/
searching$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
// Current search in groups registry
currentSearchQuery: string;
@@ -118,12 +119,12 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
* @param data Contains query param
*/
search(data: any) {
this.searching$.next(true);
if (hasValue(this.searchSub)) {
this.searchSub.unsubscribe();
this.subs = this.subs.filter((sub: Subscription) => sub !== this.searchSub);
}
this.searchSub = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe(
tap(() => this.loading$.next(true)),
switchMap((paginationOptions) => {
const query: string = data.query;
if (query != null && this.currentSearchQuery !== query) {
@@ -132,8 +133,8 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
}
return this.groupService.searchGroups(this.currentSearchQuery.trim(), {
currentPage: paginationOptions.currentPage,
elementsPerPage: paginationOptions.pageSize
});
elementsPerPage: paginationOptions.pageSize,
}, true, true, followLink('object'));
}),
getAllSucceededRemoteData(),
getRemoteDataPayload(),
@@ -141,18 +142,22 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
if (groups.page.length === 0) {
return observableOf(buildPaginatedList(groups.pageInfo, []));
}
return this.authorizationService.isAuthorized(FeatureID.AdministratorOf).pipe(
switchMap((isSiteAdmin: boolean) => {
return observableCombineLatest(groups.page.map((group: Group) => {
if (!this.deletedGroupsIds.includes(group.id)) {
if (hasValue(group) && !this.deletedGroupsIds.includes(group.id)) {
return observableCombineLatest([
this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined),
this.authorizationService.isAuthorized(FeatureID.CanDelete, group.self),
this.canManageGroup$(isSiteAdmin, group),
this.hasLinkedDSO(group),
this.getSubgroups(group),
this.getMembers(group)
]).pipe(
map(([isAuthorized, hasLinkedDSO, subgroups, members]:
[boolean, boolean, RemoteData<PaginatedList<Group>>, RemoteData<PaginatedList<EPerson>>]) => {
map(([canDelete, canManageGroup, hasLinkedDSO, subgroups, members]:
[boolean, boolean, boolean, RemoteData<PaginatedList<Group>>, RemoteData<PaginatedList<EPerson>>]) => {
const groupDtoModel: GroupDtoModel = new GroupDtoModel();
groupDtoModel.ableToDelete = isAuthorized && !hasLinkedDSO;
groupDtoModel.ableToDelete = canDelete && !hasLinkedDSO;
groupDtoModel.ableToEdit = canManageGroup;
groupDtoModel.group = group;
groupDtoModel.subgroups = subgroups.payload;
groupDtoModel.epersons = members.payload;
@@ -165,15 +170,25 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
return buildPaginatedList(groups.pageInfo, dtos);
}));
})
);
})
).subscribe((value: PaginatedList<GroupDtoModel>) => {
this.groupsDto$.next(value);
this.pageInfoState$.next(value.pageInfo);
this.searching$.next(false);
this.loading$.next(false);
});
this.subs.push(this.searchSub);
}
canManageGroup$(isSiteAdmin: boolean, group: Group): Observable<boolean> {
if (isSiteAdmin) {
return observableOf(true);
} else {
return this.authorizationService.isAuthorized(FeatureID.CanManageGroup, group.self);
}
}
/**
* Delete Group
*/

View File

@@ -6,10 +6,7 @@ import { By } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
import { AuthService } from '../../core/auth/auth.service';
import { METADATA_IMPORT_SCRIPT_NAME, ScriptDataService } from '../../core/data/processes/script-data.service';
import { EPerson } from '../../core/eperson/models/eperson.model';
import { ProcessParameter } from '../../process-page/processes/process-parameter.model';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
@@ -22,12 +19,9 @@ describe('MetadataImportPageComponent', () => {
let comp: MetadataImportPageComponent;
let fixture: ComponentFixture<MetadataImportPageComponent>;
let user;
let notificationService: NotificationsServiceStub;
let scriptService: any;
let router;
let authService;
let locationStub;
function init() {
@@ -37,13 +31,6 @@ describe('MetadataImportPageComponent', () => {
invoke: createSuccessfulRemoteDataObject$({ processId: '45' })
}
);
user = Object.assign(new EPerson(), {
id: 'userId',
email: 'user@test.com'
});
authService = jasmine.createSpyObj('authService', {
getAuthenticatedUserFromStore: observableOf(user)
});
router = jasmine.createSpyObj('router', {
navigateByUrl: jasmine.createSpy('navigateByUrl')
});
@@ -65,7 +52,6 @@ describe('MetadataImportPageComponent', () => {
{ provide: NotificationsService, useValue: notificationService },
{ provide: ScriptDataService, useValue: scriptService },
{ provide: Router, useValue: router },
{ provide: AuthService, useValue: authService },
{ provide: Location, useValue: locationStub },
],
schemas: [NO_ERRORS_SCHEMA]
@@ -107,9 +93,8 @@ describe('MetadataImportPageComponent', () => {
proceed.click();
fixture.detectChanges();
}));
it('metadata-import script is invoked with its -e currentUserEmail, -f fileName and the mockFile', () => {
it('metadata-import script is invoked with -f fileName and the mockFile', () => {
const parameterValues: ProcessParameter[] = [
Object.assign(new ProcessParameter(), { name: '-e', value: user.email }),
Object.assign(new ProcessParameter(), { name: '-f', value: 'filename.txt' }),
];
expect(scriptService.invoke).toHaveBeenCalledWith(METADATA_IMPORT_SCRIPT_NAME, parameterValues, [fileMock]);

View File

@@ -23,20 +23,14 @@ import { getProcessDetailRoute } from '../../process-page/process-page-routing.p
/**
* Component that represents a metadata import page for administrators
*/
export class MetadataImportPageComponent implements OnInit {
export class MetadataImportPageComponent {
/**
* The current value of the file
*/
fileObject: File;
/**
* The authenticated user's email
*/
private currentUserEmail$: Observable<string>;
public constructor(protected authService: AuthService,
private location: Location,
public constructor(private location: Location,
protected translate: TranslateService,
protected notificationsService: NotificationsService,
private scriptDataService: ScriptDataService,
@@ -51,15 +45,6 @@ export class MetadataImportPageComponent implements OnInit {
this.fileObject = file;
}
/**
* Method provided by Angular. Invoked after the constructor.
*/
ngOnInit() {
this.currentUserEmail$ = this.authService.getAuthenticatedUserFromStore().pipe(
map((user: EPerson) => user.email)
);
}
/**
* When return button is pressed go to previous location
*/
@@ -68,22 +53,17 @@ export class MetadataImportPageComponent implements OnInit {
}
/**
* Starts import-metadata script with -e currentUserEmail -f fileName (and the selected file)
* Starts import-metadata script with -f fileName (and the selected file)
*/
public importMetadata() {
if (this.fileObject == null) {
this.notificationsService.error(this.translate.get('admin.metadata-import.page.error.addFile'));
} else {
this.currentUserEmail$.pipe(
switchMap((email: string) => {
if (isNotEmpty(email)) {
const parameterValues: ProcessParameter[] = [
Object.assign(new ProcessParameter(), { name: '-e', value: email }),
Object.assign(new ProcessParameter(), { name: '-f', value: this.fileObject.name }),
];
return this.scriptDataService.invoke(METADATA_IMPORT_SCRIPT_NAME, parameterValues, [this.fileObject]);
}
}),
this.scriptDataService.invoke(METADATA_IMPORT_SCRIPT_NAME, parameterValues, [this.fileObject]).pipe(
getFirstCompletedRemoteData(),
).subscribe((rd: RemoteData<Process>) => {
if (rd.hasSucceeded) {

Some files were not shown because too many files have changed in this diff Show More