Compare commits

...

663 Commits

Author SHA1 Message Date
Tim Donohue
c5448fe252 Update version tag for release 2024-07-09 14:28:46 -05:00
Alan Orth
a705067aed Merge pull request #3152 from alanorth/client-user-agent
Forward client's user-agent instead of Node's
2024-07-03 08:54:24 +03:00
Tim Donohue
28bd6a5fe6 Merge pull request #3063 from tdonohue/port_new_csrf_fixes_to_7x
[Port dspace-7_x] Ensures CSRF token is initialized prior to first modifying (non-GET) request
2024-07-01 11:35:51 -05:00
Alan Orth
5b2966cf48 src/app/core: forward client user-agent
Forward the client's user-agent instead of sending Node's.
2024-06-28 07:58:15 +03:00
Tim Donohue
735a3af254 Merge pull request #3139 from tdonohue/port_3138_to_7x
[Port dspace-7_x] Fix error which prevent submitter to deposit a new item
2024-06-20 14:37:34 -05:00
Giuseppe Digilio
3036e72c4c [DURACOM-282] fix issue with submission edit page when providing a not existing workspaceitem 2024-06-20 11:35:53 -05:00
Giuseppe Digilio
4eacdfb113 [DURACOM-282] fix issue with removing duplicated json patch operations, which in some case changed the order in the queue 2024-06-20 11:24:51 -05:00
Tim Donohue
fbb97167d6 Merge pull request #3135 from tdonohue/port_3029_to_7x
[Port dspace-7_x] src/assets/i18n: CrossRef → Crossref
2024-06-20 07:00:03 -05:00
Alan Orth
2d881c9621 src/assets/i18n: CrossRef → Crossref
As part of a rebranding in 2015 Crossref started stylizing their
name as "Crossref" instead of "CrossRef".

See: https://www.crossref.org/blog/the-logo-has-landed/
2024-06-18 15:41:31 -05:00
Tim Donohue
31508ed5eb Merge pull request #3121 from alexandrevryghem/w2p-113560_edit-item-add-relationships-one-by-one_contribute-7_x
Remove Bitstreams on edit-item-page doesn't work
2024-06-18 12:12:09 -05:00
Tim Donohue
3895d26d0b Merge pull request #3133 from tdonohue/port_3129_to_7x
[Port dspace-7_x] Bump ws from 7.5.9 to 7.5.10
2024-06-17 17:14:06 -05:00
Tim Donohue
e9b39ff106 Merge pull request #3131 from tdonohue/port_3125_to_7x
[Port dspace-7_x] Bump postcss to 8.4.38
2024-06-17 16:49:42 -05:00
Tim Donohue
8615c70ccc Bump ws from 7.5.9 to 7.5.10 2024-06-17 16:21:54 -05:00
Tim Donohue
93f1ae0b49 Merge pull request #3130 from tdonohue/port_3124_to_7x
Bump express from 4.18.2 to 4.19.2
2024-06-17 15:11:52 -05:00
Tim Donohue
8990a2d132 Bump postcss to 8.4.38 2024-06-17 15:03:05 -05:00
Tim Donohue
f4eb8876c2 Bump express from 4.18.2 to 4.19.2 2024-06-17 14:37:28 -05:00
Tim Donohue
eb7a93a12f Merge pull request #3128 from tdonohue/port_2915_to_7x
[Port dspace-7_x] Bump tar to 6.2.1
2024-06-17 14:21:25 -05:00
Tim Donohue
69eedf4015 Bump tar to 6.2.1 2024-06-17 13:04:59 -05:00
Tim Donohue
ff31c302e2 Merge pull request #3126 from tdonohue/port_3123_to_7x
[Port dspace-7_x] Bump braces from 3.0.2 to 3.0.3
2024-06-17 12:34:21 -05:00
Tim Donohue
7b554a48f9 Bump braces from 3.0.2 to 3.0.3 2024-06-17 10:41:08 -05:00
Tim Donohue
c088ccb32e Merge pull request #3119 from tdonohue/port_3067_to_7x
[Port dspace-7_x] Fix "Browse by Author" remaining serious accessibility issues
2024-06-17 09:27:26 -05:00
Alexandre Vryghem
1990120fa0 Merge branch 'refs/heads/w2p-113560_edit-item-add-relationships-one-by-one_contribute-7.6' into w2p-113560_edit-item-add-relationships-one-by-one_contribute-7_x 2024-06-14 23:07:12 +02:00
Alexandre Vryghem
1444271e69 115046: Fixed edit item bitstream tab buttons not updating correctly & removed method calls retuning observables in edit collection's content source tab 2024-06-14 23:03:56 +02:00
Tim Donohue
09bf25a233 Merge pull request #3039 from VictorHugoDuranS/dspace-7_2668
Fix - Change color Contrast in cookie settings box
2024-06-14 15:31:33 -05:00
Tim Donohue
9580b29491 Merge pull request #3118 from DSpace/backport-3099-to-dspace-7_x
[Port dspace-7_x] Improving accessibility of the ds-loading component
2024-06-14 15:13:31 -05:00
Tim Donohue
5fe6dfb949 Merge pull request #3117 from DSpace/backport-3115-to-dspace-7_x
[Port dspace-7_x] Fixes: Site hangs when selecting items via keyboard from the hamburger menu
2024-06-14 15:05:43 -05:00
root
ebb846f2a9 Correcting a formatting error 2024-06-14 15:02:36 -05:00
root
983e955398 DSpace #1176 - Browse by Author serious issues 2024-06-14 15:02:13 -05:00
root
36266dffef Melhorando acessibilidade do componente ds-loading
(cherry picked from commit e96d64a622)
2024-06-14 19:09:15 +00:00
Eike Martin Löhden
53fb9f85fc Change key-listener to keydown in order to work on chrome browsers.
(cherry picked from commit a4e9a9a4b8)
2024-06-14 18:56:25 +00:00
Tim Donohue
16210365be Merge pull request #3109 from alexandrevryghem/w2p-113560_edit-item-add-relationships-one-by-one_contribute-7_x
[Port dspace-7_x] Fixed caching & same entity type relationship with different left/rightwardtype bugs on edit item relationships
2024-06-11 14:43:12 -05:00
Alexandre Vryghem
a0260f5267 Merge branch 'w2p-113560_edit-item-add-relationships-one-by-one_contribute-7.6' into w2p-113560_edit-item-add-relationships-one-by-one_contribute-7_x 2024-06-10 16:54:08 +02:00
Tim Donohue
2d28dfc6e6 Merge pull request #3107 from tdonohue/port_3098_to_7x
[Port dspace-7_x] Fixed item edit relationships with same type name but different entities all being shown under same label
2024-06-10 09:32:40 -05:00
Alexandre Vryghem
31ab05f4fe 115434: Passed relatedEntityType parameter to byLabel endpoint 2024-06-07 15:16:41 -05:00
Alexandre Vryghem
813db7cc9f 115046: Fixed same type entity relationships with same leftward/rightward type displaying twice 2024-06-07 14:55:41 +02:00
Tim Donohue
0d3db9f19d Merge pull request #3103 from alexandrevryghem/w2p-113901_edit-item-relationships-does-not-display-tilted-relationships_contribute-7_x
[Port dspace-7_x] Fixed edit item relationships tab not displaying tilted relationships
2024-06-06 09:21:14 -05:00
Alexandre Vryghem
1f4700cb91 Merge remote-tracking branch 'refs/remotes/alex/w2p-113901_edit-item-relationships-does-not-display-tilted-relationships' into dspace-7_x
# Conflicts:
#	src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts
2024-06-05 23:42:34 +02:00
Tim Donohue
27c57d6f4e Merge pull request #3102 from tdonohue/port_2953_to_7x
[Port dspace-7_x] Avoid metadata representation element is rendered twice when checking the browse configuration
2024-06-05 15:52:56 -05:00
Giuseppe Digilio
3f7e273597 Avoid metadata representation element is rendered twice when checking the browse configuration 2024-06-05 15:25:12 -05:00
Alexandre Vryghem
6d1e6f90fc 115046: Fixed issue where relationsToItems would never emit when an empty array was passed to it 2024-06-03 15:03:47 +02:00
Alexandre Vryghem
8ee375016f 115046: Fixed loading animation not resetting hen closing modal 2024-06-03 14:55:53 +02:00
Alexandre Vryghem
f1706e24c2 115046: Fixed related item not invalidating on relationship deletion & automatically invalidate the isSelected value after updating relationship on item 2024-06-03 14:55:53 +02:00
Yana De Pauw
33b59c739d 115284: Fix issue with same type relationships 2024-06-03 11:20:21 +02:00
Giuseppe Digilio
2d81a487d3 [DURACOM-247] Move check for initialized token to request effects 2024-05-31 12:13:05 -05:00
Tim Donohue
df8f0db784 Update build script to use "docker compose" instead of "docker-compose". Fixes random failures starting e2e test backend via Docker 2024-05-31 12:13:05 -05:00
Tim Donohue
950038e6df Fix tests. Add XSRFService to all specs which need it to be initialized 2024-05-31 12:13:01 -05:00
Tim Donohue
769210bef2 Update code to use GET request. Cleanup lint errors & add in basic TypeDocs 2024-05-31 12:12:02 -05:00
Art Lowel
7d5e499f21 make a call to ensure a correct XSRF token before performing any non-GET requests 2024-05-31 12:12:02 -05:00
Tim Donohue
cbee67cd4b Merge pull request #3059 from alexandrevryghem/w2p-113560_edit-item-add-relationships-one-by-one_contribute-7_x
[Port dspace-7_x] Fixed creation & deletion of relationships not working correctly & added same entity type relationship support
2024-05-31 12:09:50 -05:00
Tim Donohue
5580c81ff7 Merge pull request #3095 from tdonohue/port_2761_to_7x
[Port dspace-7_x] Fix caching issues for versioning
2024-05-31 11:45:18 -05:00
Yury Bondarenko
73be8a972c Always wait for correct draft status, fix spelling 2024-05-30 17:00:51 -05:00
Yury Bondarenko
5f5f6da067 Clean up VersionHistoryDataService changes 2024-05-30 16:57:26 -05:00
Yury Bondarenko
be817b0ab6 Fix cache invalidation for versioning 2024-05-30 16:57:14 -05:00
Alexandre Vryghem
bedfe02f65 Merge branch 'w2p-113560_edit-item-add-relationships-one-by-one_contribute-7.6' into w2p-113560_edit-item-add-relationships-one-by-one_contribute-7_x 2024-05-30 23:17:15 +02:00
Alexandre Vryghem
227d47154c 115046: Fixed issue where store operations would sometimes be performed in incorrect order 2024-05-30 23:12:03 +02:00
Alexandre Vryghem
4a70382072 115046: Fixed incorrect name variant being saved in the store when marking relationship ready for deletion 2024-05-30 23:12:02 +02:00
Alexandre Vryghem
c687d3ff5d 115046: Fixed performance issues in virtual metadata popup 2024-05-30 23:12:02 +02:00
Tim Donohue
491a61b8b1 Merge pull request #3088 from DSpace/backport-3080-to-dspace-7_x
[Port dspace-7_x] Fixed page size change and sorting not working on comcol pages
2024-05-29 16:55:00 -05:00
Tim Donohue
8c6df7ac8e Merge pull request #3089 from DSpace/backport-3079-to-dspace-7_x
[Port dspace-7_x] Fixed delete item page freezing when having relationships
2024-05-29 16:54:40 -05:00
Tim Donohue
8c35ca86c8 Merge pull request #3064 from alexandrevryghem/minor-ui-improvements_contribute-7_x
[Port dspace-7_x] Fixed item-edit.cy.ts regularly failing & minor UI improvements
2024-05-29 16:36:20 -05:00
Alexandre Vryghem
7993020463 115427: Fixed delete item not redirecting when having some virtual metadata that needs to be copied
(cherry picked from commit b8d9c650f5)
2024-05-29 20:51:02 +00:00
Alexandre Vryghem
8b3010be0c 115427: Fixed edit item pages not redirecting to 404 with invalid id
(cherry picked from commit 249cac4f27)
2024-05-29 20:51:01 +00:00
Alexandre Vryghem
2843659416 115427: Fixed delete item page freezing when it has relationships
(cherry picked from commit e6086e16ea)
2024-05-29 20:51:01 +00:00
Alexandre Vryghem
a77d80946b 115428: Fixed EventEmitter being triggered multiple times because its name was the same as a standard DOM event
(cherry picked from commit 4d89a11943)
2024-05-29 20:35:49 +00:00
Tim Donohue
a3a298f51e Merge pull request #3087 from DSpace/backport-3062-to-dspace-7_x
[Port dspace-7_x] Made legacy bitstream URLs redirect with 301 status code
2024-05-29 15:00:44 -05:00
Alexandre Vryghem
c332600633 114624: Made the legacyBitstreamURLRedirectGuard return false for valid bitstream urls in combination with a HardRedirectService#redirect, this will make ensure the redirect is visible for curl instead of being performed by Angular
(cherry picked from commit 23644e9ec7)
2024-05-29 18:38:53 +00:00
Kuno Vercammen
f1b397fc11 114624: Refactored legacyBitstreamURL resolver into a guard to set the redirect status code to 301 Moved Permanently
(cherry picked from commit 369bd693d4)
2024-05-29 18:38:53 +00:00
Alexandre Vryghem
7a9bb4d726 113901: Don't unsubscribe after first succeeded remote data, because otherwise the reRequestOnStale won't work 2024-05-23 11:58:44 +02:00
VictorHugoDuranS
975e9fd356 Hide search text field when number of facet values is less than the maximum (#3061)
Hide search text field in the filter section if the number of facet values does not exceed the maximum number of facet values shown.
2024-05-22 09:23:51 +03:00
Alexandre Vryghem
6cab13a876 Added missing alt & replaced remaining dspace logo PNGs with SVGs
(cherry picked from commit 56c786d3f7)
2024-05-17 21:34:14 +02:00
Alexandre Vryghem
da439000ac Fixed item-edit.cy.ts regularly failing because of aria-required-children for role="row" & added missing roles to edit comcol tabs
(cherry picked from commit 77d0f9cd60)
2024-05-17 21:32:32 +02:00
Alexandre Vryghem
f8a8b90556 Aligned communities search results like collections on md screens
(cherry picked from commit c2684d3078)
2024-05-17 21:31:14 +02:00
Alexandre Vryghem
7b374f7a32 Merge branch 'minor-ui-improvements_contribute-7.6' into minor-ui-improvements_contribute-7_x 2024-05-17 21:30:51 +02:00
Alexandre Vryghem
14dbced218 Merge branch 'w2p-113560_edit-item-add-relationships-one-by-one_contribute-7.6' into w2p-113560_edit-item-add-relationships-one-by-one_contribute-7_x 2024-05-17 20:06:53 +02:00
Alexandre Vryghem
9053489ddd 115046: Fixed url parameters not being filtered out of the url before hasChanges$ & isReinstatable$ is calculated 2024-05-17 18:51:23 +02:00
Alexandre Vryghem
f7c9058e50 Merge branch 'w2p-113560_edit-item-add-relationships-one-by-one_contribute-7.6' into w2p-113560_edit-item-add-relationships-one-by-one_contribute-7_x
# Conflicts:
#	src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html
#	src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html
2024-05-17 14:40:20 +02:00
Alexandre Vryghem
f65b96412a Fixed multiple edit relationship bugs
- Fixed issue making it impossible to add new relationships until the page is refreshed after deleting an existing one (only when you refreshed the page after creating the initial relationship)
- Fixed NPE in DsDynamicLookupRelationModalComponent
- Grouped buttons on relationship page in order to assure that they always have the same behaviour
2024-05-17 14:34:24 +02:00
Tim Donohue
088e56fb8a Merge pull request #3055 from DSpace/backport-3046-to-dspace-7_x
[Port dspace-7_x] Fix self link don't match warnings for authorization endpoint
2024-05-16 08:37:43 -05:00
Alexandre Vryghem
1b38f2d25d Merge branch 'w2p-113560_edit-item-add-relationships-one-by-one' into w2p-113560_edit-item-add-relationships-one-by-one_contribute-7_x
# Conflicts:
#	src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html
2024-05-16 10:17:34 +02:00
Alexandre Vryghem
e0fb590b02 Don't encode the uri parameter of /api/authz/authorizations/search/object since the returned self link doesn't encode the uri param
(cherry picked from commit b0991db1d4)
2024-05-15 21:49:41 +00:00
Tim Donohue
8ea7a2572a Merge pull request #3053 from tdonohue/port_2962_to_7x
[Port dspace-7_x] Fix submission lost changes after save
2024-05-15 14:17:49 -05:00
Tim Donohue
3edb663b21 Minor bug fix to specs 2024-05-15 13:28:19 -05:00
Tim Donohue
432692edc8 Merge pull request #3051 from tdonohue/port_2944_to_7x
[Port dspace-7_x] Disable inlineCriticalCss by default for better SSR performance
2024-05-15 13:21:04 -05:00
Tim Donohue
dfc304f9aa Merge pull request #3052 from DSpace/backport-2706-to-dspace-7_x
[Port dspace-7_x] Update pt-PT.json5
2024-05-15 13:20:45 -05:00
Art Lowel
d1890e0253 move embed item fix to SubmissionJsonPatchOperationsService 2024-05-15 12:40:23 -05:00
Art Lowel
66832eeb28 embed the item to submission patch request to fix issue where the submission form would revert to the previous data on save 2024-05-15 12:37:01 -05:00
Ricardo Saraiva
83b67149a6 Update pt-PT.json5
Revision and improvement of some of the already existing translation keys upon reviewers’ suggestions.

(cherry picked from commit 16c5665b31)
2024-05-15 17:16:57 +00:00
Ricardo Saraiva
ba1eee27b3 Update pt-PT.json5
(cherry picked from commit c4142e7a80)
2024-05-15 17:16:57 +00:00
Ricardo Saraiva
bb055e12fb Update pt-PT.json5
(cherry picked from commit 6c5e971dc1)
2024-05-15 17:16:57 +00:00
Ricardo Saraiva
242043b3f8 Update pt-PT.json5
(cherry picked from commit 3262e00d6e)
2024-05-15 17:16:57 +00:00
Ricardo Saraiva
170c7ac6cc Update pt-PT.json5
(cherry picked from commit 38eacf31bf)
2024-05-15 17:16:56 +00:00
Ricardo Saraiva
c2ddaf286d Update pt-PT.json5
Addition of new translation keys (following DSpace 7.6.1 release) & revision/improvement of some of the already existing.

(cherry picked from commit 18085698b2)
2024-05-15 17:16:56 +00:00
Ricardo Saraiva
071fa370bf Update pt-PT.json5
Addition of new translation keys (following DSpace 7.6.1 release) & revision/improvement of some of the already existing.

(cherry picked from commit 02792d28bc)
2024-05-15 17:16:56 +00:00
Tim Donohue
eeb562dd14 Disable inlineCriticalCss by default for better SSR performance 2024-05-15 12:12:55 -05:00
Alexandre Vryghem
479adf6519 115046: Fixed failing tests & added new test to cover added code 2024-05-15 16:09:24 +02:00
Tim Donohue
84e4cc01cc Merge pull request #3044 from DSpace/backport-3028-to-dspace-7_x
[Port dspace-7_x] Add HTML support on system-wide alert (Fixes #3019)
2024-05-14 13:26:10 -05:00
Tim Donohue
d95574df6f Merge pull request #3043 from tdonohue/port_3025_to_7x
[Port dspace-7_x] Czech DSpace 7 UI translation by NTK
2024-05-14 13:25:57 -05:00
Abel Gómez
40b4df6354 Fix code alignment
(cherry picked from commit af193282da)
2024-05-14 17:31:35 +00:00
Abel Gómez
7a364fddeb Add HTML support to system-wide alert banner. Fixes #3019.
(cherry picked from commit b9fb4e2159)
2024-05-14 17:31:35 +00:00
NTK
d9a5f324a5 Czech DSpace 7 UI translation by NTK 2024-05-14 12:14:29 -05:00
Tim Donohue
fdafa0f034 Merge pull request #3042 from tdonohue/port_3007_to_7x
[Port dspace-7_x] After English, reorder languages alphabetically by code
2024-05-14 10:35:44 -05:00
Tim Donohue
d55b0c0dd3 After English, reorder languages alphabetically by code 2024-05-14 09:26:49 -05:00
Tim Donohue
30c025c5c1 Merge pull request #2841 from atmire/w2p-112198_add-relationship-effects-queue
Fix for not all CREATE/DELETE Relationship requests coming through, during submission entity lookup
2024-05-14 09:24:29 -05:00
Tim Donohue
75718bf3ec Merge pull request #2983 from oscar-escire/fix/authorSearch
Fix Author search result without co-author
2024-05-13 14:19:37 -05:00
Tim Donohue
36fa4d4cae Merge pull request #3027 from alexandrevryghem/discovery-backports_contribute-7_x
[Port dspace-7_x] Multiple discovery backports
2024-05-13 11:41:22 -05:00
Tim Donohue
1aa6d358bb Merge pull request #3038 from tdonohue/port_2771_to_7x
[Port dspace-7_x] improved layout of feedback form (increase vertical spacing)
2024-05-13 08:38:59 -05:00
Oscar Chacón
5e59403779 Update: replaced logic of allMetadataValues method by allMetadataNoExcludingValues logic 2024-05-11 17:37:38 -06:00
Sascha Szott
6ff9edaa8b improved layout of feedback form (increase vertical spacing) 2024-05-10 16:38:33 -05:00
Victor Hugo Duran Santiago
876ecb1d12 Fix - Change color Contrast in cookie settings box 2024-05-10 15:27:37 -06:00
Tim Donohue
9bd154792a Merge pull request #3033 from DSpace/backport-2951-to-dspace-7_x
[Port dspace-7_x] Fix display of dso names for EPersons
2024-05-10 15:59:53 -05:00
Tim Donohue
d43c44e911 Merge pull request #2973 from oscar-escire/fix/policyNameForm
added maxlength and hint to resource policy
2024-05-10 15:14:08 -05:00
Tim Donohue
8467bb7c0a Merge pull request #3034 from tdonohue/port_2954_to_7x
[Port dspace-7_x] Live import preview should show all metadata values, not only the first one
2024-05-10 13:43:53 -05:00
Jens Vannerum
01719822c1 114440: fix test 2024-05-10 12:34:45 -05:00
Marie Verdonck
ffbe407d83 104686: Live Import preview only shows first metadata value per field 2024-05-10 12:30:13 -05:00
Gantner, Florian Klaus
202c84c484 extend eperson render type for display of dso name
https://github.com/DSpace/dspace-angular/issues/2947
(cherry picked from commit 97fade00a4)
2024-05-10 17:20:15 +00:00
Tim Donohue
6ac2e9a95d Merge pull request #3032 from DSpace/backport-2725-to-dspace-7_x
[Port dspace-7_x] Encode all `RequestParam` values
2024-05-10 11:21:55 -05:00
Alexandre Vryghem
daff8a4594 Refactored code to always encode the RequestParams
(cherry picked from commit d5cf236938)
2024-05-10 15:53:07 +00:00
Alexandre Vryghem
ae8e0f9ae8 110889: Fixed searchevents request containing the query params in the clickedObject
Also fixed minor CSS issue in mobile mode where sidebar is still visible

(cherry picked from commit cbe3f2f8f5)
2024-05-09 18:13:13 +02:00
Alexandre Vryghem
0aadcdfdd9 110889: Prevent vocabulary undefined from being retrieved in hierarchy filter when none is configured
(cherry picked from commit 9e7a59ddd0)
2024-05-09 16:17:53 +02:00
Art Lowel
a658bf4531 decrease the number of unnecessary rerenders by checking whether the relationship types have changed 2024-05-09 15:25:37 +02:00
Art Lowel
4541788a34 fix issue where relationship lists wouldn't update automatically from the second submit onwards 2024-05-09 14:55:37 +02:00
Alan Orth
c44fce9e0f Merge pull request #2866 from atmire/w2p-113124_AddInputToOpenDsMetadataUriValuesInNewWindow
Allow opening ds-metadata-uri-values links in a new window
2024-05-09 15:46:51 +03:00
Art Lowel
cb59c05baf fix spinners 2024-05-09 14:06:56 +02:00
Art Lowel
d75b158ee1 Merge remote-tracking branch 'contributions/fix-api-undefined-issue-7.6' into w2p-113560_edit-item-add-relationships-one-by-one 2024-05-09 13:44:18 +02:00
Art Lowel
1c50dbf4c6 Merge remote-tracking branch 'alexandre/fix-create-relationship-not-working-between-same-types_contribute-7.6' into w2p-113560_edit-item-add-relationships-one-by-one 2024-05-09 13:40:03 +02:00
Alexandre Vryghem
ce3eb76190 110889: Don't retrieve the accessStatus when item.showAccessStatuses is false
(cherry picked from commit 37c534b809)
2024-05-09 12:26:18 +02:00
Alexandre Vryghem
6dac7ceec4 Prevent ItemSearchResult from being recreated at every iteration
(cherry picked from commit da320b5fbc)
2024-05-09 12:18:36 +02:00
Alexandre Vryghem
3454ca99f3 108586: Fixed ItemListElementComponent not pointing to the ItemSearchResultListElementComponent from the themes folder when present
(cherry picked from commit d00fc5d9d3)
2024-05-09 12:16:56 +02:00
Tim Donohue
dd5a167d0d Merge pull request #3013 from atmire/process-ui-null-eperson-fix-7.6
[Port dspace-7_x] Handle Null Users Gracefully in Process Overview Page
2024-05-08 14:45:49 -05:00
Art Lowel
9f3ee32858 fix issue where a submit emitted from the edit relationship modal wouldn't arrive in the edit relationships component 2024-05-08 17:48:33 +02:00
Tim Donohue
3ae9c8e9f8 Merge pull request #3017 from DSpace/backport-3016-to-dspace-7_x
[Port dspace-7_x] Ensure `item` is also embedded in submission form
2024-05-07 14:22:03 -05:00
Tim Donohue
3c9cb4e3b6 Ensure item is also embedded in submission form
(cherry picked from commit bf4d04c909)
2024-05-07 18:53:19 +00:00
Oscar Chacón
f80d1fdc68 fixed lint errors 2024-05-06 11:12:22 -06:00
Tim Donohue
4bf8944c48 Merge pull request #2987 from atmire/w2p-105866_Submission-performance-7_x
7.x - Submission performance
2024-05-06 11:12:25 -05:00
Tim Donohue
2b0f0f627e Merge pull request #3009 from tdonohue/port_2994_to_7x
[Port dspace-7_x] Bump ejs from 3.1.9 to 3.1.10
2024-05-06 08:55:55 -05:00
Kuno Vercammen
b7b1c3e582 Merge remote-tracking branch 'origin/dspace-7_x' into process-ui-null-eperson-fix-7.6
# Conflicts:
#	src/assets/i18n/en.json5
2024-05-06 13:45:39 +02:00
Kuno Vercammen
488958c251 113904: Added tests for null user handling 2024-05-06 12:09:40 +02:00
Kuno Vercammen
26c234e36c 113904: Fix null user handling 2024-05-06 11:53:05 +02:00
Tim Donohue
55e742fbfb Bump ejs from 3.1.9 to 3.1.10 2024-05-03 16:07:48 -05:00
Tim Donohue
0be17bb0ed Merge pull request #3008 from tdonohue/port_2863_to_7x
[Port dspace-7_x] Added Arabic Translation
2024-05-03 15:55:51 -05:00
Mohamed Ali
aa7496e41d Added Arabic Translation 2024-05-03 15:20:51 -05:00
Tim Donohue
bc5262f5f7 Merge pull request #3006 from DSpace/backport-2831-to-dspace-7_x
[Port dspace-7_x] French translations for missing parameters (access control and batch loading)
2024-05-03 12:37:13 -05:00
Tim Donohue
07a91222c4 lint fixes
(cherry picked from commit 3bb559542d)
2024-05-03 16:44:32 +00:00
Pierre Lasou
b06bece1cb Update fr.json5 correcting bad quotation marks
(cherry picked from commit 1abc796734)
2024-05-03 16:44:32 +00:00
Pierre Lasou
053844a7bc Update fr.json5 - adding missing comma
(cherry picked from commit 3b98c8c84f)
2024-05-03 16:44:32 +00:00
Pierre Lasou
7129d6b9c8 Update fr.json5 to adresse reviewer feedback
(cherry picked from commit 46df7e805e)
2024-05-03 16:44:32 +00:00
Pierre Lasou
ae35461516 Update fr.json5 to add batch upload labels
Addition of batch upload labels (ZIP, SAF)

(cherry picked from commit c4548e63ca)
2024-05-03 16:44:32 +00:00
Pierre Lasou
a250de49a0 Update fr.json5 to add access conditions labels
Changes to the labels related to access control and access conditions.

(cherry picked from commit e747520646)
2024-05-03 16:44:32 +00:00
Tim Donohue
13775e53de Merge pull request #3001 from tdonohue/port_2933_to_7x
[Port dspace-7_x] Reset dropdown values
2024-05-02 16:29:14 -05:00
Andreas Awouters
e9c5340f67 113319: Fix dropdown component spec
(cherry picked from commit 70357803e86caabc6357b7cbec9db1ef8edd2a56)
2024-05-02 14:57:41 -05:00
Andreas Awouters
861132d04d 113319: Add dropdown entry to clear selection
(cherry picked from commit 13d1547b3dbf5d4cd74617d6327044da798ed44e)
2024-05-02 14:57:29 -05:00
Tim Donohue
a7517d6814 Merge pull request #2992 from DSpace/backport-2991-to-dspace-7_x
[Port dspace-7_x] Remove obsolete "version" tag from Docker Compose files (Angular version)
2024-05-01 16:28:54 -05:00
Tim Donohue
971c07632a Merge pull request #2641 from hutattedonmyarm/regex-error-messages-backport-dspace7x
[Port dspace-7_x] More meaningful regex error messages
2024-05-01 16:22:43 -05:00
Tim Donohue
45840c1c17 Remove obsolete "version" tag from compose files
(cherry picked from commit d6303de877)
2024-05-01 20:05:31 +00:00
Tim Donohue
91d72c19dd Merge pull request #2694 from hutattedonmyarm/dspace-7_x
[Port dspace-7_x]  Enable type-bind for checkbox inputs during submission
2024-04-30 15:13:40 -05:00
Tim Donohue
5301a8fc51 Merge pull request #2990 from DSpace/backport-2710-to-dspace-7_x
[Port dspace-7_x] Update robots.txt.ejs
2024-04-29 17:03:13 -05:00
Tim Donohue
27d00d133a Merge pull request #2989 from tdonohue/port_2675_to_7x
[Port to dspace-7_x] Multiline selected facet badge on search page
2024-04-29 16:57:02 -05:00
Bram Luyten
6447c3769b Update robots.txt.ejs
Fix for issue #2709

(cherry picked from commit fbd35296f0)
2024-04-29 21:29:37 +00:00
lotte
ebb99c813a Merge branch 'w2p-108595_multiline-search-filter-badge' into multiline-search-filter-badge_main 2024-04-29 16:02:41 -05:00
lotte
e1fe4031e5 Removed unnecessary change 2024-04-29 15:59:22 -05:00
lotte
65860b81b1 108595: fix for overflowing search filter badge selection 2024-04-29 15:59:04 -05:00
Yana De Pauw
dabc0e9b4b [Ticket 2124] Slow response times 2024-04-29 12:59:35 +02:00
Oscar Chacón
2530845b9b Fix trailing spaces and removed console.log 2024-04-26 17:38:06 -06:00
Oscar Chacón
f055ff6fbd Fix: author search result without co-author 2024-04-25 16:19:21 -06:00
Art Lowel
e4b098e64d update test 2024-04-25 10:56:54 +02:00
Alexandre Vryghem
132e1f9524 Minor UI improvements
- Fixed DSO edit menu sometimes displaying underneath header based on header z-index
- Fixed accessibility issue on request a copy page
2024-04-23 02:02:28 +02:00
Tim Donohue
679702bb5a Merge pull request #2967 from tdonohue/update_backend_docker_scripts_7x
Update REST and CI docker compose scripts to sync with 7.x backend
2024-04-22 16:39:38 -05:00
Oscar Chacón
f8e0cc6808 added maxlength and hint to resource policy 2024-04-22 14:42:32 -06:00
Tim Donohue
45ab7231a0 Merge pull request #2969 from alexandrevryghem/w2p-112970_added-missing-breadcrumbs_contribute-7_x
[Port dspace-7_x] Added missing/incomplete breadcrumbs on create community/collection/item pages
2024-04-22 12:23:00 -05:00
Tim Donohue
c794e1ebde Merge pull request #2968 from alexandrevryghem/fix-broken-item-mapper-pagination_contribute-7_x
[Port dspace-7_x] Fixed pagination issues on item mapper
2024-04-22 12:13:02 -05:00
Alexandre Vryghem
7d49666865 Merge remote-tracking branch 'alex/w2p-112970_added-missing-breadcrumbs_contribute-7.4' into dspace-7_x
# Conflicts:
#	src/app/collection-page/collection-page-routing.module.ts
#	src/app/community-page/community-page-routing.module.ts
#	src/app/core/core.module.ts
#	src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts
#	src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts
#	src/assets/i18n/en.json5
2024-04-22 16:23:48 +02:00
Alexandre Vryghem
7e6f736b2f Merge branch 'fix-broken-item-mapper-pagination_contribute-7.6' into dspace-7_x
# Conflicts:
#	src/app/access-control/bulk-access/browse/bulk-access-browse.component.html
#	src/app/access-control/epeople-registry/epeople-registry.component.html
#	src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html
#	src/app/access-control/group-registry/group-form/members-list/members-list.component.html
#	src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html
#	src/app/shared/object-select/collection-select/collection-select.component.html
#	src/app/shared/object-select/item-select/item-select.component.html
2024-04-19 23:52:34 +02:00
Tim Donohue
b43e99518d Update REST and CI docker compose scripts to sync with 7.x backend 2024-04-19 16:21:12 -05:00
Tim Donohue
7a5694347a Merge pull request #2965 from tdonohue/port_2816_to_7x
[Port dspace-7_x] Return 404 Not Found status code on missing identifiers
2024-04-19 13:21:05 -05:00
Jens Vannerum
92f2a77dae 111326: return not found status code on missing identifiers 2024-04-19 12:17:22 -05:00
Tim Donohue
57c2b02277 Merge pull request #2960 from DSpace/backport-2768-to-dspace-7_x
[Port dspace-7_x] Fix infinite scroll
2024-04-19 11:36:51 -05:00
Andrea Barbasso
fde0ebf872 [DURACOM-224] fix infinite scroll
(cherry picked from commit 2afa473cfb)
2024-04-19 15:43:02 +00:00
Alexandre Vryghem
268d2e54fc 108555: Refactored CollectionSelectComponent to not call canSelect every time changes are detected 2024-04-18 21:18:10 +02:00
Alexandre Vryghem
59197cff2d 108555: Refactored ItemSelectComponent to not call canSelect every time changes are detected 2024-04-18 21:18:10 +02:00
Alexandre Vryghem
da31c4f253 108555: Removed pageInfoState Input since it wasn't used and almost all the components gave the incorrect type of data to it 2024-04-18 19:28:01 +02:00
Tim Donohue
4aa3158f33 Merge pull request #2925 from DSpace/backport-2905-to-dspace-7_x
[Port dspace-7_x] Fix for submission describe warning contribution bug
2024-04-12 13:52:45 -05:00
lotte
3768dc030e 113500: Fixed lint issues
(cherry picked from commit 903af2ccdb)
2024-04-12 17:20:51 +00:00
lotte
27286998f7 113500: Fixed lint error
(cherry picked from commit 722bd6f7f5)
2024-04-12 17:20:51 +00:00
lotte
d8b6e86896 113500: Fixed tests after bugfix
(cherry picked from commit 4e0046022b)
2024-04-12 17:20:51 +00:00
lotte
47537a019c 113500: Fix for warning on first describe step
(cherry picked from commit 993ce1ab16)
2024-04-12 17:20:51 +00:00
Art Lowel
8e59b7d0b0 also add relationships one by one on the edit item relationships tab 2024-04-11 17:51:59 +02:00
Tim Donohue
f72725ceed Merge pull request #2919 from DSpace/backport-2782-to-dspace-7_x
[Port dspace-7_x] Fixed Font Awesome CSS Pseudo-elements not rendering in v6.x
2024-04-11 08:59:54 -05:00
Alexandre Vryghem
4251630ab8 112970: Always rerun resolvers on url change for create comcol pages 2024-04-11 00:18:04 +02:00
Alexandre Vryghem
e2c2174bf7 111373: Fixed Font Awesome CSS Pseudo-elements for v6.x
(cherry picked from commit 01f97a623f)
2024-04-10 21:51:53 +00:00
Tim Donohue
de1a240140 Merge pull request #2858 from 4Science/DURACOM-241
7.x - Header, navbar, and admin sidebar refactoring
2024-04-10 14:18:54 -05:00
Tim Donohue
b12bdfdd22 Docker build IDs must all be unique to avoid image conflicts. Ensure no builds use a generic name like "dspace-angular". 2024-04-09 15:06:43 -05:00
Tim Donohue
971c302378 Merge pull request #2911 from tdonohue/update_actions_7x
[Port dspace-7_x] Update all GitHub Actions & add token for Codecov.io
2024-04-08 16:59:26 -05:00
Tim Donohue
b758d0a5f9 Ensure all artifacts have a unique name by appending Node version to each 2024-04-08 14:46:16 -05:00
Tim Donohue
78f1d9f763 Update all GitHub Actions. Add newly required CODECOV_TOKEN to codecov action 2024-04-08 14:46:10 -05:00
Tim Donohue
90dc0e3f61 Merge pull request #2901 from DSpace/backport-2067-to-dspace-7_x
[Port dspace-7_x] Add configuration option to disable inlined CSS in SSR HTML
2024-04-05 13:45:50 -05:00
Yury Bondarenko
fd10fbe2a8 Add to config.example.yml
(cherry picked from commit 4a39f33006)
2024-04-05 18:14:43 +00:00
Yury Bondarenko
3b11ac517c Add configuration option to disable inlined CSS in SSR HTML
When inlining CSS, Angular Universal needs to extract critical styles.
This seems to take up a significant chunk of processing time.

However, loading may appear less smooth when this feature is disabled.

Added to the configuration to make it easier to A/B test this without a full re-build.

(cherry picked from commit 38a058d7af)
2024-04-05 18:14:42 +00:00
Tim Donohue
a1090fbb0d Merge pull request #2896 from tdonohue/port_2894_to_7x
[Port dspace-7_x] Bump axios to 1.6.8
2024-04-04 15:00:51 -05:00
Tim Donohue
4239d37a38 Bump axios to 1.6.8 2024-04-04 14:12:29 -05:00
Tim Donohue
a87a5f9b5e Merge pull request #2892 from tdonohue/port_2867_to_7x
[Port dspace-7_x]  Bump follow-redirects from 1.15.5 to 1.15.6
2024-04-04 12:36:55 -05:00
Tim Donohue
777fc571b0 Merge pull request #2895 from tdonohue/port_2872_to_7x
[Port dspace-7_x] Bump webpack-dev-middleware from 5.3.3 to 5.3.4
2024-04-04 12:36:36 -05:00
Tim Donohue
dc92bbd80a Merge pull request #2893 from tdonohue/port_2881_to_7x
[Port dspace-7_x] Bump express from 4.18.2 to 4.19.2
2024-04-04 12:09:24 -05:00
Tim Donohue
d503e4db07 Bump webpack-dev-middleware from 5.3.3 to 5.3.4 2024-04-04 11:58:15 -05:00
Tim Donohue
bba7fe2f74 Bump express from 4.18.2 to 4.19.2 2024-04-04 11:34:30 -05:00
Tim Donohue
ed8e280aa4 Bump follow-redirects from 1.15.5 to 1.15.6 2024-04-04 11:01:41 -05:00
Alexandre Vryghem
60d93e653f 113938: Added missing comcol structure to workspace/workflow item breadcrumbs 2024-04-04 12:07:29 +02:00
Alexandre Vryghem
fb8733ca0b 113901: Store the retrieved Relationships in the store
This needs to be done here too, since tilted relationships won't be returned by the api/core/items/{uuid}/relationships endpoint
2024-04-02 17:39:44 +02:00
Tim Donohue
2dfa075423 Merge pull request #2832 from alexandrevryghem/patch_2808
[Port dspace-7_x] Refactored community & collection pages
2024-03-29 10:04:08 -05:00
Alan Orth
43137fe676 Merge pull request #2879 from DSpace/backport-2877-to-dspace-7_x
[Port dspace-7_x] System-wide alert shouldn't be created when the message is empty
2024-03-26 11:26:31 +03:00
Alexandre Vryghem
d05438dc3e Prevent system-wide alert to be activated with empty message
(cherry picked from commit 767de4b5c8)
2024-03-26 07:12:03 +00:00
Alexandre Vryghem
25cb0455e2 Fixed bug in BrowseService where findListByHref was called with null instead of undefined, which prevented its default values from being used
(cherry picked from commit e6bf2f0ca7)
2024-03-24 19:12:48 +01:00
Alexandre Vryghem
692bb991a0 112970: Added missing breadcrumbs to create community/collection pages 2024-03-19 12:06:39 +01:00
Kuno Vercammen
badf00258c 113124: Allow opening ds-metadata-uri-values links in a new window 2024-03-15 11:42:01 +01:00
Tim Donohue
4c693a1294 Merge pull request #2861 from DSpace/backport-2777-to-dspace-7_x
[Port dspace-7_x] Element reference for typeahead directive is only defined when model has authority
2024-03-08 15:56:38 -06:00
William Welling
85922433a5 NgbTypeahead instance is only defined when model has authority
(cherry picked from commit 018d44c616)
2024-03-08 20:13:19 +00:00
Davide Negretti
3caac21648 [DURACOM-195] fix spacing in collapsible menu
(cherry picked from commit a88282d70b)
2024-03-07 18:57:39 +01:00
Davide Negretti
f26b265e64 [DURACOM-195] fix
(cherry picked from commit fdbe7a6005)
2024-03-07 18:57:36 +01:00
Davide Negretti
89c88ca6e0 [DURACOM-195] replace ul/li with div in sidebar
(cherry picked from commit d1dc8e6038)
2024-03-07 18:57:33 +01:00
Davide Negretti
e3f1fa39d5 [DURACOM-195] add background on mouse over to dropdown user menu items
(cherry picked from commit 8e35c0f713)
2024-03-07 18:57:30 +01:00
Davide Negretti
49efc4175d [DURACOM-195] fix issue with logo on small screens
(cherry picked from commit bff93a0847)
2024-03-07 18:57:27 +01:00
Davide Negretti
48e9817245 [DURACOM-195] replace ul/li with div in navbar
(cherry picked from commit abba806d40)
2024-03-07 18:57:24 +01:00
Davide Negretti
a9c3008cee [DURACOM-195] fixes
(cherry picked from commit 523850bc45)
2024-03-07 18:57:19 +01:00
Davide Negretti
8efc401811 [DURACOM-195] fix prefix of custom CSS variables
(cherry picked from commit 46fe7f63e3)
2024-03-07 18:57:16 +01:00
Davide Negretti
9518f70b42 [DURACOM-195] misc fixes
(cherry picked from commit 87d3383bba)
2024-03-07 18:57:13 +01:00
Davide Negretti
2f5c3b1267 [DURACOM-195] fix comment
(cherry picked from commit 80cc4c5d9a)
2024-03-07 18:57:08 +01:00
Davide Negretti
bb16983029 [DURACOM-195] fix issue with event handling in expandable admin sidebar section
(cherry picked from commit f25ec6210b)
2024-03-07 18:56:57 +01:00
Davide Negretti
5b1438f09f [DURACOM-195] accessibility issue fixed on log-out button
(cherry picked from commit 8f73b8ff9d)
2024-03-07 18:56:50 +01:00
Davide Negretti
510968b0c6 [DURACOM-195] [DURACOM-210] Reorder SCSS and CSS theme variables 2024-03-07 18:54:28 +01:00
Davide Negretti
8718bd0df6 [DURACOM-195] Header, navbar, and sidebar refactoring 2024-03-07 18:34:34 +01:00
Davide Negretti
7d2fdb7598 [DURACOM-195] Base components (simplify header-navbar-wrapper) 2024-03-07 18:32:51 +01:00
Davide Negretti
6c6afbf148 [DURACOM-195] Base components 2024-03-07 18:32:46 +01:00
Davide Negretti
d95d5753b4 [DURACOM-195] New host-window service methods 2024-03-07 18:32:38 +01:00
Tim Donohue
2178e0e6a3 Merge pull request #2852 from DSpace/backport-2851-to-dspace-7_x
[Port dspace-7_x] fix selector value in CommunitySidebarSearchListElementComponent
2024-03-05 09:52:46 -06:00
Sascha Szott
77f3b97e46 fix selector value in CommunitySidebarSearchListElementComponent
(cherry picked from commit ad3ef56e06)
2024-03-05 15:13:14 +00:00
Tim Donohue
203b0bbbaf Merge pull request #2850 from DSpace/backport-2847-to-dspace-7_x
[Port dspace-7_x] Bump sanitize-html from 2.10.0 to 2.12.1
2024-03-04 16:37:05 -06:00
dependabot[bot]
781de6e998 Bump sanitize-html from 2.10.0 to 2.12.1
Bumps [sanitize-html](https://github.com/apostrophecms/sanitize-html) from 2.10.0 to 2.12.1.
- [Changelog](https://github.com/apostrophecms/sanitize-html/blob/main/CHANGELOG.md)
- [Commits](https://github.com/apostrophecms/sanitize-html/compare/2.10.0...2.12.1)

---
updated-dependencies:
- dependency-name: sanitize-html
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit e7eaa9f61b)
2024-03-04 22:02:36 +00:00
Tim Donohue
6fefbe129d Merge pull request #2849 from alanorth/backport-2356-to-dspace-7_x
[Port dspace-7_x] Update dependencies for Node v20
2024-03-04 14:28:47 -06:00
Alan Orth
3add551bde [Port dspace-7_x] Update dependencies for Node v20
Port of #2356 by @mwoodiupui to dspace-7_x.
2024-03-04 20:33:12 +03:00
Tim Donohue
51f48b31dc Merge pull request #2843 from DSpace/backport-2822-to-dspace-7_x
[Port dspace-7_x] Bump ip from 2.0.0 to 2.0.1
2024-02-29 15:56:19 -06:00
dependabot[bot]
b2adc42aaf Bump ip from 2.0.0 to 2.0.1
Bumps [ip](https://github.com/indutny/node-ip) from 2.0.0 to 2.0.1.
- [Commits](https://github.com/indutny/node-ip/compare/v2.0.0...v2.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit c147c7d6ea)
2024-02-29 21:26:32 +00:00
Alexandre Vryghem
f2dab663ca 108588: Fixed browse by issue date show loading icon indefinitely when empty
(cherry picked from commit 6f51bd866c)
2024-02-23 21:40:21 +01:00
DSpace Bot
0f09920cb1 [Port dspace-7_x] site search should not adopt existing URL query string parameters (#2824)
Do not merge existing query params when using site search from MyDSpace workflow.
2024-02-22 11:50:03 +03:00
Alan Orth
41faa5ee52 Merge pull request #2821 from tdonohue/port_2733_to_7x
[Port dspace-7_x]  Use keyboard to select values (part 2)
2024-02-20 21:13:10 +03:00
Jens Vannerum
d920567f8a 111768: move original code back to init only for performance 2024-02-20 09:45:03 -06:00
Jens Vannerum
85694ec285 111768: add back necessary setCurrentValue calls that were removed earlier 2024-02-20 09:44:57 -06:00
Nona Luypaert
325728de8f 112198: Ensure relationship requests are sent one by one
- remove (de)select all buttons in DynamicLookupRelationSearchTab
- add requestQueue to RelationshipEffects
2024-02-19 11:09:05 +01:00
Tim Donohue
3ce4603328 Merge pull request #2812 from DSpace/backport-2770-to-dspace-7_x
[Port dspace-7_x] German translations of feedback page translation keys
2024-02-15 17:11:58 -06:00
Sascha Szott
9cff5e9c34 minor improvement of German translation
(cherry picked from commit e03b492dbd)
2024-02-15 22:11:36 +00:00
Sascha Szott
0fdfecd973 added german translations of feedback page translation keys
(cherry picked from commit 26ebc9f8fc)
2024-02-15 22:11:36 +00:00
Tim Donohue
9dc0363337 Merge pull request #2811 from DSpace/backport-2794-to-dspace-7_x
[Port dspace-7_x] Added scope @Input() to the SearchComponent & fixed related bugs
2024-02-15 15:16:09 -06:00
Alexandre Vryghem
d96c048874 111639: Fixed search settings not using the scope
(cherry picked from commit 93b22cba1f)
2024-02-15 20:38:01 +00:00
Alexandre Vryghem
865f4899b2 111639: Added scope @Input() and made the search facets & search results use that scope
(cherry picked from commit 22e87a5f5a)
2024-02-15 20:38:01 +00:00
Tim Donohue
b350916fe3 Merge pull request #2810 from DSpace/backport-2647-to-dspace-7_x
[Port dspace-7_x] File edit component updated to work for forms without access conditions.
2024-02-15 14:34:38 -06:00
Michael Spalti
381254a16e File edit component updated to work for forms without access conditions.
Fixed unit test

Fixed accidental reformatting

(cherry picked from commit 6fff475a5a)
2024-02-15 20:00:45 +00:00
Tim Donohue
8506d36dbf Merge pull request #2800 from alexandrevryghem/generify-component-loaders_contribute-7_x
[Port dspace-7_x] Refactored community & collection pages
2024-02-14 10:58:38 -06:00
Alexandre Vryghem
0366e8b934 Replace routerLinkActive with [class.active] to ensure visibility of aria-current tag 2024-02-14 00:10:41 +01:00
Tim Donohue
eba7683f6b Merge pull request #2803 from DSpace/backport-2705-to-dspace-7_x
[Port dspace-7_x] Minor updates to i18n strings for clarity and consistency
2024-02-07 12:31:13 -06:00
Tim Donohue
5a0018d4fa Merge pull request #2802 from DSpace/backport-2766-to-dspace-7_x
[Port dspace-7_x] Fix missing Register DOI button if configuration enabled
2024-02-07 11:48:27 -06:00
Thomas Misilo
17dc120ca1 Update "E-mail" to be "Email" for consistency
(cherry picked from commit 7b8962a7cd)
2024-02-07 17:23:05 +00:00
Alan Orth
8eb2e2ce80 src/assets/i18n/en.json5: minor changes for consistency
Fix some random capitalizations and strange wording.

(cherry picked from commit d6c46847c2)
2024-02-07 17:23:05 +00:00
Alan Orth
b2b9fb8bf9 src/assets/i18n/en.json5: minor updates for consistency
We should be capitalizing acronyms and project-specific nouns like
MyDSpace consistently in our interface.

(cherry picked from commit f78f4b45fc)
2024-02-07 17:23:05 +00:00
Alan Orth
9d9d90ab9c src/assets/i18n: change "controller" to "reviewer"
Reviewer is a less obscure term for what this actually is in most
cases.

(cherry picked from commit 98241d8925)
2024-02-07 17:23:05 +00:00
Tim Donohue
ccd7056a87 Merge pull request #2801 from DSpace/backport-2799-to-dspace-7_x
[Port dspace-7_x] Bump follow-redirects from 1.15.3 to 1.15.5
2024-02-07 11:06:06 -06:00
Kim Shepherd
c50dd1a3e9 Improve cfg handling in item-status.component.ts
(cherry picked from commit 68475d1ed6)
2024-02-07 16:57:19 +00:00
Kim Shepherd
8b3bc90864 Improve cfg handling in item-status.component.ts
(cherry picked from commit 3e33785311)
2024-02-07 16:57:19 +00:00
Agustina Martinez
9f99555c64 Update item-status.component.ts
Fix initialOperations typo and return ops with register doi op

(cherry picked from commit e77898eaac)
2024-02-07 16:57:19 +00:00
dependabot[bot]
eb4633557d Bump follow-redirects from 1.15.3 to 1.15.5
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.5.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.5)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 90a277fbeb)
2024-02-07 16:34:28 +00:00
Alexandre Vryghem
effa19f719 111679: Restored missing inAndOutputNames on ClaimedTaskActionsLoaderComponent 2024-02-07 13:56:08 +01:00
Alexandre Vryghem
835891fe59 Merge remote-tracking branch 'upstream/dspace-7_x' into generify-component-loaders_contribute-7_x
# Conflicts:
#	src/app/shared/theme-support/themed.component.ts
2024-02-07 00:18:22 +01:00
Alexandre Vryghem
ff55646c62 108588: Fixed accessibility issues on ds-comcol-page-browse-by 2024-02-07 00:17:46 +01:00
Tim Donohue
972c1098cb Merge pull request #2798 from DSpace/backport-2795-to-dspace-7_x
[Port dspace-7_x] fix German translation of admin.workflow.item.workflow
2024-02-06 16:03:36 -06:00
Sascha Szott
59fc3e5635 remove translation (use singular form instead of plural form)
(cherry picked from commit ac5670a514)
2024-02-06 22:03:10 +00:00
Tim Donohue
60b310e216 Merge pull request #2796 from tdonohue/port_2791_to_7x
[Port dspace-7_x] Fix issue with cli.yml not using same network as backend.
2024-02-05 16:08:24 -06:00
Tim Donohue
b9cc5ba824 Resync docker-compose-ci and docker/docker-compose-rest with DSpace/DSpace docker scripts 2024-02-05 14:19:53 -06:00
Tim Donohue
f072ae74af Bug fix to GitHub CI build. Set compose project name via env variable. Also add default name if unspecified 2024-02-05 11:14:00 -06:00
Tim Donohue
e2d014d0a6 Update cli.yml to reference dspacenet in docker-compose-rest. Remove unnecessary network from cli.assetstore.yml 2024-02-05 11:13:53 -06:00
Tim Donohue
ab579da614 Merge pull request #2790 from DSpace/backport-2778-to-dspace-7_x
[Port dspace-7_x] Dedupe patch operations by op and path
2024-02-01 13:50:52 -06:00
William Welling
05c978bc62 Dedupe patch operations by op and path
(cherry picked from commit f40639de86)
2024-02-01 19:00:52 +00:00
Tim Donohue
95c635c451 Merge pull request #2786 from tdonohue/port_2717_to_7x
[Port dspace-7_x] Fix accessibility issues (part 2)
2024-01-31 11:56:18 -06:00
Alexandre Vryghem
3e48e5903e Fixed dropdown accessibility issue
- Removed non-existing dropdownMenuButton references
- Added appropriate roles to dropdown menus
2024-01-31 09:51:19 -06:00
Alexandre Vryghem
63fa8f39f5 Fixed user authentication/registration accessibility issue
- Added the correct autocomplete value
- Removed dangling labels, because aria labels already describe those input fields & we can't use ids in this component because otherwise there are duplicate ids on the /login page
2024-01-31 09:51:09 -06:00
Alexandre Vryghem
138e163fa8 Fixed edit metadata page accessibility issue
- Hide fontawesome icons for screen readers
- Added missing aria-label to metadata field input
2024-01-31 09:51:03 -06:00
Alexandre Vryghem
ac743c5138 Fixed feedback form page accessibility issue
- The form used a fieldset tag instead of a form tag
2024-01-31 09:50:58 -06:00
Alexandre Vryghem
37803e9330 Fixed remaining community/collection page accessibility issues
- Hide fontawesome icons for screen readers
- Replaced dangling labels with spans and added more descriptive aria label to browse link
- Fixed empty label/missing header in edit resource policy table
- Added missing comcol logo al text
- Refactored dso-edit-menu-section to not display empty link buttons and duplicate titles
2024-01-31 09:50:50 -06:00
Alexandre Vryghem
8b5cd79bdc Fixed accessibility issues on AccessControlFormContainerComponent
- Replaced invisible label in AccessControlArrayFormComponent that was only used for styling with span
- Fixed radio buttons not being part of a fieldset
- Fixed empty table header
2024-01-31 09:50:44 -06:00
Alexandre Vryghem
949e55235a Fixed accessibility issues on BitstreamFormatsComponent
- Removed empty label of the select box and replaced it with an aria-label
- Fixed empty table header
2024-01-31 09:50:38 -06:00
Alexandre Vryghem
5173ac3704 Fixed accessibility issues on MetadataRegistryComponent
- Removed empty label of the select box and replaced it with an aria-label
- Fixed empty table header
2024-01-31 09:50:31 -06:00
Alexandre Vryghem
9335c32334 Fixed accessibility issues on MetadataSchemaComponent
- Removed empty label of the select box and replaced it with an aria-label
- Fixed empty table header
2024-01-31 09:50:23 -06:00
Alexandre Vryghem
b0d3710454 Fixed accessibility issues with pagination button
- Replaced the h6 tags with the role heading
- Gave the gear button the roles in order to be detected as an expandable menu
- Replaced the dropdown structure to render a menu of listboxes
- Added the aria-selected attribute
2024-01-31 09:49:38 -06:00
Alexandre Vryghem
e0849af926 Fix header ordering 2024-01-31 09:42:30 -06:00
Alexandre Vryghem
b076b98996 Fixed accessibility issues on SubmissionImportExternalSearchbarComponent
- Fixed input not describing itself
- Put the items from the dropdown in an ul
- Fixed broken aria labels
- Removed titles who where identical to the displayed value
2024-01-31 09:42:24 -06:00
Alexandre Vryghem
cc0501f340 Fixed search filters having empty input buttons 2024-01-31 09:42:17 -06:00
Alexandre Vryghem
6b204b5b53 Removed invisible buttons on the CommunityListComponent & fixed content displacement when expanding com/col 2024-01-31 09:42:12 -06:00
Alexandre Vryghem
3bf7d819de Fixed accessibility issues related to dso selectors
- Fixed header ordering
- Fixed input field not having a description what it does (because the header isn't always shown I decided to use aria-labels instead of regular labels)
2024-01-31 09:42:04 -06:00
Tim Donohue
67fc0054c1 Merge pull request #2773 from DSpace/backport-2755-to-dspace-7_x
[Port dspace-7_x] Add logo max constraints
2024-01-25 15:53:07 -06:00
FrancescoMolinaro
d4493cb534 add width 100% to prevent overflow on mobile
(cherry picked from commit f82ba8c5ad)
2024-01-25 21:08:11 +00:00
FrancescoMolinaro
682ec2b678 remove class from template
(cherry picked from commit d9e25882ce)
2024-01-25 21:08:11 +00:00
FrancescoMolinaro
20f5f17aac adapt solution to prevent img overflow
(cherry picked from commit d3c788a4e1)
2024-01-25 21:08:11 +00:00
FrancescoMolinaro
f1a7f36f17 add logo max costraints
(cherry picked from commit 32e3141b19)
2024-01-25 21:08:11 +00:00
Tim Donohue
5d700d5563 Merge pull request #2772 from DSpace/backport-2769-to-dspace-7_x
[Port dspace-7_x] fixed typo in info.feedback.page_help
2024-01-25 11:02:51 -06:00
Sascha Szott
c11a3b1372 fixed typo
(cherry picked from commit 4b7798fcc7)
2024-01-25 16:25:48 +00:00
Alan Orth
be0512d74e Merge pull request #2764 from alanorth/backport-2733-to-dspace-7_x
[Port dspace-7_x] Use keyboard to select values
2024-01-23 12:18:28 +03:00
Jens Vannerum
2d0d6ddc5d 110088: new implementation for keyboard support in dropdowns 2024-01-23 11:43:37 +03:00
Tim Donohue
b2cae3b48d Merge pull request #2758 from DSpace/backport-2691-to-dspace-7_x
[Port dspace-7_x] Decrease min-height for Login Dropdown Menu
2024-01-19 11:12:03 -06:00
Tim Donohue
5f19b6a26a Merge pull request #2757 from DSpace/backport-2736-to-dspace-7_x
[Port dspace-7_x] use localized error message
2024-01-19 11:11:50 -06:00
Thomas Misilo
d880b728ce Decrease min-height for Login Dropdown Menu
Fixes #2690

This is noticable when you have disabled local authentication, and only have a singular remote authentication such as Shibboleth or Orcid.

(cherry picked from commit 09fc44a539)
2024-01-19 15:25:12 +00:00
Sascha Szott
e7490340f8 use localized error message
(cherry picked from commit 92a74cee5a)
2024-01-19 15:21:44 +00:00
Alan Orth
fed70c5e9b Merge pull request #2754 from DSpace/backport-2721-to-dspace-7_x
[Port dspace-7_x] Fix whitespace in links due to newlines
2024-01-19 10:07:44 +03:00
Alan Orth
f08d74b269 src/app/item-page: remove newlines in links
The newlines cause whitespace to be enapsulated in the rendered link.

(cherry picked from commit 7207bbbd0e)
2024-01-19 05:14:00 +00:00
Alan Orth
f2f5156065 src/app/community-list-page: remove newlines in links
The newlines cause whitespace to be enapsulated in the rendered link.

(cherry picked from commit 7bedf7fc59)
2024-01-19 05:14:00 +00:00
Tim Donohue
0ed96d893d Merge pull request #2748 from DSpace/backport-2711-to-dspace-7_x
[Port dspace-7_x] Add more automated accessibility scanning to e2e tests & fix a few minor accessibility bugs
2024-01-17 14:09:02 -06:00
Tim Donohue
2137720602 Add environment variable for Person collection name. Use it in submission e2e tests.
(cherry picked from commit a5a89a4b98)
2024-01-17 19:38:04 +00:00
Tim Donohue
12a19378f7 Move increment/decrement to i18n. Update to use "name".
(cherry picked from commit 0aaf3131fa)
2024-01-17 19:38:04 +00:00
Tim Donohue
75c81b712e Add accessibility testing for admin sidebar
(cherry picked from commit 31bc05235e)
2024-01-17 19:38:04 +00:00
Tim Donohue
4e0a889fa1 Add Item Edit accessibility tests. Switch Item to use to one with bitstreams. Minor updates to HTML to pass accessibility tests.
(cherry picked from commit 80492cd88a)
2024-01-17 19:38:04 +00:00
Tim Donohue
9a7277065f Add accessibility tests (and minor fixes) for Edit Collection's Content Source tab and Item Mapper tab
(cherry picked from commit 00cb2f9e8a)
2024-01-17 19:38:04 +00:00
Tim Donohue
16a10548b8 Add Edit Collection accessibility Testing
(cherry picked from commit 9894d315a0)
2024-01-17 19:38:04 +00:00
Tim Donohue
4187847ad0 Add Edit Community accessibility tests & minor cleanup
(cherry picked from commit 6c76d8c1d7)
2024-01-17 19:38:04 +00:00
Tim Donohue
77bb3ed952 Fix circular dependency issue by using Cypres env variables directly instead of global constants
(cherry picked from commit e47593b303)
2024-01-17 19:38:04 +00:00
Tim Donohue
023f8a6900 Improve accessibility testing on Submission page
(cherry picked from commit 8d61fa6ac3)
2024-01-17 19:38:04 +00:00
Tim Donohue
0be04c754a Refactor e2e test infrastruction to allow easier way to lookup REST API info and generate CSRF tokens
(cherry picked from commit a581219df5)
2024-01-17 19:38:04 +00:00
Tim Donohue
07b95c2634 Fix ARIA labels and tabindex on date picker in submission form. Tabindex is unnecessary & throws accessibility errors from AXE tools
(cherry picked from commit 5dad8bec39)
2024-01-17 19:38:04 +00:00
Tim Donohue
e9efb50e02 Fix ARIA labels on submission form relationship button and dynamic dropdowns
(cherry picked from commit aeea1cd592)
2024-01-17 19:38:04 +00:00
Tim Donohue
868229460f Add new e2e accessibility tests & update some existing ones
(cherry picked from commit 53d521a87e)
2024-01-17 19:38:04 +00:00
Tim Donohue
9d593b657b Merge pull request #2743 from tdonohue/port_2587_to_dspace-7_x
[Port to dspace-7_x]  Deque Analysis Color Contrast fixes #2587
2024-01-16 16:48:13 -06:00
Maciej Kleban
0b43109a29 Replace hard-coded colors with bootstrap variants
* Replace custom variables with Bootstrap variants
* Replace custom button colors with Bootstrap variants
* Remove custom colors and replace them with bootstrap variants
* Fix checkbox offset styles
---------
Co-authored-by: Maciej Kleban <maciej.kleban@pcgacademia.pl>
2024-01-16 16:15:35 -06:00
Dawnkai
07a2f3bbbb Deque Analysis Color Contrast fixes 2024-01-16 16:04:27 -06:00
Alan Orth
fc28693b12 Merge pull request #2740 from DSpace/backport-2662-to-dspace-7_x
[Port dspace-7_x] Fixed ePerson link on edit group page
2024-01-12 08:51:21 +03:00
Alexandre Vryghem
77d2086796 Fixed ePerson link on edit group page
(cherry picked from commit 430ee3846a)
2024-01-12 05:25:16 +00:00
Tim Donohue
8dd0db094e Merge pull request #2670 from atmire/fix-api-undefined-issue-7.6.2-next
Ensure HALEndpointService doesn't use stale responses
2024-01-08 10:16:57 -06:00
Tim Donohue
33a5dd3e7f Merge pull request #2713 from DSpace/backport-2707-to-dspace-7_x
[Port dspace-7_x] Update fi.json5
2023-12-14 11:22:36 -06:00
reetagithub
9a9311e02a Update fi.json5
Corrected a typo  in search.view.switch.show-grid

(cherry picked from commit 2339a559e9)
2023-12-14 16:16:00 +00:00
Tim Donohue
2fec884072 Merge pull request #2696 from DSpace/backport-2683-to-dspace-7_x
[Port dspace-7_x] Fix accessibility issues
2023-12-06 10:30:55 -06:00
Alexandre Vryghem
4a0bf8a7af Fixed Edit Item Bitstream showing empty buttons when css is turned off & replaced authentication dropdown menu link with button
- Also fixed box-shadows not working the same way for all footer buttons/links

(cherry picked from commit 4545b5354d)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
53f5ceabee Fixed Edit Item Bitstream showing empty buttons when css is turned off
(cherry picked from commit ff6dff72d8)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
8f8a3acba7 Fixed DSONameService getName & getHitHighlights returning empty string for OrgUnits without organization.legalName field
(cherry picked from commit e96d3cd2e9)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
dbb7917058 Fixed accessibility issues on search page
(cherry picked from commit 9a5ad0bc9f)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
7ec57988b8 Fixed accessibility issues about header ordering
(cherry picked from commit 5566c99625)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
3300f72621 Fixed browse by vocabulary treeview accessibility issues
(cherry picked from commit 30fe424cc5)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
77f52df047 Fixed item page accessibility issues
(cherry picked from commit 8ba17c991e)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
79ce4e9ec2 Fix collection mapper accessibility issues
- Added missing aria-labels to input checkboxes
- Fixed multiple tab related accessibility issues

(cherry picked from commit 1db83ba3c5)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
73cffe990a Fix item mapper accessibility issues
- Added missing aria-labels to input checkboxes
- Fixed role="tablist" not having direct role="tab" by adding role="presentation" on the li elements

(cherry picked from commit 5b21d14583)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
b94f0a9b69 Fix resource policy accessibility issues
(cherry picked from commit 09bf8af03c)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
5d5582e2d2 Fix Bitstream format accessibility issues
- Added missing aria-labels to input checkboxes
- Fixed minor css alignment

(cherry picked from commit 57cc34cdd1)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
5e52233cf3 Fix Process detail accessibility issues
- Made Process Output keyboard accessible

(cherry picked from commit 120835cfcd)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
9ed13f31ff Fix Create a new process page accessibility issues
- Added missing aria-label to delete buttons
- Moved hardcoded translation to translation files
- Fix color contrast issues on buttons
- Fix minor alignment issues
- Added missing aria labels to input and select elements

(cherry picked from commit 52c0977489)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
bc1ed9a96f Fix Processes Overview accessibility issues
- Added missing aria-label to delete button

(cherry picked from commit 4ea487cc0d)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
57ebe88994 Fix System-wide Alert accessibility issues
- Added missing aria-label to buttons

(cherry picked from commit 3bdfc386e9)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
96b3423ed1 Fix Health Panel usability issues
- Fixed aria-controls on HealthComponentComponent, HealthInfoComponent, HealthInfoComponentComponent, HealthPanelComponent pointing to non-existing id
- Fixed the tabs not having the role tab on its tabs
- Fixed aria-expanded being set to true when collapsed and backwards for HealthPanelComponent & HealthInfoComponent
- Fixed role="tablist" not having direct role="tab" by adding role="presentation" on the li elements
- Fixed minor alignment issues

(cherry picked from commit 3628c84844)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
fc7aa27706 Fix Bulk Access Management usability issues
- Added aria-labels to trash & select bitstream icon
- Added aria-labels ui-switch components (had to upgrade ngx-ui-switch to 14.1.0)
- Fixed aria-controls pointing to non-existing ids
- Fixed bulk-access-browse not having the tab role on it's tabs
- Fixed role="tablist" not having direct role="tab" by adding role="presentation" on the li elements
- Fixed aria-expanded being set to true when collapsed and backwards for BulkAccessBrowseComponent & BulkAccessSettingsComponent

(cherry picked from commit c300123b78)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
1c4be7d1fe Fixed duplicate search-navbar-container id & renabled accessibility tests for header
(cherry picked from commit cc71d60ebb)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
9f468c2c11 Added aria-labels & fixed aria-controls on myDSpace page
- Added aria-label to the new submission buttons (for repositories with more than 1 entity type)
- Fixed aria-controls pointing to non-existing ids

(cherry picked from commit 8a7bdfe18f)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
69100f0a24 Replaced uncrawlable links with buttons
(cherry picked from commit 9ab30231aa)
2023-12-06 16:03:23 +00:00
Alexandre Vryghem
d32303bf47 Added missing aria labels to edit bitstream tab
(cherry picked from commit e229be44d6)
2023-12-06 16:03:23 +00:00
Art Lowel
91789a698f Merge branch 'fix-api-undefined-issue-7.6' into fix-api-undefined-issue-7.6.2-next 2023-12-06 11:30:28 +01:00
Art Lowel
1d4a36e3b8 Merge remote-tracking branch 'upstream/dspace-7_x' into fix-api-undefined-issue-7.6.2-next 2023-12-06 11:30:23 +01:00
Art Lowel
790e717199 add ResponsePendingStale state 2023-12-06 11:24:35 +01:00
Art Lowel
c8ac260b78 also skip loading hal requests 2023-12-06 11:24:29 +01:00
max.nuding
9f95eb452c enable type-bind for checkbox inputs during submission 2023-12-06 10:15:31 +01:00
Tim Donohue
970c449b30 Merge pull request #2685 from DSpace/backport-2684-to-dspace-7_x
[Port dspace-7_x] Updated bitstream read description in en.json5
2023-12-04 15:57:39 -06:00
Michael Spalti
3b568f7d32 Updated bitstream read description in en.json5
(cherry picked from commit 2592f87356)
2023-12-04 21:08:29 +00:00
Tim Donohue
5de6cedf5e Merge pull request #2677 from DSpace/backport-2673-to-dspace-7_x
[Port dspace-7_x] Fix for loading thumbnail images for Items when MediaViewer enabled
2023-11-30 15:08:01 -06:00
Michael Spalti
9c69a77d43 Fix for thumbnail images in items.
Revert changes in html template

Revert changes in html template

Revert changes in html template

(cherry picked from commit 88c39e8b26)
2023-11-30 20:14:36 +00:00
Art Lowel
db29263eb1 Merge branch 'fix-api-undefined-issue-7.6' into fix-api-undefined-issue-7.6.2-next 2023-11-29 16:58:43 +01:00
Tim Donohue
5b59d37e2f Merge pull request #2667 from tdonohue/port_2666_to_dspace-7_x
[Port dspace-7_x] [Docker] Refactor to simply use the reusable-docker-build.yml from DSpace/DSpace.
2023-11-29 09:38:51 -06:00
Art Lowel
38752d9d71 ensure HALEndpointService doesn't use stale requests 2023-11-29 14:25:36 +01:00
Tim Donohue
92d25dd2a8 Remove unused env variables 2023-11-28 16:52:15 -06:00
Tim Donohue
65fff9361c Use the script from the dspace-7_x branch 2023-11-28 16:13:56 -06:00
Tim Donohue
b83a8421a3 Refactor to simply use the reusable-docker-build.yml from DSpace/DSpace. 2023-11-28 16:12:21 -06:00
Alan Orth
714652ebb0 Merge pull request #2659 from DSpace/backport-2652-to-dspace-7_x
[Port dspace-7_x] Deselect all metadata fields when switching between metadata schemas in registry
2023-11-23 16:27:52 +03:00
Alexandre Vryghem
c3f424dae4 108587: Deselect all fields on component destruction
(cherry picked from commit ecca8286b5)
2023-11-23 04:57:35 +00:00
Tim Donohue
6758b4c4c7 Merge pull request #2655 from tdonohue/port_docker_gh_updates
[Port dspace-7_x] Port recent GitHub Action changes
2023-11-22 12:20:58 -06:00
Tim Donohue
bd78acd559 Redeploy demo/sandbox more quickly by only waiting for AMD64 image 2023-11-22 11:42:11 -06:00
Tim Donohue
62ccd18345 Trigger redeploy of demo/sandbox from GitHub Actions after DockerHub image updated. 2023-11-21 13:55:17 -06:00
Tim Donohue
5ab87ec6c3 Update GH actions to latest versions. Fix bug in codecov to ensure it retries on error. 2023-11-21 13:55:10 -06:00
Tim Donohue
63e792990f Fix directory structure for -dist digests 2023-11-21 13:54:42 -06:00
Tim Donohue
526da8cddf Fix bug in Docker manifest. Each build must use a separate artifact to store digests. Other minor cleanup & comments added. 2023-11-21 13:54:30 -06:00
Thomas Misilo
02eb618c5f Setup the Docker GH Action for Matrix Building
This change enables building of the amd64 and arm64 images simultaneously.

Once both images finish, the manifest is sent to Docker Hub, allowing for a single image that has both the amd64/arm64 images.
2023-11-21 13:54:03 -06:00
Alexandre Vryghem
45e8977db7 Fixed pagination issues on item mapper 2023-11-15 23:31:54 +01:00
Tim Donohue
5a839c2906 Update version tag for development of next release 2023-11-15 14:37:06 -06:00
Tim Donohue
d8ed267e5f Update version tag for release 2023-11-15 14:33:16 -06:00
max.nuding
ab23613b79 Remove unnecessary import 2023-11-15 13:42:53 +01:00
max.nuding
c93a64db83 Fix tests to include translationservice dependency 2023-11-15 13:28:15 +01:00
max.nuding
038e31ccd1 Submission form now displays custom messages for regex validated fields if they exist 2023-11-15 10:51:53 +01:00
Tim Donohue
fdcaeb592c Merge pull request #2639 from tdonohue/port_2610_to_dspace-7_x
[Port dspace-7_x] Fixed menu not updating when a new sub section is added after rendering has already completed
2023-11-13 17:00:20 -06:00
Tim Donohue
8872fd3340 Merge pull request #2638 from DSpace/backport-2633-to-dspace-7_x
[Port dspace-7_x] Edit-item view: random order of buttons in status tab
2023-11-13 16:51:21 -06:00
Tim Donohue
959d592394 Merge pull request #2637 from DSpace/backport-2632-to-dspace-7_x
[Port dspace-7_x] Fixes "some item edit pages are accessible by anonymous users"
2023-11-13 16:40:19 -06:00
Alexandre Vryghem
c98e8f6504 107902: Created test case for 2f26e686cc 2023-11-13 16:36:05 -06:00
Kristof De Langhe
e293f3db52 107685: menu-component re-render section on store update 2023-11-13 16:35:16 -06:00
Vlad Nouski
651b6a7d76 refactor: code
(cherry picked from commit fbbbc18844)
2023-11-13 22:05:45 +00:00
Vlad Nouski
dd554590b1 fix: random order of buttons in status tab
(cherry picked from commit 35f8b55f58)
2023-11-13 22:05:45 +00:00
Tim Donohue
0a3502e9cc Merge pull request #2636 from DSpace/backport-2562-to-dspace-7_x
[Port dspace-7_x] Fix match theme by handle with canonical prefix https://hdl.handle.net/ not working
2023-11-13 16:00:02 -06:00
Vlad Nouski
94866cab45 [DURACOM-202] refactor: code
(cherry picked from commit 6f64db1645)
2023-11-13 20:34:42 +00:00
Vlad Nouski
a5a59dcf8b [DURACOM-202] refactor: code
(cherry picked from commit b6d515ff09)
2023-11-13 20:34:42 +00:00
Vlad Nouski
8f9a358afb [DURACOM-202] feature: item edit pages are accessible by administrator
(cherry picked from commit ccf1cc4547)
2023-11-13 20:34:42 +00:00
Alexandre Vryghem
0cd72e4917 107671: Fixed theme matching by handle not working in production mode
(cherry picked from commit 7529ed8b35)
2023-11-13 20:21:10 +00:00
Alexandre Vryghem
1222ed45ca 107671: Fixed bug where config property would still sometimes be undefined whey calling the ngOnDestroy in the ThemedComponent
(cherry picked from commit 4e54cca600)
2023-11-13 20:21:10 +00:00
Alexandre Vryghem
27f3fc310f 107671: Split Theme model & ThemeConfig classes in separate files to prevent circular dependencies
(cherry picked from commit da8880e5ba)
2023-11-13 20:21:10 +00:00
Alexandre Vryghem
d3fdfebde1 107671: Fix handle theme not working with canonical prefix https://hdl.handle.net/
(cherry picked from commit a7faf7d449)
2023-11-13 20:21:10 +00:00
Tim Donohue
626cc30738 Merge pull request #2635 from DSpace/backport-2579-to-dspace-7_x
[Port dspace-7_x] adding new access-status-list-element-badge css classes
2023-11-13 13:19:23 -06:00
Tim Donohue
64364c9ddb Merge pull request #2634 from DSpace/backport-2630-to-dspace-7_x
[Port dspace-7_x] Fix handle redirect not working with custom nameSpace
2023-11-13 13:18:52 -06:00
Paulo Graça
a276f415a8 adding ngOnDestroy for dealing with unsubscribe
(cherry picked from commit 75b788d05b)
2023-11-13 17:30:28 +00:00
Paulo Graça
59c4d59e45 remove replaceAll and use an object property
(cherry picked from commit c7eae9242a)
2023-11-13 17:30:28 +00:00
Paulo Graça
a12488c827 new accessStatusClass atribute
(cherry picked from commit 6378dbec4a)
2023-11-13 17:30:28 +00:00
Paulo Graça
ff03243298 Create new access-status-badge.component.scss
(cherry picked from commit 3bf2eb1997)
2023-11-13 17:30:28 +00:00
Paulo Graça
116bfbded1 adding new access-status-list-element-badge css classes
(cherry picked from commit e847e4ef51)
2023-11-13 17:30:28 +00:00
Alexandre Vryghem
68cdd120c9 Fix handle redirect not working with custom nameSpace
(cherry picked from commit b894dce3b0)
2023-11-13 17:22:53 +00:00
Tim Donohue
7d5c4560cd Merge pull request #2629 from tdonohue/port_2620_to_dspace-7_x
[Port dspace-7_x] Fix for repeatable date field labels
2023-11-10 17:20:40 -06:00
Tim Donohue
755e89dffa Merge pull request #2628 from DSpace/backport-2625-to-dspace-7_x
[Port dspace-7_x] Bump axios from 0.27.2 to 1.6.0
2023-11-10 17:15:22 -06:00
lotte
8458d589b2 Fixed test 2023-11-10 16:40:31 -06:00
lotte
30ce8440e1 108045: Fix for repeatable date field labels 2023-11-10 16:39:38 -06:00
dependabot[bot]
c8d98ec0b1 Bump axios from 0.27.2 to 1.6.0
Bumps [axios](https://github.com/axios/axios) from 0.27.2 to 1.6.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.27.2...v1.6.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit ef9f31d3c6)
2023-11-10 22:23:44 +00:00
Tim Donohue
6975fd15d5 Merge pull request #2627 from DSpace/backport-2545-to-dspace-7_x
[Port dspace-7_x] Fix "Edit Group" page always requests all member Subgroups & EPersons
2023-11-10 15:14:27 -06:00
Tim Donohue
d305e6096a Address feedback. Run empty search on init. Reorder sections to list current members before add members (for both eperson and groups)
(cherry picked from commit 9117ac005f)
2023-11-10 19:03:47 +00:00
Tim Donohue
37ae09acd1 Remove seemingly unnecessary page reload after new search.
(cherry picked from commit d163db13f2)
2023-11-10 19:03:47 +00:00
Tim Donohue
8cc36d7056 Refactor subgroups-list component's "search()" to act same as member-list component's "search()". Avoids reloading the page as frequently.
(cherry picked from commit 2eb1a17e4e)
2023-11-10 19:03:47 +00:00
Tim Donohue
4a1f2a1b75 Refactor members-list and subgroups-list components to use new isNotMemberOf endpoints (via services)
(cherry picked from commit 8a10888d2a)
2023-11-10 19:03:47 +00:00
Tim Donohue
1f1dc59f8b Fix subgroups-list specs so they align with new members-list specs
(cherry picked from commit 64f968b246)
2023-11-10 19:03:47 +00:00
Tim Donohue
f0b4239df9 Also remove unnecessary EpersonDtoModel from extending ReviewersListComponent. Remove "memberOfGroup" from EpersonDtoModel as it is no longer used
(cherry picked from commit b598f1b5ca)
2023-11-10 19:03:47 +00:00
Tim Donohue
753a31f7f4 Remove unnecessary EpersonDtoModel. Rework code and tests to use EPerson instead.
(cherry picked from commit bffae54b10)
2023-11-10 19:03:47 +00:00
Tim Donohue
ac6a7be7aa Remove "isMemberOfGroup()" from members-list component.
(cherry picked from commit 43d37196fb)
2023-11-10 19:03:47 +00:00
Tim Donohue
c02cfff8da Fix bug where linked Community/Collection info was sometimes listed many times in a row
(cherry picked from commit 229236634a)
2023-11-10 19:03:47 +00:00
Tim Donohue
d7ccce1f8f Remove isSubgroupOfGroup() functionality as it loads every subgroup at once. Bad peformance for large groups
(cherry picked from commit 97479a2945)
2023-11-10 19:03:47 +00:00
Tim Donohue
139446118b Limit getMembers() and getSubgroups() to only fetching one object. These lists are only used to find the size of each
(cherry picked from commit 0da7c15f2e)
2023-11-10 19:03:47 +00:00
Tim Donohue
787feae631 Merge pull request #2626 from tdonohue/port_2607_to_dspace-7_x
[Port dspace-7_x] Added skip to main content button
2023-11-10 11:43:15 -06:00
Alexandre Vryghem
e545c42aae Added skip to main content button 2023-11-10 10:59:57 -06:00
Tim Donohue
d166b5e37a Merge pull request #2623 from DSpace/backport-2611-to-dspace-7_x
[Port dspace-7_x] Communities & Collections tree browser updates - Replaced #2597
2023-11-10 09:55:38 -06:00
Tim Donohue
61ded72183 Merge pull request #2616 from DSpace/backport-2574-to-dspace-7_x
[Port dspace-7_x] Support type-bind of elements based on repeatable list type-bound element (CHECKBOX_GROUP)
2023-11-10 09:15:59 -06:00
William Welling
b6d8c7d18e Filter expanded nodes by id
Co-Authored-By: Art Lowel <1567693+artlowel@users.noreply.github.com>
(cherry picked from commit dc2ef989e6)
2023-11-10 15:14:28 +00:00
William Welling
1d0ca04992 Update condition to render show more node
`loadingNode` ends up being the current `node` after clicking it preventing it from rendering when more pages available.

Update community list component spec

Make the show more flat node id unique

The nodes with same id are conflicting when added to the tree. Clicking on the second with same id places the show more button under the wrong branch and expands the wrong page.

(cherry picked from commit 11d3771e72)
2023-11-10 15:14:28 +00:00
Tim Donohue
34b91a7dea Merge pull request #2617 from DSpace/backport-2594-to-dspace-7_x
[Port dspace-7_x] Media viewer controls rendered behind DSpace header
2023-11-10 06:49:12 -06:00
Tim Donohue
203dcbebda Merge pull request #2612 from DSpace/backport-2596-to-dspace-7_x
[Port dspace-7_x] Fix cache issue when depositing a submission
2023-11-09 16:53:28 -06:00
Davide Negretti
c6ade09e4a [DURACOM-180] Prevent header from covering media viewer controls (base theme)
(cherry picked from commit c042cd8d11)
2023-11-09 22:44:28 +00:00
Davide Negretti
5f46b638e4 [DURACOM-180] Prevent header from covering media viewer controls (dspace theme)
(cherry picked from commit 0208a78437)
2023-11-09 22:44:28 +00:00
Davide Negretti
0d0c2dac17 [DURACOM-195] Simplify vertical spacing in header and breadcrumbs
(cherry picked from commit a3e6d9b09a)
2023-11-09 22:44:28 +00:00
Andreas Mahnke
bc21085398 Support type-bind of elements based on repeatable list type-bound element (CHECKBOX_GROUP)
(cherry picked from commit 09aaa46875)
2023-11-09 22:04:12 +00:00
Tim Donohue
137a83e7f1 Merge pull request #2614 from DSpace/backport-2595-to-dspace-7_x
[Port dspace-7_x] Add UI nameSpace context path to Mirador viewer path
2023-11-09 13:40:03 -06:00
Alan Orth
31ee580047 Merge pull request #2615 from DSpace/backport-2602-to-dspace-7_x
[Port dspace-7_x] Support for freetext values in submission for vocabulary controlled `onebox` and `tag` types
2023-11-09 22:39:53 +03:00
Jens Vannerum
e815b1d938 108055: add user input to tag list
(cherry picked from commit aac58e612d)
2023-11-09 18:56:37 +00:00
Jens Vannerum
a758848146 108055: fix issue 8686: unable to enter freetext values in the submission form for vocabulary
(cherry picked from commit 0dcf6cb885)
2023-11-09 18:56:37 +00:00
William Welling
fba30781de Add UI nameSpace context path to Mirador viewer path
(cherry picked from commit 3228c457a3)
2023-11-09 17:45:22 +00:00
Giuseppe Digilio
4b4c1dc08a [DURACOM-197] Fix cache issue when depositing a submission
(cherry picked from commit f992ff6671)
2023-11-09 17:31:52 +00:00
Tim Donohue
5eb62e22eb Merge pull request #2605 from DSpace/backport-2364-to-dspace-7_x
[Port dspace-7_x] Utility gap-* classes for flexbox
2023-11-08 16:57:59 -06:00
Tim Donohue
fbe4732450 Merge pull request #2590 from marcoaureliocardoso/dspace-7_x
fix(pt-BR.json5): fix and update the language file
2023-11-08 16:49:38 -06:00
Davide Negretti
c5f22ab959 [DURACOM-177] Use gap-* classes on navbar buttons
(cherry picked from commit a35629536e)
2023-11-08 22:15:29 +00:00
Davide Negretti
75e45cc8c2 [DURACOM-177] gap-* classes
(cherry picked from commit 930a381e4a)
2023-11-08 22:15:29 +00:00
Tim Donohue
5bb451a649 Merge pull request #2604 from DSpace/backport-2603-to-dspace-7_x
[Port dspace-7_x] Fix e2e tests by running in production mode & using a user-agent for statistics
2023-11-08 13:13:21 -06:00
Tim Donohue
042c0f06f1 Specify user agent to avoid being detected as a "bot" by backend
(cherry picked from commit 72cda41731)
2023-11-08 18:25:13 +00:00
Tim Donohue
f3f87dc928 Ensure e2e tests run in production mode
(cherry picked from commit 7dcaae8465)
2023-11-08 18:25:13 +00:00
Tim Donohue
4a10d37d0d Merge pull request #2598 from DSpace/backport-2593-to-dspace-7_x
[Port dspace-7_x] Fix "Submission input type date doesn't work properly once a value has been set"
2023-11-03 15:56:55 -05:00
Alisa Ismailati
c99487babc [DURACOM-194] fixed year input value on input type date
(cherry picked from commit c412c1fa13)
2023-11-03 20:15:37 +00:00
Alisa Ismailati
e54723aa85 Merged in DSC-106 (pull request #643)
[DSC-106] Date input usable via keyboard using tab

Approved-by: Vincenzo Mecca
(cherry picked from commit 543b4ad576)
2023-11-03 20:15:37 +00:00
Marco Aurelio Cardoso
8b48a0b118 fix(pt-BR.json5): fix and update the language file and previous errors
Fix and update the pt-BR language file and the previous errors
2023-10-29 07:18:04 -03:00
Marco Aurelio Cardoso
e1494c0518 fix(pt-BR.json5): fix and update the language file
Fix and update the pt-BR language file
2023-10-29 06:49:20 -03:00
Tim Donohue
701c6e36b0 Merge pull request #2589 from DSpace/backport-2580-to-dspace-7_x
[Port dspace-7_x]   Check cssRules before css variables are read from stylesheet (again)
2023-10-27 13:51:12 -05:00
Gantner, Florian Klaus
c6b66b62e3 more error-prone check of cssRules existence before css variables are get from stylesheet
check the existence off cssRules property before the variables are readed from this stylesheet
https://github.com/DSpace/dspace-angular/issues/2450

(cherry picked from commit 4dd334f2e7)
2023-10-27 15:28:08 +00:00
Tim Donohue
6a182c32e1 Merge pull request #2585 from tdonohue/port_2442_to_7x
[Port dspace-7_x] New themed components & minor CSS fixes
2023-10-26 16:56:03 -05:00
Alexandre Vryghem
b8079a350c New themed components & minor CSS fixes (#2442)
* 100839: Created themeable BrowseByComponent

* 100839: Added themed BrowseByComponent to custom theme

* 100839: Added themed BrowseEntryListElementComponent to custom theme

* Added PersonComponent to custom theme

* Themed LogInComponent

* Fix focus on navbar using different color

* Fix ccLicense checkbox margin

* Fix long search facets name not displaying correctly

* Removed RecentItemListComponent's unnecessary float causing alignment issues when adding components underneath it

* Themed RegisterEmailFormComponent
2023-10-26 16:07:24 -05:00
Tim Donohue
3e9f3aba92 Merge pull request #2584 from DSpace/backport-2441-to-dspace-7_x
[Port dspace-7_x] Minor css variables fixes for header & navbar
2023-10-26 15:02:26 -05:00
Alexandre Vryghem
e548ebcb5a Added new variables for the expandable navbar section
(cherry picked from commit 2ca2a3881f)
2023-10-26 19:03:08 +00:00
Alexandre Vryghem
166444fc50 Fixed breadcrumb padding using incorrect syntax
(cherry picked from commit 6c48238fa2)
2023-10-26 19:03:08 +00:00
Alexandre Vryghem
a40e26985d Fixed header bg color not being set in default (no) theme
(cherry picked from commit 14b1ce5e50)
2023-10-26 19:03:08 +00:00
Alexandre Vryghem
72dcfddff1 Added support for changing the color of the navbar
(cherry picked from commit f6649e1c38)
2023-10-26 19:03:08 +00:00
Tim Donohue
b9f60fa627 Merge pull request #2583 from DSpace/backport-2506-to-dspace-7_x
[Port dspace-7_x] allow insertion of multi-line scope notes in MD field registry
2023-10-26 13:25:32 -05:00
Tim Donohue
97b22c63f8 Merge pull request #2391 from alexandrevryghem/split-eperson-administration-page_contribute-maintenance-7.6
Created separate ePerson pages for create/edit
2023-10-26 13:23:15 -05:00
Sascha Szott
163661a956 allow to insert multi-line scope notes in MD field registry
(cherry picked from commit 5bc5dd859e)
2023-10-26 16:56:44 +00:00
Tim Donohue
f8671e7d4b Merge pull request #2581 from DSpace/backport-2542-to-dspace-7_x
[Port dspace-7_x] Fix i18n labels and alignment in vocabulary-treeview
2023-10-26 11:24:11 -05:00
Tim Donohue
673f81759e Merge pull request #2464 from alexandrevryghem/i18n-cache-busting_contribute-7.6
i18n production improvements
2023-10-26 11:22:36 -05:00
Davide Negretti
e10a08ecfa [DURACOM-190] Fix alignment in vocabulary-treeview
(cherry picked from commit feb2b2be53)
2023-10-26 15:35:38 +00:00
Davide Negretti
00eb24c39d [DURACOM-190] Fix i18n labels in vocabulary-treeview
(cherry picked from commit b321d6f727)
2023-10-26 15:35:38 +00:00
Tim Donohue
3b6dd66680 Merge pull request #2458 from atmire/backport-2423-to-dspace-7_x
[Port to dspace-7_x] Fix inherit policies at item move (Angular)
2023-10-25 16:38:44 -05:00
Tim Donohue
d6951dc8e3 Merge pull request #2576 from tdonohue/port_2362_to_7x
[Port dspace-7_x] Making user menu component themeable
2023-10-25 16:06:50 -05:00
Tim Donohue
1d2cdf75e6 Merge pull request #2575 from tdonohue/port_2530_to_7x
[Port to dspace-7_x] Fix display LogInComponent turning blank when entering wrong username/password combination
2023-10-25 15:18:20 -05:00
Eike Martin Löhden
4945460382 Removed default value from inExpandableNavbar. 2023-10-25 15:16:01 -05:00
Eike Martin Löhden
2ec90b8273 Included user-menu component in custom theme. 2023-10-25 15:15:54 -05:00
Eike Martin Löhden
a4aecce865 Replaced tags for ds-user-menu. 2023-10-25 15:15:48 -05:00
Eike Martin Löhden
de826634c8 Corrected missing semicolon. 2023-10-25 15:15:41 -05:00
Eike Martin Löhden
d04d9fd250 Added themed-user-menu component. 2023-10-25 15:15:25 -05:00
Alexandre Vryghem
86657108dd Merge branch 'fix-display-order-authentication-methods_contribute-7.4' into fix-display-order-authentication-methods_contribute-7.6
# Conflicts:
#	src/app/shared/log-in/log-in.component.html
#	src/app/shared/log-in/log-in.component.ts
2023-10-25 14:27:09 -05:00
Alan Orth
73f21f21e7 Merge pull request #2571 from DSpace/backport-2553-to-dspace-7_x
[Port dspace-7_x] Added Serbian Cyrillic translation and corrected Serbian Latin translаtion
2023-10-24 22:47:50 +03:00
imilos
5ab69af71e Added Serbian cyrilic translation and corrected Serbian latin translation.
(cherry picked from commit aa9e12dcfe)
2023-10-24 18:02:41 +00:00
imilos
884e113168 Added Serbian cyrilic translation and corrected Serbian latin translation.
(cherry picked from commit ad12e5a7f2)
2023-10-24 18:02:41 +00:00
Alan Orth
a92aa05049 Merge pull request #2566 from DSpace/backport-2527-to-dspace-7_x
[Port dspace-7_x] Minor header button improvements
2023-10-23 21:57:44 +03:00
Alexandre Vryghem
d7dba4bfcf Fixed invalid html structure the ExpandableNavbarSectionComponent had an ul tag containing non-li tags
(cherry picked from commit fa56d5dfb7)
2023-10-23 17:50:43 +00:00
Alexandre Vryghem
e82a1ebedb Applied same gap between header icons in the dspace theme and made the search field non-focusable when collapsed
(cherry picked from commit 58d31dd73f)
2023-10-23 17:50:43 +00:00
Alexandre Vryghem
d7f1d37e41 Themed LangSwitchComponent
(cherry picked from commit f9b4460e70)
2023-10-23 17:50:43 +00:00
Alexandre Vryghem
d6de6fee6c Use gap instead of individual paddings for header icons
(cherry picked from commit 9f2a1d048b)
2023-10-23 17:50:43 +00:00
Tim Donohue
7dd9156375 Merge pull request #2564 from DSpace/backport-2561-to-dspace-7_x
[Port dspace-7_x] Fix RequestService test sometimes failing in CICD
2023-10-23 10:00:35 -05:00
Alexandre Vryghem
8428b0549b Fix RequestService test failing because of different lastUpdated time
(cherry picked from commit fb315335c9)
2023-10-23 13:56:07 +00:00
Tim Donohue
33c2c98757 Merge pull request #2560 from DSpace/backport-2439-to-dspace-7_x
[Port dspace-7_x] Fix Request-a-Copy grant form optional message not optional in form validation
2023-10-20 15:14:08 -05:00
Agustina Martinez
e9b70e34d5 Update email-request-copy.component.html
Message is optional: remove req in [disabled]

(cherry picked from commit 94c756d52d)
2023-10-20 20:13:00 +00:00
Tim Donohue
c3ee2ca6c1 Merge pull request #2559 from DSpace/backport-2558-to-dspace-7_x
[Port dspace-7_x] Revert 2454 "Check cssRules before css variables are read from stylesheet"
2023-10-20 12:07:43 -05:00
Tim Donohue
2834ac33a4 Revert "Check cssRules before css variables are read from stylesheet (#2454)"
This reverts commit fa79c358c0.

(cherry picked from commit 6f73b65d53)
2023-10-20 17:07:13 +00:00
Tim Donohue
1eeed36036 Merge pull request #2552 from DSpace/backport-2549-to-dspace-7_x
[Port dspace-7_x] Bump @babel/traverse from 7.21.4 to 7.23.2
2023-10-20 09:57:08 -05:00
dependabot[bot]
bc0629e004 Bump @babel/traverse from 7.21.4 to 7.23.2
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.21.4 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 97f7a5e82a)
2023-10-19 21:45:57 +00:00
Alexandre Vryghem
d473fcdf16 Merge branch 'split-eperson-administration-page_contribute-7.6' into split-eperson-administration-page_contribute-maintenance-7.6 2023-10-19 23:01:48 +02:00
Alexandre Vryghem
2e571767ea 107664: Normalized ePerson & group edit url and moved error message to translation file 2023-10-19 23:01:24 +02:00
Tim Donohue
c25b80abdd Merge pull request #2504 from atmire/Angular-SRR-menu-issues
Fix Angular SSR menu issues
2023-10-19 10:19:27 -05:00
Alan Orth
b5f942b71d Merge pull request #2547 from DSpace/backport-2454-to-dspace-7_x
[Port dspace-7_x] Check cssRules before css variables are read from stylesheet
2023-10-17 22:50:14 +03:00
Gantner, Florian Klaus
21dcef0a42 checkstyle remove unused extra lines
(cherry picked from commit 5f8a9dea34)
2023-10-17 19:17:51 +00:00
Gantner, Florian Klaus
c4a60abd65 check cssRules existence before css variables are get from stylesheet
(cherry picked from commit 367cda2de0)
2023-10-17 19:17:51 +00:00
Alan Orth
fd850164f5 Merge pull request #2546 from DSpace/backport-2534-to-dspace-7_x
[Port dspace-7_x] Bump postcss from 8.4.23 to 8.4.31
2023-10-16 14:20:07 +03:00
dependabot[bot]
8f881dbb52 Bump postcss from 8.4.23 to 8.4.31
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.23 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.23...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 3c5079e9ce)
2023-10-16 07:36:28 +00:00
Alexandre Vryghem
a419956e2a Merge remote-tracking branch 'upstream/dspace-7_x' into split-eperson-administration-page_contribute-maintenance-7.6
# Conflicts:
#	src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html
2023-10-14 17:38:26 +02:00
Alan Orth
3cb23c18e7 Merge pull request #2536 from atmire/issue-2535_hide-add-more-button-submission-if-no-disabled-sections-7.x
Issue 2535 hide add more button submission if no disabled sections 7.x
2023-10-12 09:46:14 +03:00
Alan Orth
59be2ae907 Merge pull request #2543 from DSpace/backport-2531-to-dspace-7_x
[Port dspace-7_x] Fix browse by visual bug
2023-10-12 09:17:48 +03:00
Jens Vannerum
15d2880ca4 Fix browse by visual bug
(cherry picked from commit d0b4e15db4)
2023-10-11 19:04:20 +00:00
Pascal-Nicolas Becker
c03cd03274 Merge pull request #2541 from DSpace/backport-2537-to-dspace-7_x
[Port dspace-7_x] Translate community as 'Bereich' in de.json5
2023-10-11 05:33:28 +02:00
Janne Jensen
ec86bc12bd translate community as 'Bereich' in de.json5
(cherry picked from commit 0139670371)
2023-10-10 20:40:03 +00:00
Marie Verdonck
77dd72b6ef Merge branch 'issue-2535_hide-add-more-button-submission-if-no-disabled-sections-7.4' into issue-2535_hide-add-more-button-submission-if-no-disabled-sections-7.x 2023-10-04 17:30:45 +02:00
Marie Verdonck
709848ee25 Issue#2535: Hide add more button in submission if no disabled sections 2023-10-04 17:27:04 +02:00
Alan Orth
980e254d9a Merge pull request #2528 from DSpace/backport-2385-to-dspace-7_x
[Port dspace-7_x] Move subscription button to DSO edit menu
2023-09-27 10:56:27 +03:00
Yury Bondarenko
6d195f5ffa Update DSO edit menu resolver tests
- Abstract away the different "subsections" ~ DSO type (the tests should not care about this)
  Instead, retrieve sections of interest by ID & assert whether they're there & how they should look
- Test separately for Communities, Collections & Items
- Test newly added menu section

(cherry picked from commit 18b7a9c7de)
2023-09-27 07:02:24 +00:00
Yury Bondarenko
578a427f46 Move subscription button to DSO edit menu
(cherry picked from commit c9558167b2)
2023-09-27 07:02:24 +00:00
Alan Orth
506579cd23 Merge pull request #2510 from atmire/fix-mutliple-api-calls-on-route-change
Fix multiple api calls on route change
2023-09-27 08:12:47 +03:00
Alan Orth
19eec6ac31 Merge pull request #2526 from DSpace/backport-2525-to-dspace-7_x
[Port dspace-7_x] Fix mouse cursor on language dropdown menu
2023-09-26 22:09:17 +03:00
Davide Negretti
2aab4265a5 [DURACOM-185] Fix pointer on language dropdown menu
(cherry picked from commit 6b5708cffd)
2023-09-26 18:40:11 +00:00
Art Lowel
67a6f58865 Merge branch 'fix-mutliple-api-calls-on-route-change-7.6' into fix-mutliple-api-calls-on-route-change 2023-09-26 16:56:17 +02:00
Art Lowel
6a99185214 roll back unintended change to the responseMsToLive for RootDataservice 2023-09-26 16:56:07 +02:00
Art Lowel
d02c5397f8 Merge branch 'fix-mutliple-api-calls-on-route-change-7.6' into fix-mutliple-api-calls-on-route-change 2023-09-26 14:44:39 +02:00
Art Lowel
32fc28ec54 fix dev mode issue where retrieving the login options fails 2023-09-26 14:44:13 +02:00
DSpace Bot
77efe52f4a [Port dspace-7_x] Fix missing or wrong Italian translations (#2522)
[DURACOM-184] fix missing or wrong Italian translations

---------

Co-authored-by: Andrea Barbasso <´andrea.barbasso@4science.com´>
2023-09-26 11:04:49 +03:00
Alan Orth
83beb5474e Merge pull request #2521 from DSpace/backport-2473-to-dspace-7_x
[Port dspace-7_x] Fix some routes not using the correct baseHref
2023-09-26 10:51:51 +03:00
DSpace Bot
1c38d9259a [Port dspace-7_x] Serbian (Latin) translation (#2520)
* Serbian (latin) translation added.

---------

Co-authored-by: imilos <imilos@gmail.com>
2023-09-26 10:08:07 +03:00
Alexandre Vryghem
d6d5a2891c Fix routes not working with baseHref
(cherry picked from commit 18febff7a6)
2023-09-26 07:07:20 +00:00
Hrafn Malmquist
abd6c01a98 Merge pull request #2502 from alanorth/undo-text-capitalize
Don't capitalize metadata values in search
2023-09-26 00:19:07 +01:00
DSpace Bot
f77d01c01f [Port dspace-7_x] Update fi.json5 (#2516)
* Update fi.json5

Two last translations to the Finnish file.

(cherry picked from commit c3a908bccb)
2023-09-25 12:53:45 +03:00
Tim Donohue
fd3f1628ee Merge pull request #2514 from DSpace/backport-2505-to-dspace-7_x
[Port dspace-7_x] remove obsolete label element in metadata-schema.component.html
2023-09-22 14:53:04 -05:00
Sascha Szott
c7fe310d81 removed trailing whitespaces as suggested by reviewer
(cherry picked from commit 43f19e7d91)
2023-09-22 17:57:54 +00:00
Sascha Szott
742b2d920a remove obsolete label element in metadata-schema.component.html
(cherry picked from commit 6847c30e58)
2023-09-22 17:57:54 +00:00
Art Lowel
7a8e2206ae Merge branch 'fix-mutliple-api-calls-on-route-change-7.6' into fix-mutliple-api-calls-on-route-change 2023-09-22 12:01:24 +02:00
Art Lowel
8fbe8c16dc fix issue where invalidateRootCache didn't happen when the page first loaded 2023-09-22 12:00:59 +02:00
Art Lowel
0104f81d54 Merge branch 'fix-mutliple-api-calls-on-route-change-7.6' into fix-mutliple-api-calls-on-route-change 2023-09-22 10:48:46 +02:00
Art Lowel
5ad621b27e fix issue where more than one api call was made on every route change 2023-09-22 10:23:07 +02:00
Alan Orth
47029c0a78 src/app/shared/search: don't capitalize metadata values
Don't capitalize metadata values for display purposes.
2023-09-19 07:42:23 +03:00
Yana De Pauw
5a5f71a3d9 Merge remote-tracking branch 'upstream/dspace-7_x' into Angular-SRR-menu-issues 2023-09-18 16:49:36 +02:00
Yana De Pauw
3e8d180f1b 106974: Angular SSR menu issues 2023-09-18 16:43:32 +02:00
Tim Donohue
2d733732f6 Merge pull request #2498 from DSpace/backport-2451-to-dspace-7_x
[Port dspace-7_x] Graceful shutdown on SIGINT (e.g. from 'pm2 stop').
2023-09-14 12:29:23 -05:00
Mark H. Wood
d6cabd1d01 Document a modified method as required by PR guidelines.
(cherry picked from commit bf9b2b82e1)
2023-09-14 16:07:36 +00:00
Mark H. Wood
46e2f4e22c Properly await termination.
(cherry picked from commit 4449737aed)
2023-09-14 16:07:36 +00:00
Mark H. Wood
15c2af5fbf Graceful shutdown on SIGINT (e.g. from 'pm2 stop').
(cherry picked from commit 6709c3bb5f)
2023-09-14 16:07:36 +00:00
Tim Donohue
a37e0f29b7 Merge pull request #2497 from DSpace/backport-2493-to-dspace-7_x
[Port dspace-7_x] Correct text of help info on edit group page
2023-09-14 11:01:38 -05:00
Tim Donohue
b423b49cac Correct text of help info on edit group page
(cherry picked from commit 49247430e5)
2023-09-14 15:07:21 +00:00
Tim Donohue
bdf7414392 Merge pull request #2488 from alexandrevryghem/fix-display-order-authentication-methods_contribute-7.6
[Port dspace-7_x] Made it possible to reorder the login methods
2023-09-11 16:40:43 -05:00
Tim Donohue
459a43184a Merge pull request #2486 from DSpace/backport-2421-to-dspace-7_x
[Port dspace-7_x] 🚸remove thumbnail from file-upload section and show bitstream format …
2023-09-08 16:13:14 -05:00
Alexandre Vryghem
0905a53db5 Fix innerText still being undefined in ssr mode 2023-09-08 22:35:27 +02:00
Alan Orth
cd93c6eecd Merge pull request #2487 from DSpace/backport-2445-to-dspace-7_x
[Port dspace-7_x] Fix to Metadata Registry create new metadata schema doesn't appear without reload #1081
2023-09-08 23:29:50 +03:00
Hugo Dominguez
161d7e069b 🎨 revert format
(cherry picked from commit 3e5524de69)
2023-09-08 19:50:25 +00:00
Hugo Dominguez
e3ea2cb2b0 🐛 fix bug of caching when add new schema
(cherry picked from commit 9fb9e5848c)
2023-09-08 19:50:25 +00:00
Hugo Dominguez
8d295419c7 ♻️ refactor chain of observables to avoid async issues
(cherry picked from commit 2dc9fd44d7)
2023-09-08 19:50:25 +00:00
Tim Donohue
22538f30dc Update workspaceitem-section-upload-file.model.ts
Fix code comment

(cherry picked from commit 01c8a4d9c3)
2023-09-08 19:33:55 +00:00
Hugo Dominguez
5daf993451 🎨revert unnecessary format
(cherry picked from commit 13e4052c4d)
2023-09-08 19:33:55 +00:00
Hugo Dominguez
b7b3db5ba8 🚸remove thumbnail from file-upload section and show bitstream format and checksum
(cherry picked from commit 4c8ec8a4f2)
2023-09-08 19:33:55 +00:00
Tim Donohue
a0a8607628 Merge pull request #2484 from DSpace/backport-2412-to-dspace-7_x
[Port dspace-7_x] remove redundant cache default values from server.ts
2023-09-08 11:40:43 -05:00
Sascha Szott
3a465ac452 remove redundant cache default values from server.ts
(cherry picked from commit e53abcb69e)
2023-09-08 15:44:21 +00:00
Tim Donohue
97b2eb7a7c Merge pull request #2404 from alexandrevryghem/fix-setStaleByHrefSubtring-not-emitting-after-all-requests-were-stale_contribute-maintenance-7.6
[Port dspace-7_x] Fix `setStaleByHrefSubstring` not emitting true when all requests are stale
2023-09-07 14:39:33 -05:00
Tim Donohue
b46390c315 Merge pull request #2396 from alexandrevryghem/w2p-104312_pass-query-to-external-search-tabs_contribute-maintenance-7.6
[Port dspace-7_x] Search query is not set by default when opening an external sources tab
2023-09-07 12:49:47 -05:00
Tim Donohue
d14e258b5b Merge pull request #2483 from DSpace/backport-2470-to-dspace-7_x
[Port dspace-7_x] Minor pt-PT translation fixes
2023-09-07 11:15:21 -05:00
José Carvalho
1622b25aac Minor pt-PT translation fixes
(cherry picked from commit a6c1120700)
2023-09-07 15:35:02 +00:00
Tim Donohue
d95fa43c6b Merge pull request #2479 from DSpace/backport-2366-to-dspace-7_x
[Port dspace-7_x] CSV export fixes
2023-09-06 16:08:21 -05:00
Kristof De Langhe
4918ff212c 104189: CSV export add fixedFilter
(cherry picked from commit 45ad5f7316)
2023-09-06 19:53:01 +00:00
Kristof De Langhe
c2790584bd 104189: Allow CSV export on related entity search
(cherry picked from commit cac1407f08)
2023-09-06 19:53:01 +00:00
Tim Donohue
162cf94772 Merge pull request #2478 from DSpace/backport-2361-to-dspace-7_x
[Port dspace-7_x] Fix tree not updating when switching between "Browse by Vocabulary" pages
2023-09-06 09:22:00 -05:00
Nona Luypaert
92e0b6dddf Fix VocabularyTreeview not updating + i18n for nsi
(cherry picked from commit b5a70e8f95)
2023-09-05 22:14:44 +00:00
Tim Donohue
5b646af818 Merge pull request #2477 from DSpace/backport-2457-to-dspace-7_x
[Port dspace-7_x] config/config.example.yml: fix example syntax
2023-09-05 16:25:23 -05:00
Alan Orth
8363273f58 config/config.example.yml: fix example syntax
As of DSpace Angular 7.2 the syntax has changed from TypeScript to
YAML.

(cherry picked from commit 9e46b5310b)
2023-09-05 21:24:21 +00:00
Alan Orth
5f5d11cc0b Merge pull request #2476 from DSpace/backport-2432-to-dspace-7_x
[Port dspace-7_x] Fix to Value of dropdown changes automatically on item submission page
2023-09-05 20:16:37 +03:00
Hugo Dominguez
eb38b5877e change test event, click by mousedown on dynamic-scrollable-dropdown.component.spec.ts
(cherry picked from commit 25479e1794)
2023-09-05 17:15:51 +00:00
Hugo Dominguez
f88638e9fe 🐛 Fix Value of dropdown changes automatically on item submission page
(cherry picked from commit 651305952d)
2023-09-05 17:15:51 +00:00
Tim Donohue
cd350ddf5f Merge pull request #2472 from DSpace/backport-2448-to-dspace-7_x
[Port dspace-7_x] Spanish translation updated to 7.6
2023-09-05 09:48:32 -05:00
Alan Orth
2987ad05be Merge pull request #2474 from DSpace/backport-2444-to-dspace-7_x
[Port dspace-7_x] Fix to Mobile navbar hamburger menu doesn't work in Firefox/Safari #2372
2023-09-05 12:01:31 +03:00
Hugo Dominguez
9afbd8d746 🐛 fix when navbar expands on firefox
(cherry picked from commit 60706720e4)
2023-09-05 09:00:34 +00:00
Sergio Fernández Celorio
a4eaf02a47 Some lint errors fixed
(cherry picked from commit 1885638ba6)
2023-09-01 19:05:02 +00:00
Sergio Fernández Celorio
d1ebf07456 Spanish translation updated to 7.6
(cherry picked from commit 4cc4192e93)
2023-09-01 19:05:02 +00:00
Tim Donohue
02c47c3234 Merge pull request #2469 from DSpace/backport-2468-to-dspace-7_x
[Port dspace-7_x] Minor Accessibility Fixes & Enable accessibility scan on more pages
2023-08-30 10:31:36 -05:00
Tim Donohue
63c752b3f4 Fix heading order accessibility issue in search filters/facets
(cherry picked from commit 276d80895e)
2023-08-30 14:40:51 +00:00
Tim Donohue
6df76515ba Minor fixes to cypress tests
(cherry picked from commit 70a7bbe3cb)
2023-08-30 14:40:51 +00:00
Tim Donohue
3cdcdaf475 Fix accessibility of date sliders by adding aria-labels
(cherry picked from commit 2a881791ba)
2023-08-30 14:40:51 +00:00
Tim Donohue
b90d102e5e Update ng2-nouislider and nouislider to latest versions
(cherry picked from commit 91d8b7e4f7)
2023-08-30 14:40:51 +00:00
Tim Donohue
13ead8174a Fix heading order issue with item page & update accessibility tests to prove it now passes
(cherry picked from commit ba244bf6b1)
2023-08-30 14:40:51 +00:00
Tim Donohue
a7a807c0bb Enable excessibility checking of login menu, and remove unnecessary exclusion from header
(cherry picked from commit 339ed63734)
2023-08-30 14:40:51 +00:00
Tim Donohue
baecf2ac11 Reenable accessibility check fixed in #2251
(cherry picked from commit 158ebb0e32)
2023-08-30 14:40:51 +00:00
Tim Donohue
7ebdc43ca2 Update to latest axe-core
(cherry picked from commit 50899f1d1b)
2023-08-30 14:40:51 +00:00
Tim Donohue
13c0cb48ed Update to latest cypress
(cherry picked from commit 68a3323fca)
2023-08-30 14:40:51 +00:00
Alan Orth
6639594f7e Merge pull request #2465 from DSpace/backport-2463-to-dspace-7_x
[Port dspace-7_x] Fix to Pagination position is retained between searches #2159
2023-08-29 11:27:09 +03:00
Alexandre Vryghem
07a2e333ca Implemented i18n cache busting 2023-08-28 21:24:03 +02:00
Tim Donohue
af8c599497 Merge pull request #2466 from DSpace/backport-2447-to-dspace-7_x
[Port dspace-7_x] 2251 accessibility issues on the community list page
2023-08-28 14:13:02 -05:00
Alan Orth
0542e9b2fd Merge pull request #2467 from DSpace/backport-2399-to-dspace-7_x
[Port dspace-7_x] fix(i18n): curation-task.task.registerdoi.label
2023-08-28 21:07:04 +03:00
Mirko Scherf
2a55e36082 fix(i18m): curation-task.task.registerdoi.label
changed curation-task.task.register-doi.label back to
curation-task.task.registerdoi.label and added German translation

(cherry picked from commit 0ec72f679b)
2023-08-28 18:02:28 +00:00
Hrafn Malmquist
8a5d6897c4 Reorder instance method to come after member declaration
(cherry picked from commit e59913acab)
2023-08-28 14:44:18 +00:00
Hrafn Malmquist
e89a277702 Improve documentation
(cherry picked from commit d9b6e9d81f)
2023-08-28 14:44:18 +00:00
Hrafn Malmquist
9fc4e213df Add trackby function so cdktree can differentiate between new and old nodes
(cherry picked from commit 5f71de885b)
2023-08-28 14:44:18 +00:00
Hrafn Malmquist
46ac61dcac Replace h2 with a h1
(cherry picked from commit 05c53ad1d4)
2023-08-28 14:44:18 +00:00
Hrafn Malmquist
eef98d70c3 Replace h5 with a span
(cherry picked from commit 5ef4a827f5)
2023-08-28 14:44:18 +00:00
Hugo Dominguez
5c669fb1b7 add first page condition when search submit
(cherry picked from commit 044230209c)
2023-08-28 14:33:34 +00:00
Hugo Dominguez
a343991e74 🐛 go to first page after submit search
(cherry picked from commit 88a7088b47)
2023-08-28 14:33:34 +00:00
Tim Donohue
83de2c5769 Merge pull request #2462 from tdonohue/port_2395
[Port dspace-7_x] Fix themed components duplicating themself when switching themes
2023-08-25 13:28:03 -05:00
Alexandre Vryghem
8b57a2f6af Merge branch 'fix-ngonchanges-not-working-for-themed-components_contribute-7.2' into fix-ngonchanges-not-working-for-themed-components_contribute-7.4
# Conflicts:
#	src/app/app.component.ts
2023-08-25 12:34:10 -05:00
Alexandre Vryghem
7352d9e273 Reset to base theme when no default theme is set and leaving UUID/handle theme 2023-08-25 12:22:51 -05:00
Alexandre Vryghem
4e14bc0b78 101577: Ensure the component is always destroyed before rendering the new component 2023-08-25 12:22:32 -05:00
Alexandre Vryghem
0e289b3f39 103176: Fix vcr not being defined yet in OnInit hook 2023-08-25 12:19:30 -05:00
Koen Pauwels
815425c101 Fix lint issue. 2023-08-25 10:18:09 +02:00
Alan Orth
6ad641f4e2 Merge pull request #2460 from DSpace/backport-2434-to-dspace-7_x
[Port dspace-7_x] Input type list doesn't work correctly if multiple value-pairs have the same pair value
2023-08-25 10:37:33 +03:00
Alexandre Vryghem
7f00253d3d 105265: Made the DsDynamicListComponent's checkbox & radio buttons ids unique for each metadata field to prevent other value-pairs having the same id
(cherry picked from commit 4c419e1eee)
2023-08-25 06:17:16 +00:00
Tim Donohue
03d17678e2 Merge pull request #2459 from DSpace/backport-2449-to-dspace-7_x
[Port dspace-7_x] Update pl.json5
2023-08-24 16:03:53 -05:00
Michał Dykas
0efb95825d Update pl.json5 fix test 2 spaces instead of 1
(cherry picked from commit f58a7a2e9b)
2023-08-24 19:58:39 +00:00
Michał Dykas
95cde220e6 Update pl.json5 fix issues from tests
(cherry picked from commit 93299ec83d)
2023-08-24 19:58:39 +00:00
Michał Dykas
964066056c Update pl.json5
Translation update of 2 spaces instead of 3

(cherry picked from commit cfd753f928)
2023-08-24 19:58:39 +00:00
Michał Dykas
22db36f938 Update pl.json5
Update from 7.4 to 7.6 version

(cherry picked from commit b439ab4484)
2023-08-24 19:58:39 +00:00
Tim Donohue
9df4d660e7 Merge pull request #2453 from tdonohue/fix_demo_urls_7_x
[Port dspace-7_x] Replace mentions of demo7.dspace.org and api7.dspace.org with new demo URLs
2023-08-24 12:41:02 -05:00
Koen Pauwels
a5b30ea3c2 103818 Adjusted "Inherit policies" tooltip 2023-08-24 15:27:52 +02:00
Koen Pauwels
2078b7593a 103818 Add warning tooltip to "Inherit policies" checkbox on item move page 2023-08-24 15:27:52 +02:00
Tim Donohue
fe8429ebbe Enable new skip merge commit feature 2023-08-23 17:08:44 -05:00
Tim Donohue
8feeedfc3a Merge pull request #2455 from DSpace/backport-2438-to-dspace-7_x
[Port dspace-7_x] 2437 Correct and clarify commented-out code in several custom components
2023-08-23 16:07:17 -05:00
Hardy Pottinger
3292222e47 fix lint error in workflow-item-send-back.component.ts
(cherry picked from commit 518cc714f2)
2023-08-23 19:50:22 +00:00
Hardy Pottinger
36868c06f0 2437 Correct and clarify commented-out code in several custom components
- Correct the path in commented-out code in the custom theme's
  components
- Clarify that the workflow-item-sen-back.component.scss file is a stub
- It has no corresponding SCSS file in the base theme

(cherry picked from commit 0906234a29)
2023-08-23 19:50:22 +00:00
Tim Donohue
5853e49bd0 Update default configs to use https://demo.dspace.org/server/ 2023-08-22 16:44:10 -05:00
Tim Donohue
2fd53c7ad2 Replace mentions of demo7.dspace.org and api7.dspace.org with demo or sandbox 2023-08-22 16:38:00 -05:00
Tim Donohue
63345a335a Merge pull request #2428 from DSpace/backport-2409-to-dspace-7_x
[Port dspace-7_x] Bump word-wrap from 1.2.3 to 1.2.5
2023-08-09 10:07:14 -05:00
dependabot[bot]
bbb50f2858 Bump word-wrap from 1.2.3 to 1.2.5
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.5.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.5)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 2fec33e70a)
2023-08-08 21:42:41 +00:00
Tim Donohue
3e31c1eee3 Merge pull request #2427 from tdonohue/port_2048
[Port dspace-7_x] ProcessDetailComponent test improvement
2023-08-08 16:25:21 -05:00
Kristof De Langhe
cfcf93ecf8 104126: ProcessDetailComponent test improvement 2023-08-08 15:08:49 -05:00
Tim Donohue
74c2f3d9bb Merge pull request #2405 from alexandrevryghem/fix-collection-form-bugs_contribute-maintenance-7.6
[Port dspace-7_x] Fix minor collection form bugs
2023-08-04 12:48:34 -05:00
Tim Donohue
f22fcc7b3c Merge pull request #2420 from tdonohue/port_2388
[Port dspace-7_x] fix(i18n): add and update missing status strings
2023-08-04 12:25:44 -05:00
Mirko Scherf
c3b9a1d5c6 fix(i18n): add and update missing status strings
New strings for status filter entries:
search.filters.namedresourcetype.*
Refactored strings introduced with #2068 (refactor badged),
e.g. mydspace.status.archived -> mydspace.status.mydspaceArchived
2023-08-04 11:19:55 -05:00
Alan Orth
d072ae7027 Merge pull request #2419 from DSpace/backport-2378-to-dspace-7_x
[Port dspace-7_x] Show error message from the password validation response
2023-08-04 16:04:15 +03:00
milanmajchrak
f746d45ac1 Show error message from the error response
(cherry picked from commit e6546b4499)
2023-08-04 12:21:30 +00:00
Tim Donohue
1fd917dd4a Merge pull request #2418 from DSpace/backport-2344-to-dspace-7_x
[Port dspace-7_x] Catch and handle unsuccessful "convert rels to items" responses
2023-08-03 16:38:44 -05:00
Tim Donohue
99e349b91f Merge pull request #2392 from alexandrevryghem/fix-mathjax-displaying-twiced-in-ui_contribute-maintenance-7.6
[Port dspace-7_x] Fixed MathJax code being displayed twice by `dsMarkdown` pipe
2023-08-03 16:09:56 -05:00
Kim Shepherd
a7ed053d15 catch and handle unsuccessful "convert rels to items" responses
(cherry picked from commit a35b7d8356)
2023-08-03 20:03:27 +00:00
Tim Donohue
99c6dd1829 Merge pull request #2347 from alanorth/finnish-language-strings
src/assets/i18n: update Finnish language strings
2023-08-03 12:56:22 -05:00
Tim Donohue
0a48b09bd7 Merge pull request #2416 from tdonohue/port_of_2381
[Port dspace-7_x] refactor: rename aletr-type.ts to alert-type.ts
2023-08-03 12:37:18 -05:00
Mirko Scherf
0dc74165dc refactor: rename aletr-type.ts to alert-type.ts 2023-08-03 11:51:48 -05:00
Tim Donohue
9cbb634245 Merge pull request #2415 from DSpace/backport-2363-to-dspace-7_x
[Port dspace-7_x] Bump semver from 5.7.1 to 5.7.2
2023-08-02 15:55:32 -05:00
dependabot[bot]
7c379db7ee Bump semver from 5.7.1 to 5.7.2
Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 0b0c60e38c)
2023-08-02 20:06:40 +00:00
Alexandre Vryghem
3dc73f9021 Properly handle AuthMethod subscription in LogInComponent 2023-08-02 21:25:58 +02:00
Alexandre Vryghem
94ceee9080 Merge remote-tracking branch 'templates/dspace-7.6' into fix-display-order-authentication-methods_contribute-7.6
# Conflicts:
#	src/app/shared/log-in/log-in.component.html
#	src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.spec.ts
#	src/app/shared/log-in/methods/oidc/log-in-oidc.component.html
#	src/app/shared/log-in/methods/oidc/log-in-oidc.component.spec.ts
#	src/app/shared/log-in/methods/orcid/log-in-orcid.component.html
#	src/app/shared/log-in/methods/password/log-in-password.component.ts
#	src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.html
#	src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.spec.ts
2023-08-02 21:01:36 +02:00
Alexandre Vryghem
71cf66ecf4 Fix display order of authentication methods 2023-08-02 10:00:43 +02:00
Alan Orth
1b9656b135 src/assets/i18n: update Finnish language strings
Contributed by Reeta Kuukoski from the National Library of Finland.
2023-07-31 12:32:14 +03:00
Tim Donohue
85acdcb9c5 Merge pull request #2407 from DSpace/backport-2406-to-dspace-7_x
[Port dspace-7_x] Add GitHub action to automatically create a port PR (based on label) & minor bug fixes
2023-07-28 15:25:02 -05:00
Tim Donohue
867ae9c341 Minor update to label_merge_conflicts to ignore any errors (seem random at this time)
(cherry picked from commit d75d12b423)
2023-07-28 20:11:29 +00:00
Tim Donohue
4965bdee5f Add action to automatically create a port PR when specified
(cherry picked from commit 338b63ebb8)
2023-07-28 20:11:29 +00:00
Alexandre Vryghem
ebaccc055e Merge branch 'fix-collection-form-bugs_contribute-7.4' into fix-collection-form-bugs_contribute-7.6
# Conflicts:
#	src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html
2023-07-28 00:14:58 +02:00
Alexandre Vryghem
273be5bd81 Merge remote-tracking branch 'contributions/fix-setStaleByHrefSubtring-not-emitting-after-all-requests-were-stale_contribute-7.4' into fix-setStaleByHrefSubtring-not-emitting-after-all-requests-were-stale_contribute-7.6
# Conflicts:
#	src/app/core/data/request.service.ts
2023-07-27 23:57:18 +02:00
Alexandre Vryghem
5062e46433 104312: Add missing query @Input() to ThemedDynamicLookupRelationExternalSourceTabComponent 2023-07-25 18:26:47 +02:00
Alexandre Vryghem
9b1d18bd32 Merge tag 'dspace-7.6' into w2p-104312_pass-query-to-external-search-tabs_contribute-7.6 2023-07-25 18:26:18 +02:00
Alexandre Vryghem
15656b03ce Fix mathjax code being displayed twice
This was due to sanitizeHtml rendering the mjx-assistive-mml tag as text. This tag is used by screen readers and therefor we should allow it to be rendered
2023-07-21 23:18:40 +02:00
Alexandre Vryghem
75ec046bba Hide the delete EPerson/Group buttons when the user can't delete the EPerson/Group 2023-07-21 18:34:42 +02:00
Alexandre Vryghem
998e1fac8d Fix spacing issues for EPerson form buttons 2023-07-21 18:34:42 +02:00
Alexandre Vryghem
9ac19d40fc Cleanup access-control components
- Use the same methods to retrieve the access-control urls
- Fix EPersonDataService.startEditingNewEPerson returning the incorrect link
2023-07-21 18:34:41 +02:00
Alexandre Vryghem
2a35180a1b Created separate pages for edit & create EPersons 2023-07-21 17:15:10 +02:00
Alexandre Vryghem
648925f3e1 104312: DsDynamicLookupRelationExternalSourceTabComponent should have the form value already filled in the search input 2023-07-19 14:04:34 +02:00
Tim Donohue
4f0e1d6de1 Merge pull request #2369 from tdonohue/port_2358_2359
Port recent GitHub Actions changes to dspace-7_x branch
2023-07-14 16:56:32 -05:00
Tim Donohue
1809f0585c Split docker images into separate jobs to run in parallel. Ensure 'main' codebase is tagged as 'latest' 2023-07-14 15:41:57 -05:00
Tim Donohue
a484379f69 Ensure codescan and label_merge_conflicts run on maintenance branches 2023-07-14 15:41:41 -05:00
Tim Donohue
7bf4da55cf Enable Pull Request Opened action to assign PRs to their creator 2023-07-14 15:41:26 -05:00
Alan Orth
a079ed729c Merge pull request #2354 from alanorth/request-copy-component-path
src/app: fix path to deny-request-copy component
2023-07-06 20:45:56 +03:00
Alan Orth
3a48ed390b src/app: fix path to deny-request-copy component
The themed-deny-request-copy.component erroneously includes the cus-
tom theme's deny-request-copy component instead of its own.

Closes: https://github.com/DSpace/dspace-angular/issues/2351
2023-07-06 20:41:52 +03:00
Alexandre Vryghem
cf77726866 Fix enter not submitting collection form correctly
Fixed it for communities, collections, ePersons & groups
2023-07-01 01:01:57 +02:00
Alexandre Vryghem
b2b1782cd8 Hide entity field in collection form when entities aren't initialized 2023-06-30 22:43:46 +02:00
Alexandre Vryghem
02a20c8862 103236: Added tests for setStaleByHrefSubstring 2023-06-30 17:01:25 +02:00
Art Lowel
ae6b183fae 103236: fix issue where setStaleByHrefSubtring wouldn't emit after all requests were stale 2023-06-30 16:56:15 +02:00
Tim Donohue
884aa07430 Update version tag for development of next release 2023-06-23 13:04:19 -05:00
799 changed files with 39418 additions and 15366 deletions

View File

@@ -1,26 +0,0 @@
# This workflow runs whenever a new pull request is created
# TEMPORARILY DISABLED. Unfortunately this doesn't work for PRs created from forked repositories (which is how we tend to create PRs).
# There is no known workaround yet. See https://github.community/t/how-to-use-github-token-for-prs-from-forks/16818
name: Pull Request opened
# Only run for newly opened PRs against the "main" branch
on:
pull_request:
types: [opened]
branches:
- main
jobs:
automation:
runs-on: ubuntu-latest
steps:
# Assign the PR to whomever created it. This is useful for visualizing assignments on project boards
# See https://github.com/marketplace/actions/pull-request-assigner
- name: Assign PR to creator
uses: thomaseizinger/assign-pr-creator-action@v1.0.0
# Note, this authentication token is created automatically
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Ignore errors. It is possible the PR was created by someone who cannot be assigned
continue-on-error: true

View File

@@ -33,6 +33,8 @@ jobs:
#CHROME_VERSION: "90.0.4430.212-1"
# Bump Node heap size (OOM in CI after upgrading to Angular 15)
NODE_OPTIONS: '--max-old-space-size=4096'
# Project name to use when running "docker compose" prior to e2e tests
COMPOSE_PROJECT_NAME: 'ci'
strategy:
# Create a matrix of Node versions to test against (in parallel)
matrix:
@@ -43,11 +45,11 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v3
uses: actions/checkout@v4
# https://github.com/actions/setup-node
- name: Install Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
@@ -72,7 +74,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- name: Cache Yarn dependencies
uses: actions/cache@v3
uses: actions/cache@v4
with:
# Cache entire Yarn cache directory (see previous step)
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
@@ -99,26 +101,26 @@ jobs:
# so that it can be shared with the 'codecov' job (see below)
# NOTE: Angular CLI only supports code coverage for specs. See https://github.com/angular/angular-cli/issues/6286
- name: Upload code coverage report to Artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: matrix.node-version == '18.x'
with:
name: dspace-angular coverage report
name: coverage-report-${{ matrix.node-version }}
path: 'coverage/dspace-angular/lcov.info'
retention-days: 14
# Using docker-compose start backend using CI configuration
# Using "docker compose" start backend using CI configuration
# and load assetstore from a cached copy
- name: Start DSpace REST Backend via Docker (for e2e tests)
run: |
docker-compose -f ./docker/docker-compose-ci.yml up -d
docker-compose -f ./docker/cli.yml -f ./docker/cli.assetstore.yml run --rm dspace-cli
docker compose -f ./docker/docker-compose-ci.yml up -d
docker compose -f ./docker/cli.yml -f ./docker/cli.assetstore.yml run --rm dspace-cli
docker container ls
# 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@v5
uses: cypress-io/github-action@v6
with:
# Run tests in Chrome, headless mode (default)
browser: chrome
@@ -133,19 +135,19 @@ jobs:
# 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@v3
uses: actions/upload-artifact@v4
if: always()
with:
name: e2e-test-videos
name: e2e-test-videos-${{ matrix.node-version }}
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@v3
uses: actions/upload-artifact@v4
if: failure()
with:
name: e2e-test-screenshots
name: e2e-test-screenshots-${{ matrix.node-version }}
path: cypress/screenshots
- name: Stop app (in case it stays up after e2e tests)
@@ -180,7 +182,7 @@ jobs:
run: kill -9 $(lsof -t -i:4000)
- name: Shutdown Docker containers
run: docker-compose -f ./docker/docker-compose-ci.yml down
run: docker compose -f ./docker/docker-compose-ci.yml down
# Codecov upload is a separate job in order to allow us to restart this separate from the entire build/test
# job above. This is necessary because Codecov uploads seem to randomly fail at times.
@@ -191,11 +193,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
# Download artifacts from previous 'tests' job
- name: Download coverage artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
# Now attempt upload to Codecov using its action.
# NOTE: We use a retry action to retry the Codecov upload if it fails the first time.
@@ -203,10 +205,15 @@ jobs:
# Retry action: https://github.com/marketplace/actions/retry-action
# Codecov action: https://github.com/codecov/codecov-action
- name: Upload coverage to Codecov.io
uses: Wandalen/wretry.action@v1.0.36
uses: Wandalen/wretry.action@v1.3.0
with:
action: codecov/codecov-action@v3
# Try upload 5 times max
action: codecov/codecov-action@v4
# Ensure codecov-action throws an error when it fails to upload
# This allows us to auto-restart the action if an error is thrown
with: |
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
# Try re-running action 5 times max
attempt_limit: 5
# Run again in 30 seconds
attempt_delay: 30000

View File

@@ -5,12 +5,16 @@
# because CodeQL requires a fresh build with all tests *disabled*.
name: "Code Scanning"
# Run this code scan for all pushes / PRs to main branch. Also run once a week.
# Run this code scan for all pushes / PRs to main or maintenance branches. Also run once a week.
on:
push:
branches: [ main ]
branches:
- main
- 'dspace-**'
pull_request:
branches: [ main ]
branches:
- main
- 'dspace-**'
# Don't run if PR is only updating static documentation
paths-ignore:
- '**/*.md'
@@ -31,7 +35,7 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
# https://github.com/github/codeql-action

View File

@@ -3,6 +3,9 @@ name: Docker images
# Run this Build for all pushes to 'main' or maintenance branches, or tagged releases.
# Also run for PRs to ensure PR doesn't break Docker build process
# NOTE: uses "reusable-docker-build.yml" in DSpace/DSpace to actually build each of the Docker images
# https://github.com/DSpace/DSpace/blob/dspace-7_x/.github/workflows/reusable-docker-build.yml
#
on:
push:
branches:
@@ -16,105 +19,41 @@ permissions:
contents: read # to fetch code (actions/checkout)
jobs:
docker:
#############################################################
# Build/Push the 'dspace/dspace-angular' image
#############################################################
dspace-angular:
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace-angular'
if: github.repository == 'dspace/dspace-angular'
runs-on: ubuntu-latest
env:
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
# For a new commit on default branch (main), use the literal tag 'dspace-7_x' on Docker image.
# For a new commit on other branches, use the branch name as the tag for Docker image.
# For a new tag, copy that tag name as the tag for Docker image.
IMAGE_TAGS: |
type=raw,value=dspace-7_x,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=tag
# Define default tag "flavor" for docker/metadata-action per
# https://github.com/docker/metadata-action#flavor-input
# We turn off 'latest' tag by default.
TAGS_FLAVOR: |
latest=false
# Architectures / Platforms for which we will build Docker images
# If this is a PR, we ONLY build for AMD64. For PRs we only do a sanity check test to ensure Docker builds work.
# If this is NOT a PR (e.g. a tag or merge commit), also build for ARM64.
PLATFORMS: linux/amd64${{ github.event_name != 'pull_request' && ', linux/arm64' || '' }}
# Use the reusable-docker-build.yml script from DSpace/DSpace repo to build our Docker image
uses: DSpace/DSpace/.github/workflows/reusable-docker-build.yml@dspace-7_x
with:
build_id: dspace-angular-dev
image_name: dspace/dspace-angular
dockerfile_path: ./Dockerfile
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v3
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v2
# https://github.com/docker/setup-qemu-action
- name: Set up QEMU emulation to build for multiple architectures
uses: docker/setup-qemu-action@v2
# https://github.com/docker/login-action
- name: Login to DockerHub
# Only login if not a PR, as PRs only trigger a Docker build and not a push
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
###############################################
# Build/Push the 'dspace/dspace-angular' image
###############################################
# https://github.com/docker/metadata-action
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-angular' image
id: meta_build
uses: docker/metadata-action@v4
with:
images: dspace/dspace-angular
tags: ${{ env.IMAGE_TAGS }}
flavor: ${{ env.TAGS_FLAVOR }}
# https://github.com/docker/build-push-action
- name: Build and push 'dspace-angular' image
id: docker_build
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
platforms: ${{ env.PLATFORMS }}
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
# but we ONLY do an image push to DockerHub if it's NOT a PR
push: ${{ github.event_name != 'pull_request' }}
# Use tags / labels provided by 'docker/metadata-action' above
tags: ${{ steps.meta_build.outputs.tags }}
labels: ${{ steps.meta_build.outputs.labels }}
#####################################################
# Build/Push the 'dspace/dspace-angular' image ('-dist' tag)
#####################################################
# https://github.com/docker/metadata-action
# Get Metadata for docker_build_dist step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-angular-dist' image
id: meta_build_dist
uses: docker/metadata-action@v4
with:
images: dspace/dspace-angular
tags: ${{ env.IMAGE_TAGS }}
# As this is a "dist" image, its tags are all suffixed with "-dist". Otherwise, it uses the same
# tagging logic as the primary 'dspace/dspace-angular' image above.
flavor: ${{ env.TAGS_FLAVOR }}
suffix=-dist
- name: Build and push 'dspace-angular-dist' image
id: docker_build_dist
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile.dist
platforms: ${{ env.PLATFORMS }}
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
# but we ONLY do an image push to DockerHub if it's NOT a PR
push: ${{ github.event_name != 'pull_request' }}
# Use tags / labels provided by 'docker/metadata-action' above
tags: ${{ steps.meta_build_dist.outputs.tags }}
labels: ${{ steps.meta_build_dist.outputs.labels }}
#############################################################
# Build/Push the 'dspace/dspace-angular' image ('-dist' tag)
#############################################################
dspace-angular-dist:
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace-angular'
if: github.repository == 'dspace/dspace-angular'
# Use the reusable-docker-build.yml script from DSpace/DSpace repo to build our Docker image
uses: DSpace/DSpace/.github/workflows/reusable-docker-build.yml@dspace-7_x
with:
build_id: dspace-angular-dist
image_name: dspace/dspace-angular
dockerfile_path: ./Dockerfile.dist
# As this is a "dist" image, its tags are all suffixed with "-dist". Otherwise, it uses the same
# tagging logic as the primary 'dspace/dspace-angular' image above.
tags_flavor: suffix=-dist
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
# Enable redeploy of sandbox & demo if the branch for this image matches the deployment branch of
# these sites as specified in reusable-docker-build.xml
REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_URL }}
REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }}

View File

@@ -16,7 +16,7 @@ jobs:
# Only add to project board if issue is flagged as "needs triage" or has no labels
# NOTE: By default we flag new issues as "needs triage" in our issue template
if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '')
uses: actions/add-to-project@v0.5.0
uses: actions/add-to-project@v1.0.0
# Note, the authentication token below is an ORG level Secret.
# It must be created/recreated manually via a personal access token with admin:org, project, public_repo permissions
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token

View File

@@ -1,11 +1,12 @@
# This workflow checks open PRs for merge conflicts and labels them when conflicts are found
name: Check for merge conflicts
# Run whenever the "main" branch is updated
# NOTE: This means merge conflicts are only checked for when a PR is merged to main.
# Run this for all pushes (i.e. merges) to 'main' or maintenance branches
on:
push:
branches: [ main ]
branches:
- main
- 'dspace-**'
# So that the `conflict_label_name` is removed if conflicts are resolved,
# we allow this to run for `pull_request_target` so that github secrets are available.
pull_request_target:
@@ -24,6 +25,8 @@ jobs:
# See: https://github.com/prince-chrismc/label-merge-conflicts-action
- name: Auto-label PRs with merge conflicts
uses: prince-chrismc/label-merge-conflicts-action@v3
# Ignore any failures -- may occur (randomly?) for older, outdated PRs.
continue-on-error: true
# Add "merge conflict" label if a merge conflict is detected. Remove it when resolved.
# Note, the authentication token is created automatically
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token

View File

@@ -0,0 +1,46 @@
# This workflow will attempt to port a merged pull request to
# the branch specified in a "port to" label (if exists)
name: Port merged Pull Request
# Only run for merged PRs against the "main" or maintenance branches
# We allow this to run for `pull_request_target` so that github secrets are available
# (This is required when the PR comes from a forked repo)
on:
pull_request_target:
types: [ closed ]
branches:
- main
- 'dspace-**'
permissions:
contents: write # so action can add comments
pull-requests: write # so action can create pull requests
jobs:
port_pr:
runs-on: ubuntu-latest
# Don't run on closed *unmerged* pull requests
if: github.event.pull_request.merged
steps:
# Checkout code
- uses: actions/checkout@v4
# Port PR to other branch (ONLY if labeled with "port to")
# See https://github.com/korthout/backport-action
- name: Create backport pull requests
uses: korthout/backport-action@v2
with:
# Trigger based on a "port to [branch]" label on PR
# (This label must specify the branch name to port to)
label_pattern: '^port to ([^ ]+)$'
# Title to add to the (newly created) port PR
pull_title: '[Port ${target_branch}] ${pull_title}'
# Description to add to the (newly created) port PR
pull_description: 'Port of #${pull_number} by @${pull_author} to `${target_branch}`.'
# Copy all labels from original PR to (newly created) port PR
# NOTE: The labels matching 'label_pattern' are automatically excluded
copy_labels_pattern: '.*'
# Skip any merge commits in the ported PR. This means only non-merge commits are cherry-picked to the new PR
merge_commits: 'skip'
# Use a personal access token (PAT) to create PR as 'dspace-bot' user.
# A PAT is required in order for the new PR to trigger its own actions (for CI checks)
github_token: ${{ secrets.PR_PORT_TOKEN }}

View File

@@ -0,0 +1,24 @@
# This workflow runs whenever a new pull request is created
name: Pull Request opened
# Only run for newly opened PRs against the "main" or maintenance branches
# We allow this to run for `pull_request_target` so that github secrets are available
# (This is required to assign a PR back to the creator when the PR comes from a forked repo)
on:
pull_request_target:
types: [ opened ]
branches:
- main
- 'dspace-**'
permissions:
pull-requests: write
jobs:
automation:
runs-on: ubuntu-latest
steps:
# Assign the PR to whomever created it. This is useful for visualizing assignments on project boards
# See https://github.com/toshimaru/auto-author-assign
- name: Assign PR to creator
uses: toshimaru/auto-author-assign@v2.1.0

View File

@@ -157,8 +157,8 @@ DSPACE_UI_SSL => DSPACE_SSL
The same settings can also be overwritten by setting system environment variables instead, E.g.:
```bash
export DSPACE_HOST=api7.dspace.org
export DSPACE_UI_PORT=4200
export DSPACE_HOST=demo.dspace.org
export DSPACE_UI_PORT=4000
```
The priority works as follows: **environment variable** overrides **variable in `.env` file** overrides external config set by `DSPACE_APP_CONFIG_PATH` overrides **`config.(prod or dev).yml`**
@@ -288,7 +288,7 @@ E2E tests (aka integration tests) use [Cypress.io](https://www.cypress.io/). Con
The test files can be found in the `./cypress/integration/` folder.
Before you can run e2e tests, two things are REQUIRED:
1. You MUST be running the DSpace backend (i.e. REST API) locally. The e2e tests will *NOT* succeed if run against our demo REST API (https://api7.dspace.org/server/), as that server is uncontrolled and may have content added/removed at any time.
1. You MUST be running the DSpace backend (i.e. REST API) locally. The e2e tests will *NOT* succeed if run against our demo/sandbox REST API (https://demo.dspace.org/server/ or https://sandbox.dspace.org/server/), as those sites may have content added/removed at any time.
* After starting up your backend on localhost, make sure either your `config.prod.yml` or `config.dev.yml` has its `rest` settings defined to use that localhost backend.
* If you'd prefer, you may instead use environment variables as described at [Configuring](#configuring). For example:
```

View File

@@ -17,12 +17,19 @@ ui:
# Trust X-FORWARDED-* headers from proxies (default = true)
useProxies: true
universal:
# Whether to inline "critical" styles into the server-side rendered HTML.
# Determining which styles are critical is a relatively expensive operation;
# this option can be disabled to boost server performance at the expense of
# loading smoothness. For improved SSR performance, DSpace defaults this to false (disabled).
inlineCriticalCss: false
# The REST API server settings
# NOTE: these settings define which (publicly available) REST API to use. They are usually
# 'synced' with the 'dspace.server.url' setting in your backend's local.cfg.
rest:
ssl: true
host: api7.dspace.org
host: demo.dspace.org
port: 443
# NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
nameSpace: /server
@@ -169,6 +176,12 @@ languages:
- code: en
label: English
active: true
- code: ar
label: العربية
active: true
- code: bn
label: বাংলা
active: true
- code: ca
label: Català
active: true
@@ -178,24 +191,36 @@ languages:
- code: de
label: Deutsch
active: true
- code: el
label: Ελληνικά
active: true
- code: es
label: Español
active: true
- code: fi
label: Suomi
active: true
- code: fr
label: Français
active: true
- code: gd
label: Gàidhlig
active: true
- code: it
label: Italiano
active: true
- code: lv
label: Latviešu
- code: hi
label: हिंदी
active: true
- code: hu
label: Magyar
active: true
- code: it
label: Italiano
active: true
- code: kk
label: Қазақ
active: true
- code: lv
label: Latviešu
active: true
- code: nl
label: Nederlands
active: true
@@ -208,8 +233,11 @@ languages:
- code: pt-BR
label: Português do Brasil
active: true
- code: fi
label: Suomi
- code: sr-lat
label: Srpski (lat)
active: true
- code: sr-cyr
label: Српски
active: true
- code: sv
label: Svenska
@@ -217,24 +245,12 @@ languages:
- code: tr
label: Türkçe
active: true
- code: vi
label: Tiếng Việt
active: true
- code: kk
label: Қазақ
active: true
- code: bn
label: বাংলা
active: true
- code: hi
label: हिंदी
active: true
- code: el
label: Ελληνικά
active: true
- code: uk
label: раї́нська
active: true
- code: vi
label: Tiếng Việt
active: true
# Browse-By Pages
@@ -292,33 +308,33 @@ themes:
#
# # A theme with a handle property will match the community, collection or item with the given
# # handle, and all collections and/or items within it
# - name: 'custom',
# handle: '10673/1233'
# - name: custom
# handle: 10673/1233
#
# # A theme with a regex property will match the route using a regular expression. If it
# # matches the route for a community or collection it will also apply to all collections
# # and/or items within it
# - name: 'custom',
# regex: 'collections\/e8043bc2.*'
# - name: custom
# regex: collections\/e8043bc2.*
#
# # A theme with a uuid property will match the community, collection or item with the given
# # ID, and all collections and/or items within it
# - name: 'custom',
# uuid: '0958c910-2037-42a9-81c7-dca80e3892b4'
# - name: custom
# uuid: 0958c910-2037-42a9-81c7-dca80e3892b4
#
# # The extends property specifies an ancestor theme (by name). Whenever a themed component is not found
# # in the current theme, its ancestor theme(s) will be checked recursively before falling back to default.
# - name: 'custom-A',
# extends: 'custom-B',
# - name: custom-A
# extends: custom-B
# # Any of the matching properties above can be used
# handle: '10673/34'
# handle: 10673/34
#
# - name: 'custom-B',
# extends: 'custom',
# handle: '10673/12'
# - name: custom-B
# extends: custom
# handle: 10673/12
#
# # A theme with only a name will match every route
# name: 'custom'
# name: custom
#
# # This theme will use the default bootstrap styling for DSpace components
# - name: BASE_THEME_NAME
@@ -379,4 +395,4 @@ vocabularies:
# Default collection/community sorting order at Advanced search, Create/update community and collection when there are not a query.
comcolSelectionSort:
sortField: 'dc.title'
sortDirection: 'ASC'
sortDirection: 'ASC'

View File

@@ -1,5 +1,5 @@
rest:
ssl: true
host: api7.dspace.org
host: demo.dspace.org
port: 443
nameSpace: /server

View File

@@ -9,8 +9,9 @@ export default defineConfig({
openMode: 0,
},
env: {
// Global constants used in DSpace e2e tests (see also ./cypress/support/e2e.ts)
// May be overridden in our cypress.json config file using specified environment variables.
// Global DSpace environment variables used in all our Cypress e2e tests
// May be modified in this config, or overridden in a variety of ways.
// See Cypress environment variable docs: https://docs.cypress.io/guides/guides/environment-variables
// Default values listed here are all valid for the Demo Entities Data set available at
// https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
// (This is the data set used in our CI environment)
@@ -21,12 +22,14 @@ export default defineConfig({
// Community/collection/publication used for view/edit tests
DSPACE_TEST_COMMUNITY: '0958c910-2037-42a9-81c7-dca80e3892b4',
DSPACE_TEST_COLLECTION: '282164f5-d325-4740-8dd1-fa4d6d3e7200',
DSPACE_TEST_ENTITY_PUBLICATION: 'e98b0f27-5c19-49a0-960d-eb6ad5287067',
DSPACE_TEST_ENTITY_PUBLICATION: '6160810f-1e53-40db-81ef-f6621a727398',
// Search term (should return results) used in search tests
DSPACE_TEST_SEARCH_TERM: 'test',
// Collection used for submission tests
// Main Collection used for submission tests. Should be able to accept normal Item objects
DSPACE_TEST_SUBMIT_COLLECTION_NAME: 'Sample Collection',
DSPACE_TEST_SUBMIT_COLLECTION_UUID: '9d8334e9-25d3-4a67-9cea-3dffdef80144',
// Collection used for Person entity submission tests. MUST be configured with EntityType=Person.
DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME: 'People',
// Account used to test basic submission process
DSPACE_TEST_SUBMIT_USER: 'dspacedemo+submit@gmail.com',
DSPACE_TEST_SUBMIT_USER_PASSWORD: 'dspace',

View File

@@ -0,0 +1,28 @@
import { Options } from 'cypress-axe';
import { testA11y } from 'cypress/support/utils';
describe('Admin Sidebar', () => {
beforeEach(() => {
// Must login as an Admin for sidebar to appear
cy.visit('/login');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
it('should be pinnable and pass accessibility tests', () => {
// Pin the sidebar open
cy.get('#sidebar-collapse-toggle').click();
// Click on every expandable section to open all menus
cy.get('ds-expandable-admin-sidebar-section').click({multiple: true});
// Analyze <ds-admin-sidebar> for accessibility
testA11y('ds-admin-sidebar',
{
rules: {
// Currently all expandable sections have nested interactive elements
// See https://github.com/DSpace/dspace-angular/issues/2178
'nested-interactive': { enabled: false },
}
} as Options);
});
});

View File

@@ -1,10 +1,9 @@
import { TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e';
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/'.concat(TEST_ENTITY_PUBLICATION));
cy.visit('/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')));
// Wait for breadcrumbs to be visible
cy.get('ds-breadcrumbs').should('be.visible');

View File

@@ -0,0 +1,128 @@
import { testA11y } from 'cypress/support/utils';
const COLLECTION_EDIT_PAGE = '/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION')).concat('/edit');
beforeEach(() => {
// All tests start with visiting the Edit Collection Page
cy.visit(COLLECTION_EDIT_PAGE);
// This page is restricted, so we will be shown the login form. Fill it out & submit.
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
describe('Edit Collection > Edit Metadata tab', () => {
it('should pass accessibility tests', () => {
// <ds-edit-collection> tag must be loaded
cy.get('ds-edit-collection').should('be.visible');
// Analyze <ds-edit-collection> for accessibility issues
testA11y('ds-edit-collection');
});
});
describe('Edit Collection > Assign Roles tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="roles"]').click();
// <ds-collection-roles> tag must be loaded
cy.get('ds-collection-roles').should('be.visible');
// Analyze for accessibility issues
testA11y('ds-collection-roles');
});
});
describe('Edit Collection > Content Source tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="source"]').click();
// <ds-collection-source> tag must be loaded
cy.get('ds-collection-source').should('be.visible');
// Check the external source checkbox (to display all fields on the page)
cy.get('#externalSourceCheck').check();
// Wait for the source controls to appear
// cy.get('ds-collection-source-controls').should('be.visible');
// Analyze entire page for accessibility issues
testA11y('ds-collection-source');
});
});
describe('Edit Collection > Curate tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="curate"]').click();
// <ds-collection-curate> tag must be loaded
cy.get('ds-collection-curate').should('be.visible');
// Analyze for accessibility issues
testA11y('ds-collection-curate');
});
});
describe('Edit Collection > Access Control tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="access-control"]').click();
// <ds-collection-access-control> tag must be loaded
cy.get('ds-collection-access-control').should('be.visible');
// Analyze for accessibility issues
testA11y('ds-collection-access-control');
});
});
describe('Edit Collection > Authorizations tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="authorizations"]').click();
// <ds-collection-authorizations> tag must be loaded
cy.get('ds-collection-authorizations').should('be.visible');
// Analyze for accessibility issues
testA11y('ds-collection-authorizations');
});
});
describe('Edit Collection > Item Mapper tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="mapper"]').click();
// <ds-collection-item-mapper> tag must be loaded
cy.get('ds-collection-item-mapper').should('be.visible');
// Analyze entire page for accessibility issues
testA11y('ds-collection-item-mapper');
// Click on the "Map new Items" tab
cy.get('li[data-test="mapTab"] a').click();
// Make sure search form is now visible
cy.get('ds-search-form').should('be.visible');
// Analyze entire page (again) for accessibility issues
testA11y('ds-collection-item-mapper');
});
});
describe('Edit Collection > Delete page', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="delete-button"]').click();
// <ds-delete-collection> tag must be loaded
cy.get('ds-delete-collection').should('be.visible');
// Analyze for accessibility issues
testA11y('ds-delete-collection');
});
});

View File

@@ -1,10 +1,9 @@
import { TEST_COLLECTION } from 'cypress/support/e2e';
import { testA11y } from 'cypress/support/utils';
describe('Collection Page', () => {
it('should pass accessibility tests', () => {
cy.visit('/collections/'.concat(TEST_COLLECTION));
cy.visit('/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION')));
// <ds-collection-page> tag must be loaded
cy.get('ds-collection-page').should('be.visible');

View File

@@ -1,12 +1,12 @@
import { REGEX_MATCH_NON_EMPTY_TEXT, TEST_COLLECTION } from 'cypress/support/e2e';
import { REGEX_MATCH_NON_EMPTY_TEXT } from 'cypress/support/e2e';
import { testA11y } from 'cypress/support/utils';
describe('Collection Statistics Page', () => {
const COLLECTIONSTATISTICSPAGE = '/statistics/collections/'.concat(TEST_COLLECTION);
const COLLECTIONSTATISTICSPAGE = '/statistics/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION'));
it('should load if you click on "Statistics" from a Collection page', () => {
cy.visit('/collections/'.concat(TEST_COLLECTION));
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
cy.visit('/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION')));
cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click();
cy.location('pathname').should('eq', COLLECTIONSTATISTICSPAGE);
});
@@ -18,7 +18,7 @@ describe('Collection Statistics Page', () => {
it('should contain a "Total visits per month" section', () => {
cy.visit(COLLECTIONSTATISTICSPAGE);
// Check just for existence because this table is empty in CI environment as it's historical data
cy.get('.'.concat(TEST_COLLECTION).concat('_TotalVisitsPerMonth')).should('exist');
cy.get('.'.concat(Cypress.env('DSPACE_TEST_COLLECTION')).concat('_TotalVisitsPerMonth')).should('exist');
});
it('should pass accessibility tests', () => {

View File

@@ -0,0 +1,86 @@
import { testA11y } from 'cypress/support/utils';
const COMMUNITY_EDIT_PAGE = '/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY')).concat('/edit');
beforeEach(() => {
// All tests start with visiting the Edit Community Page
cy.visit(COMMUNITY_EDIT_PAGE);
// This page is restricted, so we will be shown the login form. Fill it out & submit.
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
describe('Edit Community > Edit Metadata tab', () => {
it('should pass accessibility tests', () => {
// <ds-edit-community> tag must be loaded
cy.get('ds-edit-community').should('be.visible');
// Analyze <ds-edit-community> for accessibility issues
testA11y('ds-edit-community');
});
});
describe('Edit Community > Assign Roles tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="roles"]').click();
// <ds-community-roles> tag must be loaded
cy.get('ds-community-roles').should('be.visible');
// Analyze for accessibility issues
testA11y('ds-community-roles');
});
});
describe('Edit Community > Curate tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="curate"]').click();
// <ds-community-curate> tag must be loaded
cy.get('ds-community-curate').should('be.visible');
// Analyze for accessibility issues
testA11y('ds-community-curate');
});
});
describe('Edit Community > Access Control tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="access-control"]').click();
// <ds-community-access-control> tag must be loaded
cy.get('ds-community-access-control').should('be.visible');
// Analyze for accessibility issues
testA11y('ds-community-access-control');
});
});
describe('Edit Community > Authorizations tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="authorizations"]').click();
// <ds-community-authorizations> tag must be loaded
cy.get('ds-community-authorizations').should('be.visible');
// Analyze for accessibility issues
testA11y('ds-community-authorizations');
});
});
describe('Edit Community > Delete page', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="delete-button"]').click();
// <ds-delete-community> tag must be loaded
cy.get('ds-delete-community').should('be.visible');
// Analyze for accessibility issues
testA11y('ds-delete-community');
});
});

View File

@@ -1,4 +1,3 @@
import { Options } from 'cypress-axe';
import { testA11y } from 'cypress/support/utils';
describe('Community List Page', () => {
@@ -13,13 +12,6 @@ describe('Community List Page', () => {
cy.get('[data-test="expand-button"]').click({ multiple: true });
// 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
);
testA11y('ds-community-list-page');
});
});

View File

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

View File

@@ -1,12 +1,12 @@
import { REGEX_MATCH_NON_EMPTY_TEXT, TEST_COMMUNITY } from 'cypress/support/e2e';
import { REGEX_MATCH_NON_EMPTY_TEXT } from 'cypress/support/e2e';
import { testA11y } from 'cypress/support/utils';
describe('Community Statistics Page', () => {
const COMMUNITYSTATISTICSPAGE = '/statistics/communities/'.concat(TEST_COMMUNITY);
const COMMUNITYSTATISTICSPAGE = '/statistics/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY'));
it('should load if you click on "Statistics" from a Community page', () => {
cy.visit('/communities/'.concat(TEST_COMMUNITY));
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
cy.visit('/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY')));
cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click();
cy.location('pathname').should('eq', COMMUNITYSTATISTICSPAGE);
});
@@ -18,7 +18,7 @@ describe('Community Statistics Page', () => {
it('should contain a "Total visits per month" section', () => {
cy.visit(COMMUNITYSTATISTICSPAGE);
// Check just for existence because this table is empty in CI environment as it's historical data
cy.get('.'.concat(TEST_COMMUNITY).concat('_TotalVisitsPerMonth')).should('exist');
cy.get('.'.concat(Cypress.env('DSPACE_TEST_COMMUNITY')).concat('_TotalVisitsPerMonth')).should('exist');
});
it('should pass accessibility tests', () => {

View File

@@ -8,12 +8,6 @@ describe('Header', () => {
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
],
});
testA11y('ds-header');
});
});

View File

@@ -1,18 +1,18 @@
import { REGEX_MATCH_NON_EMPTY_TEXT, TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e';
import { REGEX_MATCH_NON_EMPTY_TEXT } from 'cypress/support/e2e';
import { testA11y } from 'cypress/support/utils';
import '../support/commands';
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.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click();
cy.location('pathname').should('eq', '/statistics');
});
it('should pass accessibility tests', () => {
// generate 2 view events on an Item's page
cy.generateViewEvent(TEST_ENTITY_PUBLICATION, 'item');
cy.generateViewEvent(TEST_ENTITY_PUBLICATION, 'item');
cy.generateViewEvent(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'), 'item');
cy.generateViewEvent(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'), 'item');
cy.visit('/statistics');

140
cypress/e2e/item-edit.cy.ts Normal file
View File

@@ -0,0 +1,140 @@
import { Options } from 'cypress-axe';
import { testA11y } from 'cypress/support/utils';
const ITEM_EDIT_PAGE = '/items/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')).concat('/edit');
beforeEach(() => {
// All tests start with visiting the Edit Item Page
cy.visit(ITEM_EDIT_PAGE);
// This page is restricted, so we will be shown the login form. Fill it out & submit.
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
describe('Edit Item > Edit Metadata tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="metadata"]').click();
// <ds-edit-item-page> tag must be loaded
cy.get('ds-edit-item-page').should('be.visible');
// wait for all the ds-dso-edit-metadata-value components to be rendered
cy.get('ds-dso-edit-metadata-value div[role="row"]').each(($row: HTMLDivElement) => {
cy.wrap($row).find('div[role="cell"]').should('be.visible');
});
// Analyze <ds-edit-item-page> for accessibility issues
testA11y('ds-edit-item-page');
});
});
describe('Edit Item > Status tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="status"]').click();
// <ds-item-status> tag must be loaded
cy.get('ds-item-status').should('be.visible');
// Analyze for accessibility issues
testA11y('ds-item-status');
});
});
describe('Edit Item > Bitstreams tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="bitstreams"]').click();
// <ds-item-bitstreams> tag must be loaded
cy.get('ds-item-bitstreams').should('be.visible');
// Table of item bitstreams must also be loaded
cy.get('div.item-bitstreams').should('be.visible');
// Analyze for accessibility issues
testA11y('ds-item-bitstreams',
{
rules: {
// Currently Bitstreams page loads a pagination component per Bundle
// and they all use the same 'id="p-dad"'.
'duplicate-id': { enabled: false },
}
} as Options
);
});
});
describe('Edit Item > Curate tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="curate"]').click();
// <ds-item-curate> tag must be loaded
cy.get('ds-item-curate').should('be.visible');
// Analyze for accessibility issues
testA11y('ds-item-curate');
});
});
describe('Edit Item > Relationships tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="relationships"]').click();
// <ds-item-relationships> tag must be loaded
cy.get('ds-item-relationships').should('be.visible');
// Analyze for accessibility issues
testA11y('ds-item-relationships');
});
});
describe('Edit Item > Version History tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="versionhistory"]').click();
// <ds-item-version-history> tag must be loaded
cy.get('ds-item-version-history').should('be.visible');
// Analyze for accessibility issues
testA11y('ds-item-version-history');
});
});
describe('Edit Item > Access Control tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="access-control"]').click();
// <ds-item-access-control> tag must be loaded
cy.get('ds-item-access-control').should('be.visible');
// Analyze for accessibility issues
testA11y('ds-item-access-control');
});
});
describe('Edit Item > Collection Mapper tab', () => {
it('should pass accessibility tests', () => {
cy.get('a[data-test="mapper"]').click();
// <ds-item-collection-mapper> tag must be loaded
cy.get('ds-item-collection-mapper').should('be.visible');
// Analyze entire page for accessibility issues
testA11y('ds-item-collection-mapper');
// Click on the "Map new collections" tab
cy.get('li[data-test="mapTab"] a').click();
// Make sure search form is now visible
cy.get('ds-search-form').should('be.visible');
// Analyze entire page (again) for accessibility issues
testA11y('ds-item-collection-mapper');
});
});

View File

@@ -1,10 +1,8 @@
import { Options } from 'cypress-axe';
import { TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e';
import { testA11y } from 'cypress/support/utils';
describe('Item Page', () => {
const ITEMPAGE = '/items/'.concat(TEST_ENTITY_PUBLICATION);
const ENTITYPAGE = '/entities/publication/'.concat(TEST_ENTITY_PUBLICATION);
const ITEMPAGE = '/items/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'));
const ENTITYPAGE = '/entities/publication/'.concat(Cypress.env('DSPACE_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', () => {
@@ -19,13 +17,16 @@ describe('Item Page', () => {
cy.get('ds-item-page').should('be.visible');
// 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
);
testA11y('ds-item-page');
});
it('should pass accessibility tests on full item page', () => {
cy.visit(ENTITYPAGE + '/full');
// <ds-full-item-page> tag must be loaded
cy.get('ds-full-item-page').should('be.visible');
// Analyze <ds-full-item-page> for accessibility issues
testA11y('ds-full-item-page');
});
});

View File

@@ -1,12 +1,12 @@
import { REGEX_MATCH_NON_EMPTY_TEXT, TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e';
import { REGEX_MATCH_NON_EMPTY_TEXT } from 'cypress/support/e2e';
import { testA11y } from 'cypress/support/utils';
describe('Item Statistics Page', () => {
const ITEMSTATISTICSPAGE = '/statistics/items/'.concat(TEST_ENTITY_PUBLICATION);
const ITEMSTATISTICSPAGE = '/statistics/items/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'));
it('should load if you click on "Statistics" from an Item/Entity page', () => {
cy.visit('/entities/publication/'.concat(TEST_ENTITY_PUBLICATION));
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
cy.visit('/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')));
cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click();
cy.location('pathname').should('eq', ITEMSTATISTICSPAGE);
});
@@ -24,7 +24,7 @@ describe('Item Statistics Page', () => {
it('should contain a "Total visits per month" section', () => {
cy.visit(ITEMSTATISTICSPAGE);
// Check just for existence because this table is empty in CI environment as it's historical data
cy.get('.'.concat(TEST_ENTITY_PUBLICATION).concat('_TotalVisitsPerMonth')).should('exist');
cy.get('.'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')).concat('_TotalVisitsPerMonth')).should('exist');
});
it('should pass accessibility tests', () => {

View File

@@ -1,33 +1,33 @@
import { TEST_ADMIN_PASSWORD, TEST_ADMIN_USER, TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e';
import { testA11y } from 'cypress/support/utils';
const page = {
openLoginMenu() {
// Click the "Log In" dropdown menu in header
cy.get('ds-themed-navbar [data-test="login-menu"]').click();
cy.get('ds-themed-header [data-test="login-menu"]').click();
},
openUserMenu() {
// Once logged in, click the User menu in header
cy.get('ds-themed-navbar [data-test="user-menu"]').click();
cy.get('ds-themed-header [data-test="user-menu"]').click();
},
submitLoginAndPasswordByPressingButton(email, password) {
// Enter email
cy.get('ds-themed-navbar [data-test="email"]').type(email);
cy.get('ds-themed-header [data-test="email"]').type(email);
// Enter password
cy.get('ds-themed-navbar [data-test="password"]').type(password);
cy.get('ds-themed-header [data-test="password"]').type(password);
// Click login button
cy.get('ds-themed-navbar [data-test="login-button"]').click();
cy.get('ds-themed-header [data-test="login-button"]').click();
},
submitLoginAndPasswordByPressingEnter(email, password) {
// In opened Login modal, fill out email & password, then click Enter
cy.get('ds-themed-navbar [data-test="email"]').type(email);
cy.get('ds-themed-navbar [data-test="password"]').type(password);
cy.get('ds-themed-navbar [data-test="password"]').type('{enter}');
cy.get('ds-themed-header [data-test="email"]').type(email);
cy.get('ds-themed-header [data-test="password"]').type(password);
cy.get('ds-themed-header [data-test="password"]').type('{enter}');
},
submitLogoutByPressingButton() {
// This is the POST command that will actually log us out
cy.intercept('POST', '/server/api/authn/logout').as('logout');
// Click logout button
cy.get('ds-themed-navbar [data-test="logout-button"]').click();
cy.get('ds-themed-header [data-test="logout-button"]').click();
// Wait until above POST command responds before continuing
// (This ensures next action waits until logout completes)
cy.wait('@logout');
@@ -36,7 +36,7 @@ const page = {
describe('Login Modal', () => {
it('should login when clicking button & stay on same page', () => {
const ENTITYPAGE = '/entities/publication/'.concat(TEST_ENTITY_PUBLICATION);
const ENTITYPAGE = '/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'));
cy.visit(ENTITYPAGE);
// Login menu should exist
@@ -46,7 +46,7 @@ describe('Login Modal', () => {
page.openLoginMenu();
cy.get('.form-login').should('be.visible');
page.submitLoginAndPasswordByPressingButton(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD);
page.submitLoginAndPasswordByPressingButton(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
cy.get('ds-log-in').should('not.exist');
// Verify we are still on the same page
@@ -66,7 +66,7 @@ describe('Login Modal', () => {
cy.get('.form-login').should('be.visible');
// Login, and the <ds-log-in> tag should no longer exist
page.submitLoginAndPasswordByPressingEnter(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD);
page.submitLoginAndPasswordByPressingEnter(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
cy.get('.form-login').should('not.exist');
// Verify we are still on homepage
@@ -80,7 +80,7 @@ describe('Login Modal', () => {
it('should support logout', () => {
// First authenticate & access homepage
cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD);
cy.login(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
cy.visit('/');
// Verify ds-log-in tag doesn't exist, but ds-log-out tag does exist
@@ -102,12 +102,15 @@ describe('Login Modal', () => {
page.openLoginMenu();
// Registration link should be visible
cy.get('ds-themed-navbar [data-test="register"]').should('be.visible');
cy.get('ds-themed-header [data-test="register"]').should('be.visible');
// Click registration link & you should go to registration page
cy.get('ds-themed-navbar [data-test="register"]').click();
cy.get('ds-themed-header [data-test="register"]').click();
cy.location('pathname').should('eq', '/register');
cy.get('ds-register-email').should('exist');
// Test accessibility of this page
testA11y('ds-register-email');
});
it('should allow forgot password', () => {
@@ -116,11 +119,32 @@ describe('Login Modal', () => {
page.openLoginMenu();
// Forgot password link should be visible
cy.get('ds-themed-navbar [data-test="forgot"]').should('be.visible');
cy.get('ds-themed-header [data-test="forgot"]').should('be.visible');
// Click link & you should go to Forgot Password page
cy.get('ds-themed-navbar [data-test="forgot"]').click();
cy.get('ds-themed-header [data-test="forgot"]').click();
cy.location('pathname').should('eq', '/forgot');
cy.get('ds-forgot-email').should('exist');
// Test accessibility of this page
testA11y('ds-forgot-email');
});
it('should pass accessibility tests in menus', () => {
cy.visit('/');
// Open login menu & verify accessibility
page.openLoginMenu();
cy.get('ds-log-in').should('exist');
testA11y('ds-log-in');
// Now login
page.submitLoginAndPasswordByPressingButton(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
cy.get('ds-log-in').should('not.exist');
// Open user menu, verify user menu accesibility
page.openUserMenu();
cy.get('ds-user-menu').should('be.visible');
testA11y('ds-user-menu');
});
});

View File

@@ -1,5 +1,3 @@
import { Options } from 'cypress-axe';
import { TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD, TEST_SUBMIT_COLLECTION_NAME } from 'cypress/support/e2e';
import { testA11y } from 'cypress/support/utils';
describe('My DSpace page', () => {
@@ -7,7 +5,7 @@ describe('My DSpace page', () => {
cy.visit('/mydspace');
// This page is restricted, so we will be shown the login form. Fill it out & submit.
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
cy.get('ds-my-dspace-page').should('be.visible');
@@ -19,28 +17,14 @@ describe('My DSpace page', () => {
cy.get('.filter-toggle').click({ multiple: true });
// Analyze <ds-my-dspace-page> for accessibility issues
testA11y(
{
include: ['ds-my-dspace-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
);
testA11y('ds-my-dspace-page');
});
it('should have a working detailed view that passes accessibility tests', () => {
cy.visit('/mydspace');
// This page is restricted, so we will be shown the login form. Fill it out & submit.
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
cy.get('ds-my-dspace-page').should('be.visible');
@@ -49,16 +33,8 @@ describe('My DSpace page', () => {
cy.get('ds-object-detail').should('be.visible');
// Analyze <ds-search-page> for accessibility issues
testA11y('ds-my-dspace-page',
{
rules: {
// Search filters fail these two "moderate" impact rules
'heading-order': { enabled: false },
'landmark-unique': { enabled: false }
}
} as Options
);
// Analyze <ds-my-dspace-page> for accessibility issues
testA11y('ds-my-dspace-page');
});
// NOTE: Deleting existing submissions is exercised by submission.spec.ts
@@ -66,7 +42,7 @@ describe('My DSpace page', () => {
cy.visit('/mydspace');
// This page is restricted, so we will be shown the login form. Fill it out & submit.
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
// Open the New Submission dropdown
cy.get('button[data-test="submission-dropdown"]').click();
@@ -77,10 +53,10 @@ describe('My DSpace page', () => {
cy.get('ds-create-item-parent-selector').should('be.visible');
// Type in a known Collection name in the search box
cy.get('ds-authorized-collection-selector input[type="search"]').type(TEST_SUBMIT_COLLECTION_NAME);
cy.get('ds-authorized-collection-selector input[type="search"]').type(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME'));
// Click on the button matching that known Collection name
cy.get('ds-authorized-collection-selector button[title="'.concat(TEST_SUBMIT_COLLECTION_NAME).concat('"]')).click();
cy.get('ds-authorized-collection-selector button[title="'.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME')).concat('"]')).click();
// New URL should include /workspaceitems, as we've started a new submission
cy.url().should('include', '/workspaceitems');
@@ -89,7 +65,7 @@ describe('My DSpace page', () => {
cy.get('ds-submission-edit').should('be.visible');
// A Collection menu button should exist & its value should be the selected collection
cy.get('#collectionControlsMenuButton span').should('have.text', TEST_SUBMIT_COLLECTION_NAME);
cy.get('#collectionControlsMenuButton span').should('have.text', Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME'));
// Now that we've created a submission, we'll test that we can go back and Edit it.
// Get our Submission URL, to parse out the ID of this new submission
@@ -138,7 +114,7 @@ describe('My DSpace page', () => {
cy.visit('/mydspace');
// This page is restricted, so we will be shown the login form. Fill it out & submit.
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
// Open the New Import dropdown
cy.get('button[data-test="import-dropdown"]').click();
@@ -150,6 +126,9 @@ describe('My DSpace page', () => {
// The external import searchbox should be visible
cy.get('ds-submission-import-external-searchbar').should('be.visible');
// Test for accessibility issues
testA11y('ds-submission-import-external');
});
});

View File

@@ -1,8 +1,13 @@
import { testA11y } from 'cypress/support/utils';
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('be.visible');
// Analyze <ds-pagenotfound> for accessibility issues
testA11y('ds-pagenotfound');
});
it('should not contain element ds-pagenotfound when navigating to existing page', () => {

View File

@@ -1,23 +1,21 @@
import { TEST_SEARCH_TERM } from 'cypress/support/e2e';
const page = {
fillOutQueryInNavBar(query) {
// Click the magnifying glass
cy.get('ds-themed-navbar [data-test="header-search-icon"]').click();
cy.get('ds-themed-header [data-test="header-search-icon"]').click();
// Fill out a query in input that appears
cy.get('ds-themed-navbar [data-test="header-search-box"]').type(query);
cy.get('ds-themed-header [data-test="header-search-box"]').type(query);
},
submitQueryByPressingEnter() {
cy.get('ds-themed-navbar [data-test="header-search-box"]').type('{enter}');
cy.get('ds-themed-header [data-test="header-search-box"]').type('{enter}');
},
submitQueryByPressingIcon() {
cy.get('ds-themed-navbar [data-test="header-search-icon"]').click();
cy.get('ds-themed-header [data-test="header-search-icon"]').click();
}
};
describe('Search from Navigation Bar', () => {
// NOTE: these tests currently assume this query will return results!
const query = TEST_SEARCH_TERM;
const query = Cypress.env('DSPACE_TEST_SEARCH_TERM');
it('should go to search page with correct query if submitted (from home)', () => {
cy.visit('/');

View File

@@ -1,8 +1,10 @@
import { Options } from 'cypress-axe';
import { TEST_SEARCH_TERM } from 'cypress/support/e2e';
import { testA11y } from 'cypress/support/utils';
describe('Search Page', () => {
// NOTE: these tests currently assume this query will return results!
const query = Cypress.env('DSPACE_TEST_SEARCH_TERM');
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');
@@ -13,8 +15,8 @@ describe('Search Page', () => {
});
it('should load results and pass accessibility tests', () => {
cy.visit('/search?query='.concat(TEST_SEARCH_TERM));
cy.get('[data-test="search-box"]').should('have.value', TEST_SEARCH_TERM);
cy.visit('/search?query='.concat(query));
cy.get('[data-test="search-box"]').should('have.value', query);
// <ds-search-page> tag must be loaded
cy.get('ds-search-page').should('be.visible');
@@ -27,25 +29,11 @@ describe('Search Page', () => {
cy.get('[data-test="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
);
testA11y('ds-search-page');
});
it('should have a working grid view that passes accessibility tests', () => {
cy.visit('/search?query='.concat(TEST_SEARCH_TERM));
cy.visit('/search?query='.concat(query));
// Click button in sidebar to display grid view
cy.get('ds-search-sidebar [data-test="grid-view"]').click();
@@ -60,9 +48,8 @@ describe('Search Page', () => {
testA11y('ds-search-page',
{
rules: {
// Search filters fail these two "moderate" impact rules
'heading-order': { enabled: false },
'landmark-unique': { enabled: false }
// Card titles fail this test currently
'heading-order': { enabled: false }
}
} as Options
);

View File

@@ -1,14 +1,16 @@
import { TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD, TEST_SUBMIT_COLLECTION_NAME, TEST_SUBMIT_COLLECTION_UUID } from 'cypress/support/e2e';
import { testA11y } from 'cypress/support/utils';
//import { TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD, TEST_SUBMIT_COLLECTION_NAME, TEST_SUBMIT_COLLECTION_UUID, TEST_ADMIN_USER, TEST_ADMIN_PASSWORD } from 'cypress/support/e2e';
import { Options } from 'cypress-axe';
describe('New Submission page', () => {
// NOTE: We already test that new submissions can be started from MyDSpace in my-dspace.spec.ts
// NOTE: We already test that new Item submissions can be started from MyDSpace in my-dspace.spec.ts
it('should create a new submission when using /submit path & pass accessibility', () => {
// Test that calling /submit with collection & entityType will create a new submission
cy.visit('/submit?collection='.concat(TEST_SUBMIT_COLLECTION_UUID).concat('&entityType=none'));
cy.visit('/submit?collection='.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID')).concat('&entityType=none'));
// This page is restricted, so we will be shown the login form. Fill it out & submit.
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
// Should redirect to /workspaceitems, as we've started a new submission
cy.url().should('include', '/workspaceitems');
@@ -17,7 +19,7 @@ describe('New Submission page', () => {
cy.get('ds-submission-edit').should('be.visible');
// A Collection menu button should exist & it's value should be the selected collection
cy.get('#collectionControlsMenuButton span').should('have.text', TEST_SUBMIT_COLLECTION_NAME);
cy.get('#collectionControlsMenuButton span').should('have.text', Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME'));
// 4 sections should be visible by default
cy.get('div#section_traditionalpageone').should('be.visible');
@@ -25,6 +27,25 @@ describe('New Submission page', () => {
cy.get('div#section_upload').should('be.visible');
cy.get('div#section_license').should('be.visible');
// Test entire page for accessibility
testA11y('ds-submission-edit',
{
rules: {
// Author & Subject fields have invalid "aria-multiline" attrs.
// See https://github.com/DSpace/dspace-angular/issues/1272
'aria-allowed-attr': { enabled: false },
// All panels are accordians & fail "aria-required-children" and "nested-interactive".
// Seem to require updating ng-bootstrap and https://github.com/DSpace/dspace-angular/issues/2216
'aria-required-children': { enabled: false },
'nested-interactive': { enabled: false },
// All select boxes fail to have a name / aria-label.
// This is a bug in ng-dynamic-forms and may require https://github.com/DSpace/dspace-angular/issues/2216
'select-name': { enabled: false },
}
} as Options
);
// Discard button should work
// Clicking it will display a confirmation, which we will confirm with another click
cy.get('button#discard').click();
@@ -33,10 +54,10 @@ describe('New Submission page', () => {
it('should block submission & show errors if required fields are missing', () => {
// Create a new submission
cy.visit('/submit?collection='.concat(TEST_SUBMIT_COLLECTION_UUID).concat('&entityType=none'));
cy.visit('/submit?collection='.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID')).concat('&entityType=none'));
// This page is restricted, so we will be shown the login form. Fill it out & submit.
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
// Attempt an immediate deposit without filling out any fields
cy.get('button#deposit').click();
@@ -93,10 +114,10 @@ describe('New Submission page', () => {
it('should allow for deposit if all required fields completed & file uploaded', () => {
// Create a new submission
cy.visit('/submit?collection='.concat(TEST_SUBMIT_COLLECTION_UUID).concat('&entityType=none'));
cy.visit('/submit?collection='.concat(Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID')).concat('&entityType=none'));
// This page is restricted, so we will be shown the login form. Fill it out & submit.
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
cy.loginViaForm(Cypress.env('DSPACE_TEST_SUBMIT_USER'), Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD'));
// Fill out all required fields (Title, Date)
cy.get('input#dc_title').type('DSpace logo uploaded via e2e tests');
@@ -116,7 +137,7 @@ describe('New Submission page', () => {
// Upload our DSpace logo via drag & drop onto submission form
// cy.get('div#section_upload')
cy.get('div.ds-document-drop-zone').selectFile('src/assets/images/dspace-logo.png', {
cy.get('div.ds-document-drop-zone').selectFile('src/assets/images/dspace-logo.svg', {
action: 'drag-drop'
});
@@ -131,4 +152,76 @@ describe('New Submission page', () => {
cy.get('ds-notification div.alert-success').should('be.visible');
});
it('is possible to submit a new "Person" and that form passes accessibility', () => {
// To submit a different entity type, we'll start from MyDSpace
cy.visit('/mydspace');
// This page is restricted, so we will be shown the login form. Fill it out & submit.
// NOTE: At this time, we MUST login as admin to submit Person objects
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
// Open the New Submission dropdown
cy.get('button[data-test="submission-dropdown"]').click();
// Click on the "Person" type in that dropdown
cy.get('#entityControlsDropdownMenu button[title="Person"]').click();
// This should display the <ds-create-item-parent-selector> (popup window)
cy.get('ds-create-item-parent-selector').should('be.visible');
// Type in a known Collection name in the search box
cy.get('ds-authorized-collection-selector input[type="search"]').type(Cypress.env('DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME'));
// Click on the button matching that known Collection name
cy.get('ds-authorized-collection-selector button[title="'.concat(Cypress.env('DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME')).concat('"]')).click();
// New URL should include /workspaceitems, as we've started a new submission
cy.url().should('include', '/workspaceitems');
// The Submission edit form tag should be visible
cy.get('ds-submission-edit').should('be.visible');
// A Collection menu button should exist & its value should be the selected collection
cy.get('#collectionControlsMenuButton span').should('have.text', Cypress.env('DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME'));
// 3 sections should be visible by default
cy.get('div#section_personStep').should('be.visible');
cy.get('div#section_upload').should('be.visible');
cy.get('div#section_license').should('be.visible');
// Test entire page for accessibility
testA11y('ds-submission-edit',
{
rules: {
// All panels are accordians & fail "aria-required-children" and "nested-interactive".
// Seem to require updating ng-bootstrap and https://github.com/DSpace/dspace-angular/issues/2216
'aria-required-children': { enabled: false },
'nested-interactive': { enabled: false },
}
} as Options
);
// Click the lookup button next to "Publication" field
cy.get('button[data-test="lookup-button"]').click();
// A popup modal window should be visible
cy.get('ds-dynamic-lookup-relation-modal').should('be.visible');
// Popup modal should also pass accessibility tests
//testA11y('ds-dynamic-lookup-relation-modal');
testA11y({
include: ['ds-dynamic-lookup-relation-modal'],
exclude: [
['ul.nav-tabs'] // Tabs at top of model have several issues which seem to be caused by ng-bootstrap
],
});
// Close popup window
cy.get('ds-dynamic-lookup-relation-modal button.close').click();
// Back on the form, click the discard button to remove new submission
// Clicking it will display a confirmation, which we will confirm with another click
cy.get('button#discard').click();
cy.get('button#discard_submit').click();
});
});

View File

@@ -1,5 +1,11 @@
const fs = require('fs');
// These two global variables are used to store information about the REST API used
// by these e2e tests. They are filled out prior to running any tests in the before()
// method of e2e.ts. They can then be accessed by any tests via the getters below.
let REST_BASE_URL: string;
let REST_DOMAIN: string;
// 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) => {
@@ -30,6 +36,24 @@ module.exports = (on, config) => {
}
return null;
},
// Save value of REST Base URL, looked up before all tests.
// This allows other tests to use it easily via getRestBaseURL() below.
saveRestBaseURL(url: string) {
return (REST_BASE_URL = url);
},
// Retrieve currently saved value of REST Base URL
getRestBaseURL() {
return REST_BASE_URL ;
},
// Save value of REST Domain, looked up before all tests.
// This allows other tests to use it easily via getRestBaseDomain() below.
saveRestBaseDomain(domain: string) {
return (REST_DOMAIN = domain);
},
// Retrieve currently saved value of REST Domain
getRestBaseDomain() {
return REST_DOMAIN ;
}
});
};

View File

@@ -5,11 +5,7 @@
import { AuthTokenInfo, TOKENITEM } from 'src/app/core/auth/models/auth-token-info.model';
import { DSPACE_XSRF_COOKIE, XSRF_REQUEST_HEADER } from 'src/app/core/xsrf/xsrf.constants';
// NOTE: FALLBACK_TEST_REST_BASE_URL is only used if Cypress cannot read the REST API BaseURL
// from the Angular UI's config.json. See 'login()'.
export const FALLBACK_TEST_REST_BASE_URL = 'http://localhost:8080/server';
export const FALLBACK_TEST_REST_DOMAIN = 'localhost';
import { v4 as uuidv4 } from 'uuid';
// Declare Cypress namespace to help with Intellisense & code completion in IDEs
// ALL custom commands MUST be listed here for code completion to work
@@ -41,6 +37,13 @@ declare global {
* @param dsoType type of DSpace Object (e.g. "item", "collection", "community")
*/
generateViewEvent(uuid: string, dsoType: string): typeof generateViewEvent;
/**
* Create a new CSRF token and add to required Cookie. CSRF Token is returned
* in chainable in order to allow it to be sent also in required CSRF header.
* @returns Chainable reference to allow CSRF token to also be sent in header.
*/
createCSRFCookie(): Chainable<any>;
}
}
}
@@ -54,59 +57,32 @@ declare global {
* @param password password to login as
*/
function login(email: string, password: string): void {
// Cypress doesn't have access to the running application in Node.js.
// So, it's not possible to inject or load the AppConfig or environment of the Angular UI.
// Instead, we'll read our running application's config.json, which contains the configs &
// is regenerated at runtime each time the Angular UI application starts up.
cy.task('readUIConfig').then((str: string) => {
// Parse config into a JSON object
const config = JSON.parse(str);
// Create a fake CSRF cookie/token to use in POST
cy.createCSRFCookie().then((csrfToken: string) => {
// get our REST API's base URL, also needed for POST
cy.task('getRestBaseURL').then((baseRestUrl: string) => {
// Now, send login POST request including that CSRF token
cy.request({
method: 'POST',
url: baseRestUrl + '/api/authn/login',
headers: { [XSRF_REQUEST_HEADER]: csrfToken},
form: true, // indicates the body should be form urlencoded
body: { user: email, password: password }
}).then((resp) => {
// We expect a successful login
expect(resp.status).to.eq(200);
// We expect to have a valid authorization header returned (with our auth token)
expect(resp.headers).to.have.property('authorization');
// Find the URL of our REST API. Have a fallback ready, just in case 'rest.baseUrl' cannot be found.
let baseRestUrl = FALLBACK_TEST_REST_BASE_URL;
if (!config.rest.baseUrl) {
console.warn("Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL);
} else {
//console.log("Found 'rest.baseUrl' in config.json. Using this REST API for login: ".concat(config.rest.baseUrl));
baseRestUrl = config.rest.baseUrl;
}
// Initialize our AuthTokenInfo object from the authorization header.
const authheader = resp.headers.authorization as string;
const authinfo: AuthTokenInfo = new AuthTokenInfo(authheader);
// Now find domain of our REST API, again with a fallback.
let baseDomain = FALLBACK_TEST_REST_DOMAIN;
if (!config.rest.host) {
console.warn("Could not load 'rest.host' from config.json. Falling back to " + FALLBACK_TEST_REST_DOMAIN);
} else {
baseDomain = config.rest.host;
}
// Create a fake CSRF Token. Set it in the required server-side cookie
const csrfToken = 'fakeLoginCSRFToken';
cy.setCookie(DSPACE_XSRF_COOKIE, csrfToken, { 'domain': baseDomain });
// Now, send login POST request including that CSRF token
cy.request({
method: 'POST',
url: baseRestUrl + '/api/authn/login',
headers: { [XSRF_REQUEST_HEADER]: csrfToken},
form: true, // indicates the body should be form urlencoded
body: { user: email, password: password }
}).then((resp) => {
// We expect a successful login
expect(resp.status).to.eq(200);
// We expect to have a valid authorization header returned (with our auth token)
expect(resp.headers).to.have.property('authorization');
// Initialize our AuthTokenInfo object from the authorization header.
const authheader = resp.headers.authorization as string;
const authinfo: AuthTokenInfo = new AuthTokenInfo(authheader);
// Save our AuthTokenInfo object to our dsAuthInfo UI cookie
// This ensures the UI will recognize we are logged in on next "visit()"
cy.setCookie(TOKENITEM, JSON.stringify(authinfo));
// Save our AuthTokenInfo object to our dsAuthInfo UI cookie
// This ensures the UI will recognize we are logged in on next "visit()"
cy.setCookie(TOKENITEM, JSON.stringify(authinfo));
});
});
// Remove cookie with fake CSRF token, as it's no longer needed
cy.clearCookie(DSPACE_XSRF_COOKIE);
});
}
// Add as a Cypress command (i.e. assign to 'cy.login')
@@ -141,54 +117,53 @@ Cypress.Commands.add('loginViaForm', loginViaForm);
* @param dsoType type of DSpace Object (e.g. "item", "collection", "community")
*/
function generateViewEvent(uuid: string, dsoType: string): void {
// Cypress doesn't have access to the running application in Node.js.
// So, it's not possible to inject or load the AppConfig or environment of the Angular UI.
// Instead, we'll read our running application's config.json, which contains the configs &
// is regenerated at runtime each time the Angular UI application starts up.
cy.task('readUIConfig').then((str: string) => {
// Parse config into a JSON object
const config = JSON.parse(str);
// Find the URL of our REST API. Have a fallback ready, just in case 'rest.baseUrl' cannot be found.
let baseRestUrl = FALLBACK_TEST_REST_BASE_URL;
if (!config.rest.baseUrl) {
console.warn("Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL);
} else {
baseRestUrl = config.rest.baseUrl;
}
// Now find domain of our REST API, again with a fallback.
let baseDomain = FALLBACK_TEST_REST_DOMAIN;
if (!config.rest.host) {
console.warn("Could not load 'rest.host' from config.json. Falling back to " + FALLBACK_TEST_REST_DOMAIN);
} else {
baseDomain = config.rest.host;
}
// Create a fake CSRF Token. Set it in the required server-side cookie
const csrfToken = 'fakeGenerateViewEventCSRFToken';
cy.setCookie(DSPACE_XSRF_COOKIE, csrfToken, { 'domain': baseDomain });
// Now, send 'statistics/viewevents' POST request including that fake CSRF token in required header
cy.request({
method: 'POST',
url: baseRestUrl + '/api/statistics/viewevents',
headers: {
[XSRF_REQUEST_HEADER] : csrfToken,
// use a known public IP address to avoid being seen as a "bot"
'X-Forwarded-For': '1.1.1.1',
},
//form: true, // indicates the body should be form urlencoded
body: { targetId: uuid, targetType: dsoType },
}).then((resp) => {
// We expect a 201 (which means statistics event was created)
expect(resp.status).to.eq(201);
// Create a fake CSRF cookie/token to use in POST
cy.createCSRFCookie().then((csrfToken: string) => {
// get our REST API's base URL, also needed for POST
cy.task('getRestBaseURL').then((baseRestUrl: string) => {
// Now, send 'statistics/viewevents' POST request including that fake CSRF token in required header
cy.request({
method: 'POST',
url: baseRestUrl + '/api/statistics/viewevents',
headers: {
[XSRF_REQUEST_HEADER] : csrfToken,
// use a known public IP address to avoid being seen as a "bot"
'X-Forwarded-For': '1.1.1.1',
// Use a user-agent of a Firefox browser on Windows. This again avoids being seen as a "bot"
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0',
},
//form: true, // indicates the body should be form urlencoded
body: { targetId: uuid, targetType: dsoType },
}).then((resp) => {
// We expect a 201 (which means statistics event was created)
expect(resp.status).to.eq(201);
});
});
// Remove cookie with fake CSRF token, as it's no longer needed
cy.clearCookie(DSPACE_XSRF_COOKIE);
});
}
// Add as a Cypress command (i.e. assign to 'cy.generateViewEvent')
Cypress.Commands.add('generateViewEvent', generateViewEvent);
/**
* Can be used by tests to generate a random XSRF/CSRF token and save it to
* the required XSRF/CSRF cookie for usage when sending POST requests or similar.
* The generated CSRF token is returned in a Chainable to allow it to be also sent
* in the CSRF HTTP Header.
* @returns a Cypress Chainable which can be used to get the generated CSRF Token
*/
function createCSRFCookie(): Cypress.Chainable {
// Generate a new token which is a random UUID
const csrfToken: string = uuidv4();
// Save it to our required cookie
cy.task('getRestBaseDomain').then((baseDomain: string) => {
// Create a fake CSRF Token. Set it in the required server-side cookie
cy.setCookie(DSPACE_XSRF_COOKIE, csrfToken, { 'domain': baseDomain });
});
// return the generated token wrapped in a chainable
return cy.wrap(csrfToken);
}
// Add as a Cypress command (i.e. assign to 'cy.createCSRFCookie')
Cypress.Commands.add('createCSRFCookie', createCSRFCookie);

View File

@@ -19,45 +19,54 @@ import './commands';
// Import Cypress Axe tools for all tests
// https://github.com/component-driven/cypress-axe
import 'cypress-axe';
import { DSPACE_XSRF_COOKIE } from 'src/app/core/xsrf/xsrf.constants';
// Runs once before all tests
before(() => {
// Cypress doesn't have access to the running application in Node.js.
// So, it's not possible to inject or load the AppConfig or environment of the Angular UI.
// Instead, we'll read our running application's config.json, which contains the configs &
// is regenerated at runtime each time the Angular UI application starts up.
cy.task('readUIConfig').then((str: string) => {
// Parse config into a JSON object
const config = JSON.parse(str);
// Find URL of our REST API & save to global variable via task
let baseRestUrl = FALLBACK_TEST_REST_BASE_URL;
if (!config.rest.baseUrl) {
console.warn("Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL);
} else {
baseRestUrl = config.rest.baseUrl;
}
cy.task('saveRestBaseURL', baseRestUrl);
// Find domain of our REST API & save to global variable via task.
let baseDomain = FALLBACK_TEST_REST_DOMAIN;
if (!config.rest.host) {
console.warn("Could not load 'rest.host' from config.json. Falling back to " + FALLBACK_TEST_REST_DOMAIN);
} else {
baseDomain = config.rest.host;
}
cy.task('saveRestBaseDomain', baseDomain);
});
});
// Runs once before the first test in each "block"
beforeEach(() => {
// Pre-agree to all Klaro cookies by setting the klaro-anonymous cookie
// This just ensures it doesn't get in the way of matching other objects in the page.
cy.setCookie('klaro-anonymous', '{%22authentication%22:true%2C%22preferences%22:true%2C%22acknowledgement%22:true%2C%22google-analytics%22:true%2C%22google-recaptcha%22:true}');
// Remove any CSRF cookies saved from prior tests
cy.clearCookie(DSPACE_XSRF_COOKIE);
});
// For better stability between tests, we visit "about:blank" (i.e. blank page) after each test.
// This ensures any remaining/outstanding XHR requests are killed, so they don't affect the next test.
// Borrowed from: https://glebbahmutov.com/blog/visit-blank-page-between-tests/
/*afterEach(() => {
cy.window().then((win) => {
win.location.href = 'about:blank';
});
});*/
// Global constants used in tests
// May be overridden in our cypress.json config file using specified environment variables.
// Default values listed here are all valid for the Demo Entities Data set available at
// https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
// (This is the data set used in our CI environment)
// Admin account used for administrative tests
export const TEST_ADMIN_USER = Cypress.env('DSPACE_TEST_ADMIN_USER') || 'dspacedemo+admin@gmail.com';
export const TEST_ADMIN_PASSWORD = Cypress.env('DSPACE_TEST_ADMIN_PASSWORD') || 'dspace';
// Community/collection/publication used for view/edit tests
export const TEST_COLLECTION = Cypress.env('DSPACE_TEST_COLLECTION') || '282164f5-d325-4740-8dd1-fa4d6d3e7200';
export const TEST_COMMUNITY = Cypress.env('DSPACE_TEST_COMMUNITY') || '0958c910-2037-42a9-81c7-dca80e3892b4';
export const TEST_ENTITY_PUBLICATION = Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION') || 'e98b0f27-5c19-49a0-960d-eb6ad5287067';
// Search term (should return results) used in search tests
export const TEST_SEARCH_TERM = Cypress.env('DSPACE_TEST_SEARCH_TERM') || 'test';
// Collection used for submission tests
export const TEST_SUBMIT_COLLECTION_NAME = Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME') || 'Sample Collection';
export const TEST_SUBMIT_COLLECTION_UUID = Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID') || '9d8334e9-25d3-4a67-9cea-3dffdef80144';
export const TEST_SUBMIT_USER = Cypress.env('DSPACE_TEST_SUBMIT_USER') || 'dspacedemo+submit@gmail.com';
export const TEST_SUBMIT_USER_PASSWORD = Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD') || 'dspace';
// NOTE: FALLBACK_TEST_REST_BASE_URL is only used if Cypress cannot read the REST API BaseURL
// from the Angular UI's config.json. See 'before()' above.
const FALLBACK_TEST_REST_BASE_URL = 'http://localhost:8080/server';
const FALLBACK_TEST_REST_DOMAIN = 'localhost';
// USEFUL REGEX for testing

View File

@@ -101,8 +101,8 @@ and the backend at http://localhost:8080/server/
## Run DSpace Angular dist build with DSpace Demo site backend
This allows you to run the Angular UI in *production* mode, pointing it at the demo backend
(https://api7.dspace.org/server/).
This allows you to run the Angular UI in *production* mode, pointing it at the demo or sandbox backend
(https://demo.dspace.org/server/ or https://sandbox.dspace.org/server/).
```
docker-compose -f docker/docker-compose-dist.yml pull

View File

@@ -12,15 +12,8 @@
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/cli.assetstore.yml
#
# Therefore, it should be kept in sync with that file
version: "3.7"
networks:
dspacenet:
services:
dspace-cli:
networks:
dspacenet: {}
environment:
# This assetstore zip is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
- LOADASSETS=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/assetstore.tar.gz

View File

@@ -12,8 +12,6 @@
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/cli.ingest.yml
#
# Therefore, it should be kept in sync with that file
version: "3.7"
services:
dspace-cli:
environment:

View File

@@ -12,8 +12,13 @@
# https://github.com/DSpace/DSpace/blob/main/docker-compose-cli.yml
#
# Therefore, it should be kept in sync with that file
version: "3.7"
networks:
# Default to using network named 'dspacenet' from docker-compose-rest.yml.
# Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet")
# If COMPOSITE_PROJECT_NAME is missing, default value will be "docker" (name of folder this file is in)
default:
name: ${COMPOSE_PROJECT_NAME:-docker}_dspacenet
external: true
services:
dspace-cli:
image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}"
@@ -30,16 +35,12 @@ services:
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
solr__P__server: http://dspacesolr:8983/solr
volumes:
- "assetstore:/dspace/assetstore"
# Keep DSpace assetstore directory between reboots
- assetstore:/dspace/assetstore
entrypoint: /dspace/bin/dspace
command: help
networks:
- dspacenet
tty: true
stdin_open: true
volumes:
assetstore:
networks:
dspacenet:

View File

@@ -12,8 +12,6 @@
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/db.entities.yml
#
# # Therefore, it should be kept in sync with that file
version: "3.7"
services:
dspacedb:
image: dspace/dspace-postgres-pgcrypto:loadsql

View File

@@ -10,7 +10,6 @@
# This is used by our GitHub CI at .github/workflows/build.yml
# It is based heavily on the Backend's Docker Compose:
# https://github.com/DSpace/DSpace/blob/main/docker-compose.yml
version: '3.7'
networks:
dspacenet:
services:
@@ -33,11 +32,12 @@ services:
# Tell Statistics to commit all views immediately instead of waiting on Solr's autocommit.
# This allows us to generate statistics in e2e tests so that statistics pages can be tested thoroughly.
solr__D__statistics__P__autoCommit: 'false'
LOGGING_CONFIG: /dspace/config/log4j2-container.xml
image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-test}"
depends_on:
- dspacedb
image: dspace/dspace:dspace-7_x-test
networks:
dspacenet:
- dspacenet
ports:
- published: 8080
target: 8080
@@ -45,8 +45,6 @@ services:
tty: true
volumes:
- assetstore:/dspace/assetstore
# Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below)
- solr_configs:/dspace/solr
# Ensure that the database is ready BEFORE starting tomcat
# 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep
# 2. Then, run database migration to init database tables (including any out-of-order ignored migrations, if any)
@@ -62,29 +60,30 @@ services:
# NOTE: This is customized to use our loadsql image, so that we are using a database with existing test data
dspacedb:
container_name: dspacedb
image: "${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x-loadsql}"
environment:
# This LOADSQL should be kept in sync with the LOADSQL in
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/db.entities.yml
# This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
LOADSQL: https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql
PGDATA: /pgdata
image: dspace/dspace-postgres-pgcrypto:loadsql
POSTGRES_PASSWORD: dspace
networks:
dspacenet:
- dspacenet
ports:
- published: 5432
target: 5432
stdin_open: true
tty: true
volumes:
# Keep Postgres data directory between reboots
- pgdata:/pgdata
# DSpace Solr container
dspacesolr:
container_name: dspacesolr
# Uses official Solr image at https://hub.docker.com/_/solr/
image: solr:8.11-slim
# Needs main 'dspace' container to start first to guarantee access to solr_configs
depends_on:
- dspace
image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-7_x}"
networks:
dspacenet:
- dspacenet
ports:
- published: 8983
target: 8983
@@ -92,9 +91,6 @@ services:
tty: true
working_dir: /var/solr/data
volumes:
# Mount our "solr_configs" volume available under the Solr's configsets folder (in a 'dspace' subfolder)
# This copies the Solr configs from main 'dspace' container into 'dspacesolr' via that volume
- solr_configs:/opt/solr/server/solr/configsets/dspace
# Keep Solr data directory between reboots
- solr_data:/var/solr/data
# Initialize all DSpace Solr cores using the mounted configsets (see above), then start Solr
@@ -103,14 +99,16 @@ services:
- '-c'
- |
init-var-solr
precreate-core authority /opt/solr/server/solr/configsets/dspace/authority
precreate-core oai /opt/solr/server/solr/configsets/dspace/oai
precreate-core search /opt/solr/server/solr/configsets/dspace/search
precreate-core statistics /opt/solr/server/solr/configsets/dspace/statistics
precreate-core authority /opt/solr/server/solr/configsets/authority
cp -r /opt/solr/server/solr/configsets/authority/* authority
precreate-core oai /opt/solr/server/solr/configsets/oai
cp -r /opt/solr/server/solr/configsets/oai/* oai
precreate-core search /opt/solr/server/solr/configsets/search
cp -r /opt/solr/server/solr/configsets/search/* search
precreate-core statistics /opt/solr/server/solr/configsets/statistics
cp -r /opt/solr/server/solr/configsets/statistics/* statistics
exec solr -f
volumes:
assetstore:
pgdata:
solr_data:
# Special volume used to share Solr configs from 'dspace' to 'dspacesolr' container (see above)
solr_configs:

View File

@@ -8,7 +8,6 @@
# Docker Compose for running the DSpace Angular UI dist build
# for previewing with the DSpace Demo site backend
version: '3.7'
networks:
dspacenet:
services:
@@ -24,7 +23,7 @@ services:
# This is because Server Side Rendering (SSR) currently requires a public URL,
# see this bug: https://github.com/DSpace/dspace-angular/issues/1485
DSPACE_REST_SSL: 'true'
DSPACE_REST_HOST: api7.dspace.org
DSPACE_REST_HOST: demo.dspace.org
DSPACE_REST_PORT: 443
DSPACE_REST_NAMESPACE: /server
image: dspace/dspace-angular:dspace-7_x-dist

View File

@@ -10,7 +10,6 @@
# This is based heavily on the docker-compose.yml that is available in the DSpace/DSpace
# (Backend) at:
# https://github.com/DSpace/DSpace/blob/main/docker-compose.yml
version: '3.7'
networks:
dspacenet:
ipam:
@@ -29,8 +28,9 @@ services:
# __D__ => "-" (e.g. google__D__metadata => google-metadata)
# dspace.dir, dspace.server.url, dspace.ui.url and dspace.name
dspace__P__dir: /dspace
dspace__P__server__P__url: http://localhost:8080/server
dspace__P__ui__P__url: http://localhost:4000
# Uncomment to set a non-default value for dspace.server.url or dspace.ui.url
# dspace__P__server__P__url: http://localhost:8080/server
# dspace__P__ui__P__url: http://localhost:4000
dspace__P__name: 'DSpace Started with Docker Compose'
# db.url: Ensure we are using the 'dspacedb' image for our database
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
@@ -39,11 +39,12 @@ services:
# proxies.trusted.ipranges: This setting is required for a REST API running in Docker to trust requests
# from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above.
proxies__P__trusted__P__ipranges: '172.23.0'
LOGGING_CONFIG: /dspace/config/log4j2-container.xml
image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-test}"
depends_on:
- dspacedb
networks:
dspacenet:
- dspacenet
ports:
- published: 8080
target: 8080
@@ -51,8 +52,6 @@ services:
tty: true
volumes:
- assetstore:/dspace/assetstore
# Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below)
- solr_configs:/dspace/solr
# Ensure that the database is ready BEFORE starting tomcat
# 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep
# 2. Then, run database migration to init database tables
@@ -67,27 +66,27 @@ services:
# DSpace database container
dspacedb:
container_name: dspacedb
# Uses a custom Postgres image with pgcrypto installed
image: "${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x}"
environment:
PGDATA: /pgdata
image: dspace/dspace-postgres-pgcrypto
POSTGRES_PASSWORD: dspace
networks:
dspacenet:
- dspacenet
ports:
- published: 5432
target: 5432
stdin_open: true
tty: true
volumes:
# Keep Postgres data directory between reboots
- pgdata:/pgdata
# DSpace Solr container
dspacesolr:
container_name: dspacesolr
image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-7_x}"
# Needs main 'dspace' container to start first to guarantee access to solr_configs
depends_on:
- dspace
networks:
dspacenet:
- dspacenet
ports:
- published: 8983
target: 8983
@@ -120,5 +119,3 @@ volumes:
assetstore:
pgdata:
solr_data:
# Special volume used to share Solr configs from 'dspace' to 'dspacesolr' container (see above)
solr_configs:

View File

@@ -9,7 +9,6 @@
# Docker Compose for running the DSpace Angular UI for testing/development
# Requires also running a REST API backend (either locally or remotely),
# for example via 'docker-compose-rest.yml'
version: '3.7'
networks:
dspacenet:
services:

View File

@@ -48,7 +48,7 @@ dspace-angular connects to your DSpace installation by using its REST endpoint.
```yaml
rest:
ssl: true
host: api7.dspace.org
host: demo.dspace.org
port: 443
nameSpace: /server
}
@@ -57,7 +57,7 @@ rest:
Alternately you can set the following environment variables. If any of these are set, it will override all configuration files:
```
DSPACE_REST_SSL=true
DSPACE_REST_HOST=api7.dspace.org
DSPACE_REST_HOST=demo.dspace.org
DSPACE_REST_PORT=443
DSPACE_REST_NAMESPACE=/server
```

View File

@@ -1,6 +1,6 @@
{
"name": "dspace-angular",
"version": "7.6.0",
"version": "7.6.2",
"scripts": {
"ng": "ng",
"config:watch": "nodemon",
@@ -15,14 +15,14 @@
"analyze": "webpack-bundle-analyzer dist/browser/stats.json",
"build": "ng build --configuration development",
"build:stats": "ng build --stats-json",
"build:prod": "yarn run build:ssr",
"build:prod": "cross-env NODE_ENV=production yarn run build:ssr",
"build:ssr": "ng build --configuration production && ng run dspace-angular:server:production",
"test": "ng test --source-map=true --watch=false --configuration test",
"test:watch": "nodemon --exec \"ng test --source-map=true --watch=true --configuration test\"",
"test:headless": "ng test --source-map=true --watch=false --configuration test --browsers=ChromeHeadless --code-coverage",
"lint": "ng lint",
"lint-fix": "ng lint --fix=true",
"e2e": "ng e2e",
"e2e": "cross-env NODE_ENV=production ng e2e",
"clean:dev:config": "rimraf src/assets/config.json",
"clean:coverage": "rimraf coverage",
"clean:dist": "rimraf dist",
@@ -82,7 +82,7 @@
"@types/grecaptcha": "^3.0.4",
"angular-idle-preload": "3.0.0",
"angulartics2": "^12.2.0",
"axios": "^0.27.2",
"axios": "^1.6.0",
"bootstrap": "^4.6.1",
"cerialize": "0.1.18",
"cli-progress": "^3.12.0",
@@ -93,12 +93,13 @@
"date-fns": "^2.29.3",
"date-fns-tz": "^1.3.7",
"deepmerge": "^4.3.1",
"ejs": "^3.1.9",
"express": "^4.18.2",
"ejs": "^3.1.10",
"express": "^4.19.2",
"express-rate-limit": "^5.1.3",
"fast-json-patch": "^3.1.1",
"filesize": "^6.1.0",
"http-proxy-middleware": "^1.0.5",
"http-terminator": "^3.2.0",
"isbot": "^3.6.10",
"js-cookie": "2.2.1",
"js-yaml": "^4.1.0",
@@ -116,18 +117,18 @@
"morgan": "^1.10.0",
"ng-mocks": "^14.10.0",
"ng2-file-upload": "1.4.0",
"ng2-nouislider": "^1.8.3",
"ng2-nouislider": "^2.0.0",
"ngx-infinite-scroll": "^15.0.0",
"ngx-pagination": "6.0.3",
"ngx-sortablejs": "^11.1.0",
"ngx-ui-switch": "^14.0.3",
"nouislider": "^14.6.3",
"ngx-ui-switch": "^14.1.0",
"nouislider": "^15.7.1",
"pem": "1.14.7",
"prop-types": "^15.8.1",
"react-copy-to-clipboard": "^5.1.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0",
"sanitize-html": "^2.10.0",
"sanitize-html": "^2.12.1",
"sortablejs": "1.15.0",
"uuid": "^8.3.2",
"webfontloader": "1.6.28",
@@ -141,7 +142,7 @@
"@angular-eslint/eslint-plugin-template": "15.2.1",
"@angular-eslint/schematics": "15.2.1",
"@angular-eslint/template-parser": "15.2.1",
"@angular/cli": "^15.2.6",
"@angular/cli": "^16.0.4",
"@angular/compiler-cli": "^15.2.8",
"@angular/language-service": "^15.2.8",
"@cypress/schematic": "^1.5.0",
@@ -159,17 +160,17 @@
"@types/sanitize-html": "^2.9.0",
"@typescript-eslint/eslint-plugin": "^5.59.1",
"@typescript-eslint/parser": "^5.59.1",
"axe-core": "^4.7.0",
"axe-core": "^4.7.2",
"compression-webpack-plugin": "^9.2.0",
"copy-webpack-plugin": "^6.4.1",
"cross-env": "^7.0.3",
"cypress": "12.10.0",
"cypress": "12.17.4",
"cypress-axe": "^1.4.0",
"deep-freeze": "0.0.1",
"eslint": "^8.39.0",
"eslint-plugin-deprecation": "^1.4.1",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsdoc": "^39.6.4",
"eslint-plugin-jsdoc": "^45.0.0",
"eslint-plugin-jsonc": "^2.6.0",
"eslint-plugin-lodash": "^7.4.0",
"eslint-plugin-unused-imports": "^2.0.0",

View File

@@ -32,6 +32,7 @@ import isbot from 'isbot';
import { createCertificate } from 'pem';
import { createServer } from 'https';
import { json } from 'body-parser';
import { createHttpTerminator } from 'http-terminator';
import { readFileSync } from 'fs';
import { join } from 'path';
@@ -130,6 +131,7 @@ export function app() {
server.engine('html', (_, options, callback) =>
ngExpressEngine({
bootstrap: ServerAppModule,
inlineCriticalCss: environment.universal.inlineCriticalCss,
providers: [
{
provide: REQUEST,
@@ -320,22 +322,23 @@ function initCache() {
if (botCacheEnabled()) {
// Initialize a new "least-recently-used" item cache (where least recently used pages are removed first)
// See https://www.npmjs.com/package/lru-cache
// When enabled, each page defaults to expiring after 1 day
// When enabled, each page defaults to expiring after 1 day (defined in default-app-config.ts)
botCache = new LRU( {
max: environment.cache.serverSide.botCache.max,
ttl: environment.cache.serverSide.botCache.timeToLive || 24 * 60 * 60 * 1000, // 1 day
allowStale: environment.cache.serverSide.botCache.allowStale ?? true // if object is stale, return stale value before deleting
ttl: environment.cache.serverSide.botCache.timeToLive,
allowStale: environment.cache.serverSide.botCache.allowStale
});
}
if (anonymousCacheEnabled()) {
// NOTE: While caches may share SSR pages, this cache must be kept separately because the timeToLive
// may expire pages more frequently.
// When enabled, each page defaults to expiring after 10 seconds (to minimize anonymous users seeing out-of-date content)
// When enabled, each page defaults to expiring after 10 seconds (defined in default-app-config.ts)
// to minimize anonymous users seeing out-of-date content
anonymousCache = new LRU( {
max: environment.cache.serverSide.anonymousCache.max,
ttl: environment.cache.serverSide.anonymousCache.timeToLive || 10 * 1000, // 10 seconds
allowStale: environment.cache.serverSide.anonymousCache.allowStale ?? true // if object is stale, return stale value before deleting
ttl: environment.cache.serverSide.anonymousCache.timeToLive,
allowStale: environment.cache.serverSide.anonymousCache.allowStale
});
}
}
@@ -487,7 +490,7 @@ function saveToCache(req, page: any) {
*/
function hasNotSucceeded(statusCode) {
const rgx = new RegExp(/^20+/);
return !rgx.test(statusCode)
return !rgx.test(statusCode);
}
function retrieveHeaders(response) {
@@ -525,23 +528,46 @@ function serverStarted() {
* @param keys SSL credentials
*/
function createHttpsServer(keys) {
createServer({
const listener = createServer({
key: keys.serviceKey,
cert: keys.certificate
}, app).listen(environment.ui.port, environment.ui.host, () => {
serverStarted();
});
// Graceful shutdown when signalled
const terminator = createHttpTerminator({server: listener});
process.on('SIGINT', () => {
void (async ()=> {
console.debug('Closing HTTPS server on signal');
await terminator.terminate().catch(e => { console.error(e); });
console.debug('HTTPS server closed');
})();
});
}
/**
* Create an HTTP server with the configured port and host.
*/
function run() {
const port = environment.ui.port || 4000;
const host = environment.ui.host || '/';
// Start up the Node server
const server = app();
server.listen(port, host, () => {
const listener = server.listen(port, host, () => {
serverStarted();
});
// Graceful shutdown when signalled
const terminator = createHttpTerminator({server: listener});
process.on('SIGINT', () => {
void (async () => {
console.debug('Closing HTTP server on signal');
await terminator.terminate().catch(e => { console.error(e); });
console.debug('HTTP server closed.');return undefined;
})();
});
}
function start() {

View File

@@ -1,12 +1,22 @@
import { URLCombiner } from '../core/url-combiner/url-combiner';
import { getAccessControlModuleRoute } from '../app-routing-paths';
export const GROUP_EDIT_PATH = 'groups';
export const EPERSON_PATH = 'epeople';
export function getEPersonsRoute(): string {
return new URLCombiner(getAccessControlModuleRoute(), EPERSON_PATH).toString();
}
export function getEPersonEditRoute(id: string): string {
return new URLCombiner(getEPersonsRoute(), id, 'edit').toString();
}
export const GROUP_PATH = 'groups';
export function getGroupsRoute() {
return new URLCombiner(getAccessControlModuleRoute(), GROUP_EDIT_PATH).toString();
return new URLCombiner(getAccessControlModuleRoute(), GROUP_PATH).toString();
}
export function getGroupEditRoute(id: string) {
return new URLCombiner(getAccessControlModuleRoute(), GROUP_EDIT_PATH, id).toString();
return new URLCombiner(getGroupsRoute(), id, 'edit').toString();
}

View File

@@ -3,7 +3,7 @@ import { RouterModule } from '@angular/router';
import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.component';
import { GroupFormComponent } from './group-registry/group-form/group-form.component';
import { GroupsRegistryComponent } from './group-registry/groups-registry.component';
import { GROUP_EDIT_PATH } from './access-control-routing-paths';
import { EPERSON_PATH, GROUP_PATH } from './access-control-routing-paths';
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
import { GroupPageGuard } from './group-registry/group-page.guard';
import {
@@ -13,12 +13,14 @@ import {
SiteAdministratorGuard
} from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
import { BulkAccessComponent } from './bulk-access/bulk-access.component';
import { EPersonFormComponent } from './epeople-registry/eperson-form/eperson-form.component';
import { EPersonResolver } from './epeople-registry/eperson-resolver.service';
@NgModule({
imports: [
RouterModule.forChild([
{
path: 'epeople',
path: EPERSON_PATH,
component: EPeopleRegistryComponent,
resolve: {
breadcrumb: I18nBreadcrumbResolver
@@ -27,7 +29,26 @@ import { BulkAccessComponent } from './bulk-access/bulk-access.component';
canActivate: [SiteAdministratorGuard]
},
{
path: GROUP_EDIT_PATH,
path: `${EPERSON_PATH}/create`,
component: EPersonFormComponent,
resolve: {
breadcrumb: I18nBreadcrumbResolver,
},
data: { title: 'admin.access-control.epeople.add.title', breadcrumbKey: 'admin.access-control.epeople.add' },
canActivate: [SiteAdministratorGuard],
},
{
path: `${EPERSON_PATH}/:id/edit`,
component: EPersonFormComponent,
resolve: {
breadcrumb: I18nBreadcrumbResolver,
ePerson: EPersonResolver,
},
data: { title: 'admin.access-control.epeople.edit.title', breadcrumbKey: 'admin.access-control.epeople.edit' },
canActivate: [SiteAdministratorGuard],
},
{
path: GROUP_PATH,
component: GroupsRegistryComponent,
resolve: {
breadcrumb: I18nBreadcrumbResolver
@@ -36,7 +57,7 @@ import { BulkAccessComponent } from './bulk-access/bulk-access.component';
canActivate: [GroupAdministratorGuard]
},
{
path: `${GROUP_EDIT_PATH}/newGroup`,
path: `${GROUP_PATH}/create`,
component: GroupFormComponent,
resolve: {
breadcrumb: I18nBreadcrumbResolver
@@ -45,7 +66,7 @@ import { BulkAccessComponent } from './bulk-access/bulk-access.component';
canActivate: [GroupAdministratorGuard]
},
{
path: `${GROUP_EDIT_PATH}/:groupId`,
path: `${GROUP_PATH}/:groupId/edit`,
component: GroupFormComponent,
resolve: {
breadcrumb: I18nBreadcrumbResolver

View File

@@ -1,15 +1,15 @@
<ngb-accordion #acc="ngbAccordion" [activeIds]="'browse'">
<ngb-panel [id]="'browse'">
<ng-template ngbPanelHeader>
<div class="w-100 d-flex justify-content-between collapse-toggle" ngbPanelToggle (click)="acc.toggle('browse')"
<div class="w-100 d-flex gap-3 justify-content-between collapse-toggle" ngbPanelToggle (click)="acc.toggle('browse')"
data-test="browse">
<button type="button" class="btn btn-link p-0" (click)="$event.preventDefault()"
[attr.aria-expanded]="!acc.isExpanded('browse')"
aria-controls="collapsePanels">
[attr.aria-expanded]="acc.isExpanded('browse')"
aria-controls="bulk-access-browse-panel-content">
{{ 'admin.access-control.bulk-access-browse.header' | translate }}
</button>
<div class="text-right d-flex">
<div class="ml-3 d-inline-block">
<div class="text-right d-flex gap-2">
<div class="d-flex my-auto">
<span *ngIf="acc.isExpanded('browse')" class="fas fa-chevron-up fa-fw"></span>
<span *ngIf="!acc.isExpanded('browse')" class="fas fa-chevron-down fa-fw"></span>
</div>
@@ -17,51 +17,52 @@
</div>
</ng-template>
<ng-template ngbPanelContent>
<ul ngbNav #nav="ngbNav" [(activeId)]="activateId" class="nav-pills">
<li [ngbNavItem]="'search'">
<a ngbNavLink>{{'admin.access-control.bulk-access-browse.search.header' | translate}}</a>
<ng-template ngbNavContent>
<div class="mx-n3">
<ds-themed-search [configuration]="'administrativeBulkAccess'"
[selectable]="true"
[selectionConfig]="{ repeatable: true, listId: listId }"
[showThumbnails]="false"></ds-themed-search>
</div>
</ng-template>
</li>
<li [ngbNavItem]="'selected'">
<a ngbNavLink>
{{'admin.access-control.bulk-access-browse.selected.header' | translate: {number: ((objectsSelected$ | async)?.payload?.totalElements) ? (objectsSelected$ | async)?.payload?.totalElements : '0'} }}
</a>
<ng-template ngbNavContent>
<ds-pagination
[paginationOptions]="(paginationOptions$ | async)"
[pageInfoState]="(objectsSelected$|async)?.payload.pageInfo"
[collectionSize]="(objectsSelected$|async)?.payload?.totalElements"
[objects]="(objectsSelected$|async)"
[showPaginator]="false"
(prev)="pagePrev()"
(next)="pageNext()">
<ul *ngIf="(objectsSelected$|async)?.hasSucceeded" class="list-unstyled ml-4">
<li *ngFor='let object of (objectsSelected$|async)?.payload?.page | paginate: { itemsPerPage: (paginationOptions$ | async).pageSize,
currentPage: (paginationOptions$ | async).currentPage, totalItems: (objectsSelected$|async)?.payload?.page.length }; let i = index; let last = last '
class="mt-4 mb-4 d-flex"
[attr.data-test]="'list-object' | dsBrowserOnly">
<ds-selectable-list-item-control [index]="i"
[object]="object"
[selectionConfig]="{ repeatable: true, listId: listId }"></ds-selectable-list-item-control>
<ds-listable-object-component-loader [listID]="listId"
[index]="i"
[object]="object"
[showThumbnails]="false"
[viewMode]="'list'"></ds-listable-object-component-loader>
</li>
</ul>
</ds-pagination>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav" class="mt-5"></div>
<div id="bulk-access-browse-panel-content">
<ul ngbNav #nav="ngbNav" [(activeId)]="activateId" class="nav-pills">
<li [ngbNavItem]="'search'" role="presentation">
<a ngbNavLink>{{'admin.access-control.bulk-access-browse.search.header' | translate}}</a>
<ng-template ngbNavContent>
<div class="mx-n3">
<ds-themed-search [configuration]="'administrativeBulkAccess'"
[selectable]="true"
[selectionConfig]="{ repeatable: true, listId: listId }"
[showThumbnails]="false"></ds-themed-search>
</div>
</ng-template>
</li>
<li [ngbNavItem]="'selected'" role="presentation">
<a ngbNavLink>
{{'admin.access-control.bulk-access-browse.selected.header' | translate: {number: ((objectsSelected$ | async)?.payload?.totalElements) ? (objectsSelected$ | async)?.payload?.totalElements : '0'} }}
</a>
<ng-template ngbNavContent>
<ds-pagination
[paginationOptions]="(paginationOptions$ | async)"
[collectionSize]="(objectsSelected$|async)?.payload?.totalElements"
[objects]="(objectsSelected$|async)"
[showPaginator]="false"
(prev)="pagePrev()"
(next)="pageNext()">
<ul *ngIf="(objectsSelected$|async)?.hasSucceeded" class="list-unstyled ml-4">
<li *ngFor='let object of (objectsSelected$|async)?.payload?.page | paginate: { itemsPerPage: (paginationOptions$ | async).pageSize,
currentPage: (paginationOptions$ | async).currentPage, totalItems: (objectsSelected$|async)?.payload?.page.length }; let i = index; let last = last '
class="mt-4 mb-4 d-flex"
[attr.data-test]="'list-object' | dsBrowserOnly">
<ds-selectable-list-item-control [index]="i"
[object]="object"
[selectionConfig]="{ repeatable: true, listId: listId }"></ds-selectable-list-item-control>
<ds-listable-object-component-loader [listID]="listId"
[index]="i"
[object]="object"
[showThumbnails]="false"
[viewMode]="'list'"></ds-listable-object-component-loader>
</li>
</ul>
</ds-pagination>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav" class="mt-5"></div>
</div>
</ng-template>
</ngb-panel>
</ngb-accordion>

View File

@@ -1,4 +1,5 @@
<div class="container">
<h1>{{ 'admin.access-control.bulk-access.title' | translate }}</h1>
<ds-bulk-access-browse [listId]="listId"></ds-bulk-access-browse>
<div class="clearfix mb-3"></div>
<ds-bulk-access-settings #dsBulkSettings ></ds-bulk-access-settings>

View File

@@ -1,13 +1,13 @@
<ngb-accordion #acc="ngbAccordion" [activeIds]="'settings'">
<ngb-panel [id]="'settings'">
<ng-template ngbPanelHeader>
<div class="w-100 d-flex justify-content-between collapse-toggle" ngbPanelToggle (click)="acc.toggle('settings')" data-test="settings">
<button type="button" class="btn btn-link p-0" (click)="$event.preventDefault()" [attr.aria-expanded]="!acc.isExpanded('browse')"
aria-controls="collapsePanels">
<div class="w-100 d-flex gap-3 justify-content-between collapse-toggle" ngbPanelToggle (click)="acc.toggle('settings')" data-test="settings">
<button type="button" class="btn btn-link p-0" (click)="$event.preventDefault()" [attr.aria-expanded]="acc.isExpanded('settings')"
aria-controls="bulk-access-settings-panel-content">
{{ 'admin.access-control.bulk-access-settings.header' | translate }}
</button>
<div class="text-right d-flex">
<div class="ml-3 d-inline-block">
<div class="text-right d-flex gap-2">
<div class="d-flex my-auto">
<span *ngIf="acc.isExpanded('settings')" class="fas fa-chevron-up fa-fw"></span>
<span *ngIf="!acc.isExpanded('settings')" class="fas fa-chevron-down fa-fw"></span>
</div>
@@ -15,7 +15,7 @@
</div>
</ng-template>
<ng-template ngbPanelContent>
<ds-access-control-form-container #dsAccessControlForm [showSubmit]="false"></ds-access-control-form-container>
<ds-access-control-form-container id="bulk-access-settings-panel-content" #dsAccessControlForm [showSubmit]="false"></ds-access-control-form-container>
</ng-template>
</ngb-panel>
</ngb-accordion>

View File

@@ -2,98 +2,92 @@
<div class="epeople-registry row">
<div class="col-12">
<div class="d-flex justify-content-between border-bottom mb-3">
<h2 id="header" class="pb-2">{{labelPrefix + 'head' | translate}}</h2>
<h1 id="header" class="pb-2">{{labelPrefix + 'head' | translate}}</h1>
<div *ngIf="!isEPersonFormShown">
<div>
<button class="mr-auto btn btn-success addEPerson-button"
(click)="isEPersonFormShown = true">
[routerLink]="'create'">
<i class="fas fa-plus"></i>
<span class="d-none d-sm-inline ml-1">{{labelPrefix + 'button.add' | translate}}</span>
</button>
</div>
</div>
<ds-eperson-form *ngIf="isEPersonFormShown" (submitForm)="reset()"
(cancelForm)="isEPersonFormShown = false"></ds-eperson-form>
<div *ngIf="!isEPersonFormShown">
<h3 id="search" class="border-bottom pb-2">{{labelPrefix + 'search.head' | translate}}
</h3>
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
<div>
<select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope">
<option value="metadata">{{labelPrefix + 'search.scope.metadata' | translate}}</option>
<option value="email">{{labelPrefix + 'search.scope.email' | translate}}</option>
</select>
</div>
<div class="flex-grow-1 mr-3 ml-3">
<div class="form-group input-group">
<input type="text" name="query" id="query" formControlName="query"
class="form-control" [attr.aria-label]="labelPrefix + 'search.placeholder' | translate"
[placeholder]="(labelPrefix + 'search.placeholder' | translate)">
<span class="input-group-append">
<h2 id="search" class="border-bottom pb-2">
{{labelPrefix + 'search.head' | translate}}
</h2>
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
<div>
<select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope">
<option value="metadata">{{labelPrefix + 'search.scope.metadata' | translate}}</option>
<option value="email">{{labelPrefix + 'search.scope.email' | translate}}</option>
</select>
</div>
<div class="flex-grow-1 mr-3 ml-3">
<div class="form-group input-group">
<input type="text" name="query" id="query" formControlName="query"
class="form-control" [attr.aria-label]="labelPrefix + 'search.placeholder' | translate"
[placeholder]="(labelPrefix + 'search.placeholder' | translate)">
<span class="input-group-append">
<button type="submit" class="search-button btn btn-primary">
<i class="fas fa-search"></i> {{ labelPrefix + 'search.button' | translate }}
</button>
</span>
</div>
</div>
<div>
<button (click)="clearFormAndResetResult();"
class="search-button btn btn-secondary">{{labelPrefix + 'button.see-all' | translate}}</button>
</div>
</form>
<ds-themed-loading *ngIf="searching$ | async"></ds-themed-loading>
<ds-pagination
*ngIf="(pageInfoState$ | async)?.totalElements > 0 && !(searching$ | async)"
[paginationOptions]="config"
[pageInfoState]="pageInfoState$"
[collectionSize]="(pageInfoState$ | async)?.totalElements"
[hideGear]="true"
[hidePagerWhenSinglePage]="true">
<div class="table-responsive">
<table id="epeople" class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th scope="col">{{labelPrefix + 'table.id' | translate}}</th>
<th scope="col">{{labelPrefix + 'table.name' | translate}}</th>
<th scope="col">{{labelPrefix + 'table.email' | translate}}</th>
<th>{{labelPrefix + 'table.edit' | translate}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let epersonDto of (ePeopleDto$ | async)?.page"
[ngClass]="{'table-primary' : isActive(epersonDto.eperson) | async}">
<td>{{epersonDto.eperson.id}}</td>
<td>{{ dsoNameService.getName(epersonDto.eperson) }}</td>
<td>{{epersonDto.eperson.email}}</td>
<td>
<div class="btn-group edit-field">
<button (click)="toggleEditEPerson(epersonDto.eperson)"
class="btn btn-outline-primary btn-sm access-control-editEPersonButton"
title="{{labelPrefix + 'table.edit.buttons.edit' | translate: { name: dsoNameService.getName(epersonDto.eperson) } }}">
<i class="fas fa-edit fa-fw"></i>
</button>
<button [disabled]="!epersonDto.ableToDelete" (click)="deleteEPerson(epersonDto.eperson)"
class="delete-button btn btn-outline-danger btn-sm access-control-deleteEPersonButton"
title="{{labelPrefix + 'table.edit.buttons.remove' | translate: { name: dsoNameService.getName(epersonDto.eperson) } }}">
<i class="fas fa-trash-alt fa-fw"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</ds-pagination>
<div *ngIf="(pageInfoState$ | async)?.totalElements == 0" class="alert alert-info w-100 mb-2" role="alert">
{{labelPrefix + 'no-items' | translate}}
</div>
<div>
<button (click)="clearFormAndResetResult();"
class="search-button btn btn-secondary">{{labelPrefix + 'button.see-all' | translate}}</button>
</div>
</form>
<ds-themed-loading *ngIf="searching$ | async"></ds-themed-loading>
<ds-pagination
*ngIf="(pageInfoState$ | async)?.totalElements > 0 && !(searching$ | async)"
[paginationOptions]="config"
[collectionSize]="(pageInfoState$ | async)?.totalElements"
[hideGear]="true"
[hidePagerWhenSinglePage]="true">
<div class="table-responsive">
<table id="epeople" class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th scope="col">{{labelPrefix + 'table.id' | translate}}</th>
<th scope="col">{{labelPrefix + 'table.name' | translate}}</th>
<th scope="col">{{labelPrefix + 'table.email' | translate}}</th>
<th>{{labelPrefix + 'table.edit' | translate}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let epersonDto of (ePeopleDto$ | async)?.page"
[ngClass]="{'table-primary' : isActive(epersonDto.eperson) | async}">
<td>{{epersonDto.eperson.id}}</td>
<td>{{ dsoNameService.getName(epersonDto.eperson) }}</td>
<td>{{epersonDto.eperson.email}}</td>
<td>
<div class="btn-group edit-field">
<button [routerLink]="getEditEPeoplePage(epersonDto.eperson.id)"
class="btn btn-outline-primary btn-sm access-control-editEPersonButton"
title="{{labelPrefix + 'table.edit.buttons.edit' | translate: { name: dsoNameService.getName(epersonDto.eperson) } }}">
<i class="fas fa-edit fa-fw"></i>
</button>
<button *ngIf="epersonDto.ableToDelete" (click)="deleteEPerson(epersonDto.eperson)"
class="delete-button btn btn-outline-danger btn-sm access-control-deleteEPersonButton"
title="{{labelPrefix + 'table.edit.buttons.remove' | translate: { name: dsoNameService.getName(epersonDto.eperson) } }}">
<i class="fas fa-trash-alt fa-fw"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</ds-pagination>
<div *ngIf="(pageInfoState$ | async)?.totalElements == 0" class="alert alert-info w-100 mb-2" role="alert">
{{labelPrefix + 'no-items' | translate}}
</div>
</div>
</div>

View File

@@ -203,36 +203,6 @@ describe('EPeopleRegistryComponent', () => {
});
});
describe('toggleEditEPerson', () => {
describe('when you click on first edit eperson button', () => {
beforeEach(fakeAsync(() => {
const editButtons = fixture.debugElement.queryAll(By.css('.access-control-editEPersonButton'));
editButtons[0].triggerEventHandler('click', {
preventDefault: () => {/**/
}
});
tick();
fixture.detectChanges();
}));
it('editEPerson form is toggled', () => {
const ePeopleIds = fixture.debugElement.queryAll(By.css('#epeople tr td:first-child'));
ePersonDataServiceStub.getActiveEPerson().subscribe((activeEPerson: EPerson) => {
if (ePeopleIds[0] && activeEPerson === ePeopleIds[0].nativeElement.textContent) {
expect(component.isEPersonFormShown).toEqual(false);
} else {
expect(component.isEPersonFormShown).toEqual(true);
}
});
});
it('EPerson search section is hidden', () => {
expect(fixture.debugElement.query(By.css('#search'))).toBeNull();
});
});
});
describe('deleteEPerson', () => {
describe('when you click on first delete eperson button', () => {
let ePeopleIdsFoundBeforeDelete;

View File

@@ -22,6 +22,7 @@ import { PageInfo } from '../../core/shared/page-info.model';
import { NoContent } from '../../core/shared/NoContent.model';
import { PaginationService } from '../../core/pagination/pagination.service';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { getEPersonEditRoute, getEPersonsRoute } from '../access-control-routing-paths';
@Component({
selector: 'ds-epeople-registry',
@@ -64,11 +65,6 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
currentPage: 1
});
/**
* Whether or not to show the EPerson form
*/
isEPersonFormShown: boolean;
// The search form
searchForm;
@@ -114,17 +110,11 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
*/
initialisePage() {
this.searching$.next(true);
this.isEPersonFormShown = false;
this.search({scope: this.currentSearchScope, query: this.currentSearchQuery});
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
if (eperson != null && eperson.id) {
this.isEPersonFormShown = true;
}
}));
this.subs.push(this.ePeople$.pipe(
switchMap((epeople: PaginatedList<EPerson>) => {
if (epeople.pageInfo.totalElements > 0) {
return combineLatest([...epeople.page.map((eperson: EPerson) => {
return combineLatest(epeople.page.map((eperson: EPerson) => {
return this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined).pipe(
map((authorized) => {
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
@@ -133,7 +123,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
return epersonDtoModel;
})
);
})]).pipe(map((dtos: EpersonDtoModel[]) => {
})).pipe(map((dtos: EpersonDtoModel[]) => {
return buildPaginatedList(epeople.pageInfo, dtos);
}));
} else {
@@ -160,14 +150,14 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
const query: string = data.query;
const scope: string = data.scope;
if (query != null && this.currentSearchQuery !== query) {
this.router.navigate([this.epersonService.getEPeoplePageRouterLink()], {
void this.router.navigate([getEPersonsRoute()], {
queryParamsHandling: 'merge'
});
this.currentSearchQuery = query;
this.paginationService.resetPage(this.config.id);
}
if (scope != null && this.currentSearchScope !== scope) {
this.router.navigate([this.epersonService.getEPeoplePageRouterLink()], {
void this.router.navigate([getEPersonsRoute()], {
queryParamsHandling: 'merge'
});
this.currentSearchScope = scope;
@@ -205,23 +195,6 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
return this.epersonService.getActiveEPerson();
}
/**
* Start editing the selected EPerson
* @param ePerson
*/
toggleEditEPerson(ePerson: EPerson) {
this.getActiveEPerson().pipe(take(1)).subscribe((activeEPerson: EPerson) => {
if (ePerson === activeEPerson) {
this.epersonService.cancelEditEPerson();
this.isEPersonFormShown = false;
} else {
this.epersonService.editEPerson(ePerson);
this.isEPersonFormShown = true;
}
});
this.scrollToTop();
}
/**
* Deletes EPerson, show notification on success/failure & updates EPeople list
*/
@@ -242,7 +215,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
if (restResponse.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', {name: this.dsoNameService.getName(ePerson)}));
} else {
this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + ePerson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage);
this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { id: ePerson.id, statusCode: restResponse.statusCode, errorMessage: restResponse.errorMessage }));
}
});
}
@@ -264,16 +237,6 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
}
scrollToTop() {
(function smoothscroll() {
const currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
if (currentScroll > 0) {
window.requestAnimationFrame(smoothscroll);
window.scrollTo(0, currentScroll - (currentScroll / 8));
}
})();
}
/**
* Reset all input-fields to be empty and search all search
*/
@@ -284,20 +247,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
this.search({query: ''});
}
/**
* This method will set everything to stale, which will cause the lists on this page to update.
*/
reset(): void {
this.epersonService.getBrowseEndpoint().pipe(
take(1),
switchMap((href: string) => {
return this.requestService.setStaleByHrefSubstring(href).pipe(
take(1),
);
})
).subscribe(()=>{
this.epersonService.cancelEditEPerson();
this.isEPersonFormShown = false;
});
getEditEPeoplePage(id: string): string {
return getEPersonEditRoute(id);
}
}

View File

@@ -1,89 +1,96 @@
<div *ngIf="epersonService.getActiveEPerson() | async; then editheader; else createHeader"></div>
<div class="container">
<div class="group-form row">
<div class="col-12">
<ng-template #createHeader>
<h4>{{messagePrefix + '.create' | translate}}</h4>
</ng-template>
<div *ngIf="epersonService.getActiveEPerson() | async; then editHeader; else createHeader"></div>
<ng-template #editheader>
<h4>{{messagePrefix + '.edit' | translate}}</h4>
</ng-template>
<ng-template #createHeader>
<h1 class="border-bottom pb-2">{{messagePrefix + '.create' | translate}}</h1>
</ng-template>
<ds-form [formId]="formId"
[formModel]="formModel"
[formGroup]="formGroup"
[formLayout]="formLayout"
[displayCancel]="false"
[submitLabel]="submitLabel"
(submitForm)="onSubmit()">
<div before class="btn-group">
<button (click)="onCancel()"
class="btn btn-outline-secondary"><i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}</button>
</div>
<div *ngIf="displayResetPassword" between class="btn-group">
<button class="btn btn-primary" [disabled]="!(canReset$ | async)" (click)="resetPassword()">
<i class="fa fa-key"></i> {{'admin.access-control.epeople.actions.reset' | translate}}
</button>
</div>
<div between class="btn-group ml-1">
<button *ngIf="!isImpersonated" class="btn btn-primary" [ngClass]="{'d-none' : !(canImpersonate$ | async)}" (click)="impersonate()">
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.impersonate' | translate}}
</button>
<button *ngIf="isImpersonated" class="btn btn-primary" (click)="stopImpersonating()">
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.stop-impersonating' | translate}}
</button>
</div>
<button after class="btn btn-danger delete-button" [disabled]="!(canDelete$ | async)" (click)="delete()">
<i class="fas fa-trash"></i> {{'admin.access-control.epeople.actions.delete' | translate}}
</button>
</ds-form>
<ng-template #editHeader>
<h1 class="border-bottom pb-2">{{messagePrefix + '.edit' | translate}}</h1>
</ng-template>
<ds-themed-loading [showMessage]="false" *ngIf="!formGroup"></ds-themed-loading>
<ds-form [formId]="formId"
[formModel]="formModel"
[formGroup]="formGroup"
[formLayout]="formLayout"
[displayCancel]="false"
[submitLabel]="submitLabel"
(submitForm)="onSubmit()">
<div before class="btn-group">
<button (click)="onCancel()" type="button" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}
</button>
</div>
<div *ngIf="displayResetPassword" between class="btn-group">
<button class="btn btn-primary" [disabled]="!(canReset$ | async)" type="button" (click)="resetPassword()">
<i class="fa fa-key"></i> {{'admin.access-control.epeople.actions.reset' | translate}}
</button>
</div>
<div *ngIf="canImpersonate$ | async" between class="btn-group">
<button *ngIf="!isImpersonated" class="btn btn-primary" type="button" (click)="impersonate()">
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.impersonate' | translate}}
</button>
<button *ngIf="isImpersonated" class="btn btn-primary" type="button" (click)="stopImpersonating()">
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.stop-impersonating' | translate}}
</button>
</div>
<button *ngIf="canDelete$ | async" after class="btn btn-danger delete-button" type="button" (click)="delete()">
<i class="fas fa-trash"></i> {{'admin.access-control.epeople.actions.delete' | translate}}
</button>
</ds-form>
<div *ngIf="epersonService.getActiveEPerson() | async">
<h5>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h5>
<ds-themed-loading [showMessage]="false" *ngIf="!formGroup"></ds-themed-loading>
<ds-themed-loading [showMessage]="false" *ngIf="!(groups | async)"></ds-themed-loading>
<div *ngIf="epersonService.getActiveEPerson() | async">
<h2>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h2>
<ds-pagination
*ngIf="(groups | async)?.payload?.totalElements > 0"
[paginationOptions]="config"
[pageInfoState]="(groups | async)?.payload"
[collectionSize]="(groups | async)?.payload?.totalElements"
[hideGear]="true"
[hidePagerWhenSinglePage]="true"
(pageChange)="onPageChange($event)">
<ds-themed-loading [showMessage]="false" *ngIf="!(groups | async)"></ds-themed-loading>
<div class="table-responsive">
<table id="groups" class="table table-striped table-hover table-bordered">
<thead>
<tr>
<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 class="align-middle">{{group.id}}</td>
<td class="align-middle">
<a (click)="groupsDataService.startEditingNewGroup(group)"
[routerLink]="[groupsDataService.getGroupEditPageRouterLink(group)]">
{{ dsoNameService.getName(group) }}
</a>
</td>
<td class="align-middle">{{ dsoNameService.getName(undefined) }}</td>
</tr>
</tbody>
</table>
</div>
<ds-pagination
*ngIf="(groups | async)?.payload?.totalElements > 0"
[paginationOptions]="config"
[collectionSize]="(groups | async)?.payload?.totalElements"
[hideGear]="true"
[hidePagerWhenSinglePage]="true"
(pageChange)="onPageChange($event)">
</ds-pagination>
<div class="table-responsive">
<table id="groups" class="table table-striped table-hover table-bordered">
<thead>
<tr>
<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 class="align-middle">{{group.id}}</td>
<td class="align-middle">
<a (click)="groupsDataService.startEditingNewGroup(group)"
[routerLink]="[groupsDataService.getGroupEditPageRouterLink(group)]">
{{ dsoNameService.getName(group) }}
</a>
</td>
<td class="align-middle">{{ dsoNameService.getName(undefined) }}</td>
</tr>
</tbody>
</table>
</div>
<div *ngIf="(groups | async)?.payload?.totalElements == 0" class="alert alert-info w-100 mb-2" role="alert">
<div>{{messagePrefix + '.memberOfNoGroups' | translate}}</div>
<div>
<button [routerLink]="[groupsDataService.getGroupRegistryRouterLink()]"
class="btn btn-primary">{{messagePrefix + '.goToGroups' | translate}}</button>
</ds-pagination>
<div *ngIf="(groups | async)?.payload?.totalElements == 0" class="alert alert-info w-100 mb-2" role="alert">
<div>{{messagePrefix + '.memberOfNoGroups' | translate}}</div>
<div>
<button [routerLink]="[groupsDataService.getGroupRegistryRouterLink()]"
class="btn btn-primary">{{messagePrefix + '.goToGroups' | translate}}</button>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -31,6 +31,10 @@ import { PaginationServiceStub } from '../../../shared/testing/pagination-servic
import { FindListOptions } from '../../../core/data/find-list-options.model';
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service';
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
import { ActivatedRoute, Router } from '@angular/router';
import { RouterStub } from '../../../shared/testing/router.stub';
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
describe('EPersonFormComponent', () => {
let component: EPersonFormComponent;
@@ -43,6 +47,8 @@ describe('EPersonFormComponent', () => {
let authorizationService: AuthorizationDataService;
let groupsDataService: GroupDataService;
let epersonRegistrationService: EpersonRegistrationService;
let route: ActivatedRouteStub;
let router: RouterStub;
let paginationService;
@@ -106,6 +112,9 @@ describe('EPersonFormComponent', () => {
},
getEPersonByEmail(email): Observable<RemoteData<EPerson>> {
return createSuccessfulRemoteDataObject$(null);
},
findById(_id: string, _useCachedVersionIfAvailable = true, _reRequestOnStale = true, ..._linksToFollow: FollowLinkConfig<EPerson>[]): Observable<RemoteData<EPerson>> {
return createSuccessfulRemoteDataObject$(null);
}
};
builderService = Object.assign(getMockFormBuilderService(),{
@@ -182,6 +191,8 @@ describe('EPersonFormComponent', () => {
});
paginationService = new PaginationServiceStub();
route = new ActivatedRouteStub();
router = new RouterStub();
TestBed.configureTestingModule({
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
TranslateModule.forRoot({
@@ -202,6 +213,8 @@ describe('EPersonFormComponent', () => {
{ provide: PaginationService, useValue: paginationService },
{ provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeByHrefSubstring'])},
{ provide: EpersonRegistrationService, useValue: epersonRegistrationService },
{ provide: ActivatedRoute, useValue: route },
{ provide: Router, useValue: router },
EPeopleRegistryComponent
],
schemas: [NO_ERRORS_SCHEMA]
@@ -263,24 +276,18 @@ describe('EPersonFormComponent', () => {
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();
});
}));
it('form should be invalid because the firstName is required', () => {
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', () => {
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', () => {
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', () => {
@@ -290,24 +297,18 @@ describe('EPersonFormComponent', () => {
component.formGroup.controls.email.setValue('test@test.com');
fixture.detectChanges();
});
it('firstName should be valid because the firstName is set', waitForAsync(() => {
fixture.whenStable().then(() => {
it('firstName should be valid because the firstName is set', () => {
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(() => {
});
it('lastName should be valid because the lastName is set', () => {
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(() => {
});
it('email should be valid because the email is set', () => {
expect(component.formGroup.controls.email.valid).toBeTrue();
expect(component.formGroup.controls.email.errors).toBeNull();
});
}));
});
});
@@ -316,12 +317,10 @@ describe('EPersonFormComponent', () => {
component.formGroup.controls.email.setValue('test@test');
fixture.detectChanges();
});
it('email should not be valid because the email pattern', waitForAsync(() => {
fixture.whenStable().then(() => {
it('email should not be valid because the email pattern', () => {
expect(component.formGroup.controls.email.valid).toBeFalse();
expect(component.formGroup.controls.email.errors.pattern).toBeTruthy();
});
}));
});
});
describe('after already utilized email', () => {
@@ -336,12 +335,10 @@ describe('EPersonFormComponent', () => {
fixture.detectChanges();
});
it('email should not be valid because email is already taken', waitForAsync(() => {
fixture.whenStable().then(() => {
it('email should not be valid because email is already taken', () => {
expect(component.formGroup.controls.email.valid).toBeFalse();
expect(component.formGroup.controls.email.errors.emailTaken).toBeTruthy();
});
}));
});
});
@@ -393,11 +390,9 @@ describe('EPersonFormComponent', () => {
fixture.detectChanges();
});
it('should emit a new eperson using the correct values', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.submitForm.emit).toHaveBeenCalledWith(expected);
});
}));
it('should emit a new eperson using the correct values', () => {
expect(component.submitForm.emit).toHaveBeenCalledWith(expected);
});
});
describe('with an active eperson', () => {
@@ -428,11 +423,9 @@ describe('EPersonFormComponent', () => {
fixture.detectChanges();
});
it('should emit the existing eperson using the correct values', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.submitForm.emit).toHaveBeenCalledWith(expectedWithId);
});
}));
it('should emit the existing eperson using the correct values', () => {
expect(component.submitForm.emit).toHaveBeenCalledWith(expectedWithId);
});
});
});
@@ -491,16 +484,16 @@ describe('EPersonFormComponent', () => {
});
it('the delete button should be active if the eperson can be deleted', () => {
it('the delete button should be visible if the ePerson can be deleted', () => {
const deleteButton = fixture.debugElement.query(By.css('.delete-button'));
expect(deleteButton.nativeElement.disabled).toBe(false);
expect(deleteButton).not.toBeNull();
});
it('the delete button should be disabled if the eperson cannot be deleted', () => {
it('the delete button should be hidden if the ePerson cannot be deleted', () => {
component.canDelete$ = observableOf(false);
fixture.detectChanges();
const deleteButton = fixture.debugElement.query(By.css('.delete-button'));
expect(deleteButton.nativeElement.disabled).toBe(true);
expect(deleteButton).toBeNull();
});
it('should call the epersonFormComponent delete when clicked on the button', () => {

View File

@@ -38,6 +38,8 @@ import { Registration } from '../../../core/shared/registration.model';
import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service';
import { TYPE_REQUEST_FORGOT } from '../../../register-email-form/register-email-form.component';
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
import { ActivatedRoute, Router } from '@angular/router';
import { getEPersonsRoute } from '../../access-control-routing-paths';
@Component({
selector: 'ds-eperson-form',
@@ -194,6 +196,8 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
public requestService: RequestService,
private epersonRegistrationService: EpersonRegistrationService,
public dsoNameService: DSONameService,
protected route: ActivatedRoute,
protected router: Router,
) {
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
this.epersonInitial = eperson;
@@ -213,7 +217,9 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
* This method will initialise the page
*/
initialisePage() {
this.subs.push(this.epersonService.findById(this.route.snapshot.params.id).subscribe((ePersonRD: RemoteData<EPerson>) => {
this.epersonService.editEPerson(ePersonRD.payload);
}));
observableCombineLatest([
this.translateService.get(`${this.messagePrefix}.firstName`),
this.translateService.get(`${this.messagePrefix}.lastName`),
@@ -339,6 +345,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
onCancel() {
this.epersonService.cancelEditEPerson();
this.cancelForm.emit();
void this.router.navigate([getEPersonsRoute()]);
}
/**
@@ -390,6 +397,8 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', { name: this.dsoNameService.getName(ePersonToCreate) }));
this.submitForm.emit(ePersonToCreate);
this.epersonService.clearEPersonRequests();
void this.router.navigateByUrl(getEPersonsRoute());
} else {
this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.created.failure', { name: this.dsoNameService.getName(ePersonToCreate) }));
this.cancelForm.emit();
@@ -429,6 +438,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', { name: this.dsoNameService.getName(editedEperson) }));
this.submitForm.emit(editedEperson);
void this.router.navigateByUrl(getEPersonsRoute());
} else {
this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.edited.failure', { name: this.dsoNameService.getName(editedEperson) }));
this.cancelForm.emit();
@@ -495,6 +505,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
).subscribe(({ restResponse, eperson }: { restResponse: RemoteData<NoContent> | null, eperson: EPerson }) => {
if (restResponse?.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: this.dsoNameService.getName(eperson) }));
void this.router.navigate([getEPersonsRoute()]);
} else {
this.notificationsService.error(`Error occurred when trying to delete EPerson with id: ${eperson?.id} with code: ${restResponse?.statusCode} and message: ${restResponse?.errorMessage}`);
}
@@ -541,16 +552,6 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
}
}
/**
* This method will ensure that the page gets reset and that the cache is cleared
*/
reset() {
this.epersonService.getActiveEPerson().pipe(take(1)).subscribe((eperson: EPerson) => {
this.requestService.removeByHrefSubstring(eperson.self);
});
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

View File

@@ -0,0 +1,53 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { EPerson } from '../../core/eperson/models/eperson.model';
import { RemoteData } from '../../core/data/remote-data';
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
import { ResolvedAction } from '../../core/resolving/resolver.actions';
import { EPersonDataService } from '../../core/eperson/eperson-data.service';
import { Store } from '@ngrx/store';
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
export const EPERSON_EDIT_FOLLOW_LINKS: FollowLinkConfig<EPerson>[] = [
followLink('groups'),
];
/**
* This class represents a resolver that requests a specific {@link EPerson} before the route is activated
*/
@Injectable({
providedIn: 'root',
})
export class EPersonResolver implements Resolve<RemoteData<EPerson>> {
constructor(
protected ePersonService: EPersonDataService,
protected store: Store<any>,
) {
}
/**
* Method for resolving a {@link EPerson} based on the parameters in the current route
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @returns `Observable<<RemoteData<EPerson>>` Emits the found {@link EPerson} based on the parameters in the current
* route, or an error if something went wrong
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<EPerson>> {
const ePersonRD$: Observable<RemoteData<EPerson>> = this.ePersonService.findById(route.params.id,
true,
false,
...EPERSON_EDIT_FOLLOW_LINKS,
).pipe(
getFirstCompletedRemoteData(),
);
ePersonRD$.subscribe((ePersonRD: RemoteData<EPerson>) => {
this.store.dispatch(new ResolvedAction(state.url, ePersonRD.payload));
});
return ePersonRD$;
}
}

View File

@@ -2,14 +2,14 @@
<div class="group-form row">
<div class="col-12">
<div *ngIf="groupDataService.getActiveGroup() | async; then editheader; else createHeader"></div>
<div *ngIf="groupDataService.getActiveGroup() | async; then editHeader; else createHeader"></div>
<ng-template #createHeader>
<h2 class="border-bottom pb-2">{{messagePrefix + '.head.create' | translate}}</h2>
<h1 class="border-bottom pb-2">{{messagePrefix + '.head.create' | translate}}</h1>
</ng-template>
<ng-template #editheader>
<h2 class="border-bottom pb-2">
<ng-template #editHeader>
<h1 class="border-bottom pb-2">
<span
*dsContextHelp="{
content: 'admin.access-control.groups.form.tooltip.editGroupPage',
@@ -20,7 +20,7 @@
>
{{messagePrefix + '.head.edit' | translate}}
</span>
</h2>
</h1>
</ng-template>
<ds-alert *ngIf="groupBeingEdited?.permanent" [type]="AlertTypeEnum.Warning"
@@ -36,12 +36,11 @@
[displayCancel]="false"
(submitForm)="onSubmit()">
<div before class="btn-group">
<button (click)="onCancel()"
<button (click)="onCancel()" type="button"
class="btn btn-outline-secondary"><i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}</button>
</div>
<div after *ngIf="groupBeingEdited != null" class="btn-group">
<button class="btn btn-danger delete-button" [disabled]="!(canEdit$ | async) || groupBeingEdited.permanent"
(click)="delete()">
<div after *ngIf="(canEdit$ | async) && !groupBeingEdited?.permanent" class="btn-group">
<button (click)="delete()" class="btn btn-danger delete-button" type="button">
<i class="fa fa-trash"></i> {{ messagePrefix + '.actions.delete' | translate}}
</button>
</div>

View File

@@ -23,6 +23,7 @@ import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { HALEndpointService } from '../../../core/shared/hal-endpoint.service';
import { PageInfo } from '../../../core/shared/page-info.model';
import { UUIDService } from '../../../core/shared/uuid.service';
import { XSRFService } from '../../../core/xsrf/xsrf.service';
import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { GroupMock, GroupMock2 } from '../../../shared/testing/group-mock';
@@ -211,6 +212,7 @@ describe('GroupFormComponent', () => {
{ provide: HttpClient, useValue: {} },
{ provide: ObjectCacheService, useValue: {} },
{ provide: UUIDService, useValue: {} },
{ provide: XSRFService, useValue: {} },
{ provide: Store, useValue: {} },
{ provide: RemoteDataBuildService, useValue: {} },
{ provide: HALEndpointService, useValue: {} },

View File

@@ -10,7 +10,6 @@ import {
} from '@ng-dynamic-forms/core';
import { TranslateService } from '@ngx-translate/core';
import {
ObservedValueOf,
combineLatest as observableCombineLatest,
Observable,
of as observableOf,
@@ -37,7 +36,7 @@ import {
getFirstCompletedRemoteData,
getFirstSucceededRemoteDataPayload
} from '../../../core/shared/operators';
import { AlertType } from '../../../shared/alert/aletr-type';
import { AlertType } from '../../../shared/alert/alert-type';
import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component';
import { hasValue, isNotEmpty, hasValueOperator } from '../../../shared/empty.util';
import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
@@ -48,6 +47,7 @@ import { Operation } from 'fast-json-patch';
import { ValidateGroupExists } from './validators/group-exists.validator';
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
import { environment } from '../../../../environments/environment';
import { getGroupEditRoute, getGroupsRoute } from '../../access-control-routing-paths';
@Component({
selector: 'ds-group-form',
@@ -165,19 +165,19 @@ export class GroupFormComponent implements OnInit, OnDestroy {
this.canEdit$ = this.groupDataService.getActiveGroup().pipe(
hasValueOperator(),
switchMap((group: Group) => {
return observableCombineLatest(
return observableCombineLatest([
this.authorizationService.isAuthorized(FeatureID.CanDelete, isNotEmpty(group) ? group.self : undefined),
this.hasLinkedDSO(group),
(isAuthorized: ObservedValueOf<Observable<boolean>>, hasLinkedDSO: ObservedValueOf<Observable<boolean>>) => {
return isAuthorized && !hasLinkedDSO;
});
})
]).pipe(
map(([isAuthorized, hasLinkedDSO]: [boolean, boolean]) => isAuthorized && !hasLinkedDSO),
);
}),
);
observableCombineLatest(
observableCombineLatest([
this.translateService.get(`${this.messagePrefix}.groupName`),
this.translateService.get(`${this.messagePrefix}.groupCommunity`),
this.translateService.get(`${this.messagePrefix}.groupDescription`)
).subscribe(([groupName, groupCommunity, groupDescription]) => {
]).subscribe(([groupName, groupCommunity, groupDescription]) => {
this.groupName = new DynamicInputModel({
id: 'groupName',
label: groupName,
@@ -215,12 +215,12 @@ export class GroupFormComponent implements OnInit, OnDestroy {
}
this.subs.push(
observableCombineLatest(
observableCombineLatest([
this.groupDataService.getActiveGroup(),
this.canEdit$,
this.groupDataService.getActiveGroup()
.pipe(filter((activeGroup) => hasValue(activeGroup)),switchMap((activeGroup) => this.getLinkedDSO(activeGroup).pipe(getFirstSucceededRemoteDataPayload())))
).subscribe(([activeGroup, canEdit, linkedObject]) => {
]).subscribe(([activeGroup, canEdit, linkedObject]) => {
if (activeGroup != null) {
@@ -230,12 +230,14 @@ export class GroupFormComponent implements OnInit, OnDestroy {
this.groupBeingEdited = activeGroup;
if (linkedObject?.name) {
this.formBuilderService.insertFormGroupControl(1, this.formGroup, this.formModel, this.groupCommunity);
this.formGroup.patchValue({
groupName: activeGroup.name,
groupCommunity: linkedObject?.name ?? '',
groupDescription: activeGroup.firstMetadataValue('dc.description'),
});
if (!this.formGroup.controls.groupCommunity) {
this.formBuilderService.insertFormGroupControl(1, this.formGroup, this.formModel, this.groupCommunity);
this.formGroup.patchValue({
groupName: activeGroup.name,
groupCommunity: linkedObject?.name ?? '',
groupDescription: activeGroup.firstMetadataValue('dc.description'),
});
}
} else {
this.formModel = [
this.groupName,
@@ -263,7 +265,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
onCancel() {
this.groupDataService.cancelEditGroup();
this.cancelForm.emit();
this.router.navigate([this.groupDataService.getGroupRegistryRouterLink()]);
void this.router.navigate([getGroupsRoute()]);
}
/**
@@ -310,7 +312,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
const groupSelfLink = rd.payload._links.self.href;
this.setActiveGroupWithLink(groupSelfLink);
this.groupDataService.clearGroupsRequests();
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLinkWithID(rd.payload.uuid));
void this.router.navigateByUrl(getGroupEditRoute(rd.payload.uuid));
}
} else {
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.created.failure', { name: groupToCreate.name }));

View File

@@ -1,110 +1,11 @@
<ng-container>
<h3 class="border-bottom pb-2">{{messagePrefix + '.head' | translate}}</h3>
<h2 class="border-bottom pb-2">{{messagePrefix + '.head' | translate}}</h2>
<h4 id="search" class="border-bottom pb-2">
<span
*dsContextHelp="{
content: 'admin.access-control.groups.form.tooltip.editGroup.addEpeople',
id: 'edit-group-add-epeople',
iconPlacement: 'right',
tooltipPlacement: ['top', 'right', 'bottom']
}"
>
{{messagePrefix + '.search.head' | translate}}
</span>
</h4>
<h3>{{messagePrefix + '.headMembers' | translate}}</h3>
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
<div>
<select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope">
<option value="metadata">{{messagePrefix + '.search.scope.metadata' | translate}}</option>
<option value="email">{{messagePrefix + '.search.scope.email' | translate}}</option>
</select>
</div>
<div class="flex-grow-1 mr-3 ml-3">
<div class="form-group input-group">
<input type="text" name="query" id="query" formControlName="query"
class="form-control" aria-label="Search input">
<span class="input-group-append">
<button type="submit" class="search-button btn btn-primary">
<i class="fas fa-search"></i> {{ messagePrefix + '.search.button' | translate }}</button>
</span>
</div>
</div>
<div>
<button (click)="clearFormAndResetResult();"
class="btn btn-secondary">{{messagePrefix + '.button.see-all' | translate}}</button>
</div>
</form>
<ds-pagination *ngIf="(ePeopleSearchDtos | async)?.totalElements > 0"
[paginationOptions]="configSearch"
[pageInfoState]="(ePeopleSearchDtos | async)"
[collectionSize]="(ePeopleSearchDtos | async)?.totalElements"
[hideGear]="true"
[hidePagerWhenSinglePage]="true">
<div class="table-responsive">
<table id="epersonsSearch" class="table table-striped table-hover table-bordered">
<thead>
<tr>
<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 class="align-middle">{{ePerson.eperson.id}}</td>
<td class="align-middle">
<a (click)="ePersonDataService.startEditingNewEPerson(ePerson.eperson)"
[routerLink]="[ePersonDataService.getEPeoplePageRouterLink()]">
{{ dsoNameService.getName(ePerson.eperson) }}
</a>
</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)"
[disabled]="actionConfig.remove.disabled"
[ngClass]="['btn btn-sm', actionConfig.remove.css]"
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(ePerson.eperson) } }}">
<i [ngClass]="actionConfig.remove.icon"></i>
</button>
<button *ngIf="!ePerson.memberOfGroup"
(click)="addMemberToGroup(ePerson)"
[disabled]="actionConfig.add.disabled"
[ngClass]="['btn btn-sm', actionConfig.add.css]"
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(ePerson.eperson) } }}">
<i [ngClass]="actionConfig.add.icon"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</ds-pagination>
<div *ngIf="(ePeopleSearchDtos | async)?.totalElements == 0 && searchDone"
class="alert alert-info w-100 mb-2"
role="alert">
{{messagePrefix + '.no-items' | translate}}
</div>
<h4>{{messagePrefix + '.headMembers' | translate}}</h4>
<ds-pagination *ngIf="(ePeopleMembersOfGroupDtos | async)?.totalElements > 0"
<ds-pagination *ngIf="(ePeopleMembersOfGroup | async)?.totalElements > 0"
[paginationOptions]="config"
[pageInfoState]="(ePeopleMembersOfGroupDtos | async)"
[collectionSize]="(ePeopleMembersOfGroupDtos | async)?.totalElements"
[collectionSize]="(ePeopleMembersOfGroup | async)?.totalElements"
[hideGear]="true"
[hidePagerWhenSinglePage]="true">
@@ -119,32 +20,103 @@
</tr>
</thead>
<tbody>
<tr *ngFor="let ePerson of (ePeopleMembersOfGroupDtos | async)?.page">
<td class="align-middle">{{ePerson.eperson.id}}</td>
<tr *ngFor="let eperson of (ePeopleMembersOfGroup | async)?.page">
<td class="align-middle">{{eperson.id}}</td>
<td class="align-middle">
<a (click)="ePersonDataService.startEditingNewEPerson(ePerson.eperson)"
[routerLink]="[ePersonDataService.getEPeoplePageRouterLink()]">
{{ dsoNameService.getName(ePerson.eperson) }}
<a [routerLink]="getEPersonEditRoute(eperson.id)">
{{ dsoNameService.getName(eperson) }}
</a>
</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 : '-' }}
{{messagePrefix + '.table.email' | translate}}: {{ eperson.email ? eperson.email : '-' }}<br/>
{{messagePrefix + '.table.netid' | translate}}: {{ eperson.netid ? eperson.netid : '-' }}
</td>
<td class="align-middle">
<div class="btn-group edit-field">
<button *ngIf="ePerson.memberOfGroup"
(click)="deleteMemberFromGroup(ePerson)"
<button (click)="deleteMemberFromGroup(eperson)"
[disabled]="actionConfig.remove.disabled"
[ngClass]="['btn btn-sm', actionConfig.remove.css]"
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(ePerson.eperson) } }}">
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(eperson) } }}">
<i [ngClass]="actionConfig.remove.icon"></i>
</button>
<button *ngIf="!ePerson.memberOfGroup"
(click)="addMemberToGroup(ePerson)"
</div>
</td>
</tr>
</tbody>
</table>
</div>
</ds-pagination>
<div *ngIf="(ePeopleMembersOfGroup | async) == undefined || (ePeopleMembersOfGroup | async)?.totalElements == 0" class="alert alert-info w-100 mb-2"
role="alert">
{{messagePrefix + '.no-members-yet' | translate}}
</div>
<h3 id="search" class="border-bottom pb-2">
<span
*dsContextHelp="{
content: 'admin.access-control.groups.form.tooltip.editGroup.addEpeople',
id: 'edit-group-add-epeople',
iconPlacement: 'right',
tooltipPlacement: ['top', 'right', 'bottom']
}"
>
{{messagePrefix + '.search.head' | translate}}
</span>
</h3>
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
<div class="flex-grow-1 mr-3">
<div class="form-group input-group mr-3">
<input type="text" name="query" id="query" formControlName="query"
class="form-control" aria-label="Search input">
<span class="input-group-append">
<button type="submit" class="search-button btn btn-primary">
<i class="fas fa-search"></i> {{ messagePrefix + '.search.button' | translate }}</button>
</span>
</div>
</div>
<div>
<button (click)="clearFormAndResetResult();"
class="btn btn-secondary">{{messagePrefix + '.button.see-all' | translate}}</button>
</div>
</form>
<ds-pagination *ngIf="(ePeopleSearch | async)?.totalElements > 0"
[paginationOptions]="configSearch"
[collectionSize]="(ePeopleSearch | async)?.totalElements"
[hideGear]="true"
[hidePagerWhenSinglePage]="true">
<div class="table-responsive">
<table id="epersonsSearch" class="table table-striped table-hover table-bordered">
<thead>
<tr>
<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 (ePeopleSearch | async)?.page">
<td class="align-middle">{{eperson.id}}</td>
<td class="align-middle">
<a [routerLink]="getEPersonEditRoute(eperson.id)">
{{ dsoNameService.getName(eperson) }}
</a>
</td>
<td class="align-middle">
{{messagePrefix + '.table.email' | translate}}: {{ eperson.email ? eperson.email : '-' }}<br/>
{{messagePrefix + '.table.netid' | translate}}: {{ eperson.netid ? eperson.netid : '-' }}
</td>
<td class="align-middle">
<div class="btn-group edit-field">
<button (click)="addMemberToGroup(eperson)"
[disabled]="actionConfig.add.disabled"
[ngClass]="['btn btn-sm', actionConfig.add.css]"
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(ePerson.eperson) } }}">
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(eperson) } }}">
<i [ngClass]="actionConfig.add.icon"></i>
</button>
</div>
@@ -156,9 +128,10 @@
</ds-pagination>
<div *ngIf="(ePeopleMembersOfGroupDtos | async) == undefined || (ePeopleMembersOfGroupDtos | async)?.totalElements == 0" class="alert alert-info w-100 mb-2"
<div *ngIf="(ePeopleSearch | async)?.totalElements == 0 && searchDone"
class="alert alert-info w-100 mb-2"
role="alert">
{{messagePrefix + '.no-members-yet' | translate}}
{{messagePrefix + '.no-items' | translate}}
</div>
</ng-container>

View File

@@ -17,7 +17,7 @@ import { Group } from '../../../../core/eperson/models/group.model';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { GroupMock, GroupMock2 } from '../../../../shared/testing/group-mock';
import { GroupMock } from '../../../../shared/testing/group-mock';
import { MembersListComponent } from './members-list.component';
import { EPersonMock, EPersonMock2 } from '../../../../shared/testing/eperson.mock';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
@@ -39,28 +39,26 @@ describe('MembersListComponent', () => {
let ePersonDataServiceStub: any;
let groupsDataServiceStub: any;
let activeGroup;
let allEPersons: EPerson[];
let allGroups: Group[];
let epersonMembers: EPerson[];
let subgroupMembers: Group[];
let epersonNonMembers: EPerson[];
let paginationService;
beforeEach(waitForAsync(() => {
activeGroup = GroupMock;
epersonMembers = [EPersonMock2];
subgroupMembers = [GroupMock2];
allEPersons = [EPersonMock, EPersonMock2];
allGroups = [GroupMock, GroupMock2];
epersonNonMembers = [EPersonMock];
ePersonDataServiceStub = {
activeGroup: activeGroup,
epersonMembers: epersonMembers,
subgroupMembers: subgroupMembers,
epersonNonMembers: epersonNonMembers,
// This method is used to get all the current members
findListByHref(_href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
return createSuccessfulRemoteDataObject$(buildPaginatedList<EPerson>(new PageInfo(), groupsDataServiceStub.getEPersonMembers()));
},
searchByScope(scope: string, query: string): Observable<RemoteData<PaginatedList<EPerson>>> {
// This method is used to search across *non-members*
searchNonMembers(query: string, group: string): Observable<RemoteData<PaginatedList<EPerson>>> {
if (query === '') {
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), allEPersons));
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), epersonNonMembers));
}
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []));
},
@@ -70,29 +68,26 @@ describe('MembersListComponent', () => {
clearLinkRequests() {
// empty
},
getEPeoplePageRouterLink(): string {
return '/access-control/epeople';
}
};
groupsDataServiceStub = {
activeGroup: activeGroup,
epersonMembers: epersonMembers,
subgroupMembers: subgroupMembers,
allGroups: allGroups,
epersonNonMembers: epersonNonMembers,
getActiveGroup(): Observable<Group> {
return observableOf(activeGroup);
},
getEPersonMembers() {
return this.epersonMembers;
},
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
if (query === '') {
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), this.allGroups));
}
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []));
},
addMemberToGroup(parentGroup, eperson: EPerson): Observable<RestResponse> {
this.epersonMembers = [...this.epersonMembers, eperson];
addMemberToGroup(parentGroup, epersonToAdd: EPerson): Observable<RestResponse> {
// Add eperson to list of members
this.epersonMembers = [...this.epersonMembers, epersonToAdd];
// Remove eperson from list of non-members
this.epersonNonMembers.forEach( (eperson: EPerson, index: number) => {
if (eperson.id === epersonToAdd.id) {
this.epersonNonMembers.splice(index, 1);
}
});
return observableOf(new RestResponse(true, 200, 'Success'));
},
clearGroupsRequests() {
@@ -105,14 +100,14 @@ describe('MembersListComponent', () => {
return '/access-control/groups/' + group.id;
},
deleteMemberFromGroup(parentGroup, epersonToDelete: EPerson): Observable<RestResponse> {
this.epersonMembers = this.epersonMembers.find((eperson: EPerson) => {
if (eperson.id !== epersonToDelete.id) {
return eperson;
// Remove eperson from list of members
this.epersonMembers.forEach( (eperson: EPerson, index: number) => {
if (eperson.id === epersonToDelete.id) {
this.epersonMembers.splice(index, 1);
}
});
if (this.epersonMembers === undefined) {
this.epersonMembers = [];
}
// Add eperson to list of non-members
this.epersonNonMembers = [...this.epersonNonMembers, epersonToDelete];
return observableOf(new RestResponse(true, 200, 'Success'));
}
};
@@ -160,13 +155,37 @@ describe('MembersListComponent', () => {
expect(comp).toBeDefined();
}));
it('should show list of eperson members of current active group', () => {
const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child'));
expect(epersonIdsFound.length).toEqual(1);
epersonMembers.map((eperson: EPerson) => {
expect(epersonIdsFound.find((foundEl) => {
return (foundEl.nativeElement.textContent.trim() === eperson.uuid);
})).toBeTruthy();
describe('current members list', () => {
it('should show list of eperson members of current active group', () => {
const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child'));
expect(epersonIdsFound.length).toEqual(1);
epersonMembers.map((eperson: EPerson) => {
expect(epersonIdsFound.find((foundEl) => {
return (foundEl.nativeElement.textContent.trim() === eperson.uuid);
})).toBeTruthy();
});
});
it('should show a delete button next to each member', () => {
const epersonsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tbody tr'));
epersonsFound.map((foundEPersonRowElement: DebugElement) => {
const addButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-plus'));
const deleteButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-trash-alt'));
expect(addButton).toBeNull();
expect(deleteButton).not.toBeNull();
});
});
describe('if first delete button is pressed', () => {
beforeEach(() => {
const deleteButton: DebugElement = fixture.debugElement.query(By.css('#ePeopleMembersOfGroup tbody .fa-trash-alt'));
deleteButton.nativeElement.click();
fixture.detectChanges();
});
it('then no ePerson remains as a member of the active group.', () => {
const epersonsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tbody tr'));
expect(epersonsFound.length).toEqual(0);
});
});
});
@@ -174,76 +193,40 @@ describe('MembersListComponent', () => {
describe('when searching without query', () => {
let epersonsFound: DebugElement[];
beforeEach(fakeAsync(() => {
spyOn(component, 'isMemberOfGroup').and.callFake((ePerson: EPerson) => {
return observableOf(activeGroup.epersons.includes(ePerson));
});
component.search({ scope: 'metadata', query: '' });
tick();
fixture.detectChanges();
epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr'));
// Stop using the fake spy function (because otherwise the clicking on the buttons will not change anything
// because they don't change the value of activeGroup.epersons)
jasmine.getEnv().allowRespy(true);
spyOn(component, 'isMemberOfGroup').and.callThrough();
}));
it('should display all epersons', () => {
expect(epersonsFound.length).toEqual(2);
it('should display only non-members of the group', () => {
const epersonIdsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr td:first-child'));
expect(epersonIdsFound.length).toEqual(1);
epersonNonMembers.map((eperson: EPerson) => {
expect(epersonIdsFound.find((foundEl) => {
return (foundEl.nativeElement.textContent.trim() === eperson.uuid);
})).toBeTruthy();
});
});
describe('if eperson is already a eperson', () => {
it('should have delete button, else it should have add button', () => {
const memberIds: string[] = activeGroup.epersons.map((ePerson: EPerson) => ePerson.id);
epersonsFound.map((foundEPersonRowElement: DebugElement) => {
const epersonId: DebugElement = foundEPersonRowElement.query(By.css('td:first-child'));
const addButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-plus'));
const deleteButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-trash-alt'));
if (memberIds.includes(epersonId.nativeElement.textContent)) {
expect(addButton).toBeNull();
expect(deleteButton).not.toBeNull();
} else {
expect(deleteButton).toBeNull();
expect(addButton).not.toBeNull();
}
});
it('should display an add button next to non-members, not a delete button', () => {
epersonsFound.map((foundEPersonRowElement: DebugElement) => {
const addButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-plus'));
const deleteButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-trash-alt'));
expect(addButton).not.toBeNull();
expect(deleteButton).toBeNull();
});
});
describe('if first add button is pressed', () => {
beforeEach(fakeAsync(() => {
beforeEach(() => {
const addButton: DebugElement = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-plus'));
addButton.nativeElement.click();
tick();
fixture.detectChanges();
}));
it('then all the ePersons are member of the active group', () => {
epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr'));
expect(epersonsFound.length).toEqual(2);
epersonsFound.map((foundEPersonRowElement: DebugElement) => {
const addButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-plus'));
const deleteButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-trash-alt'));
expect(addButton).toBeNull();
expect(deleteButton).not.toBeNull();
});
});
});
describe('if first delete button is pressed', () => {
beforeEach(fakeAsync(() => {
const deleteButton: DebugElement = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-trash-alt'));
deleteButton.nativeElement.click();
tick();
fixture.detectChanges();
}));
it('then no ePerson is member of the active group', () => {
it('then all (two) ePersons are member of the active group. No non-members left', () => {
epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr'));
expect(epersonsFound.length).toEqual(2);
epersonsFound.map((foundEPersonRowElement: DebugElement) => {
const addButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-plus'));
const deleteButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-trash-alt'));
expect(deleteButton).toBeNull();
expect(addButton).not.toBeNull();
});
expect(epersonsFound.length).toEqual(0);
});
});
});

View File

@@ -4,38 +4,34 @@ import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import {
Observable,
of as observableOf,
Subscription,
BehaviorSubject,
combineLatest as observableCombineLatest,
ObservedValueOf,
BehaviorSubject
} from 'rxjs';
import { defaultIfEmpty, map, mergeMap, switchMap, take } from 'rxjs/operators';
import { buildPaginatedList, PaginatedList } from '../../../../core/data/paginated-list.model';
import { map, 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';
import { GroupDataService } from '../../../../core/eperson/group-data.service';
import { EPerson } from '../../../../core/eperson/models/eperson.model';
import { Group } from '../../../../core/eperson/models/group.model';
import {
getFirstSucceededRemoteData,
getFirstCompletedRemoteData,
getAllCompletedRemoteData,
getRemoteDataPayload
} from '../../../../core/shared/operators';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model';
import { PaginationService } from '../../../../core/pagination/pagination.service';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
import { getEPersonEditRoute } from '../../../access-control-routing-paths';
/**
* Keys to keep track of specific subscriptions
*/
enum SubKey {
ActiveGroup,
MembersDTO,
SearchResultsDTO,
Members,
SearchResults,
}
/**
@@ -96,11 +92,11 @@ export class MembersListComponent implements OnInit, OnDestroy {
/**
* EPeople being displayed in search result, initially all members, after search result of search
*/
ePeopleSearchDtos: BehaviorSubject<PaginatedList<EpersonDtoModel>> = new BehaviorSubject<PaginatedList<EpersonDtoModel>>(undefined);
ePeopleSearch: BehaviorSubject<PaginatedList<EPerson>> = new BehaviorSubject<PaginatedList<EPerson>>(undefined);
/**
* List of EPeople members of currently active group being edited
*/
ePeopleMembersOfGroupDtos: BehaviorSubject<PaginatedList<EpersonDtoModel>> = new BehaviorSubject<PaginatedList<EpersonDtoModel>>(undefined);
ePeopleMembersOfGroup: BehaviorSubject<PaginatedList<EPerson>> = new BehaviorSubject<PaginatedList<EPerson>>(undefined);
/**
* Pagination config used to display the list of EPeople that are result of EPeople search
@@ -129,7 +125,6 @@ export class MembersListComponent implements OnInit, OnDestroy {
// Current search in edit group - epeople search form
currentSearchQuery: string;
currentSearchScope: string;
// Whether or not user has done a EPeople search yet
searchDone: boolean;
@@ -137,6 +132,8 @@ export class MembersListComponent implements OnInit, OnDestroy {
// current active group being edited
groupBeingEdited: Group;
readonly getEPersonEditRoute = getEPersonEditRoute;
constructor(
protected groupDataService: GroupDataService,
public ePersonDataService: EPersonDataService,
@@ -148,18 +145,17 @@ export class MembersListComponent implements OnInit, OnDestroy {
public dsoNameService: DSONameService,
) {
this.currentSearchQuery = '';
this.currentSearchScope = 'metadata';
}
ngOnInit(): void {
this.searchForm = this.formBuilder.group(({
scope: 'metadata',
query: '',
}));
this.subs.set(SubKey.ActiveGroup, this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => {
if (activeGroup != null) {
this.groupBeingEdited = activeGroup;
this.retrieveMembers(this.config.currentPage);
this.search({query: ''});
}
}));
}
@@ -171,8 +167,8 @@ export class MembersListComponent implements OnInit, OnDestroy {
* @private
*/
retrieveMembers(page: number): void {
this.unsubFrom(SubKey.MembersDTO);
this.subs.set(SubKey.MembersDTO,
this.unsubFrom(SubKey.Members);
this.subs.set(SubKey.Members,
this.paginationService.getCurrentPagination(this.config.id, this.config).pipe(
switchMap((currentPagination) => {
return this.ePersonDataService.findListByHref(this.groupBeingEdited._links.epersons.href, {
@@ -189,49 +185,12 @@ export class MembersListComponent implements OnInit, OnDestroy {
return rd;
}
}),
switchMap((epersonListRD: RemoteData<PaginatedList<EPerson>>) => {
const dtos$ = observableCombineLatest([...epersonListRD.payload.page.map((member: EPerson) => {
const dto$: Observable<EpersonDtoModel> = observableCombineLatest(
this.isMemberOfGroup(member), (isMember: ObservedValueOf<Observable<boolean>>) => {
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
epersonDtoModel.eperson = member;
epersonDtoModel.memberOfGroup = isMember;
return epersonDtoModel;
});
return dto$;
})]);
return dtos$.pipe(defaultIfEmpty([]), map((dtos: EpersonDtoModel[]) => {
return buildPaginatedList(epersonListRD.payload.pageInfo, dtos);
}));
}))
.subscribe((paginatedListOfDTOs: PaginatedList<EpersonDtoModel>) => {
this.ePeopleMembersOfGroupDtos.next(paginatedListOfDTOs);
getRemoteDataPayload())
.subscribe((paginatedListOfEPersons: PaginatedList<EPerson>) => {
this.ePeopleMembersOfGroup.next(paginatedListOfEPersons);
}));
}
/**
* Whether the given ePerson is a member of the group currently being edited
* @param possibleMember EPerson that is a possible member (being tested) of the group currently being edited
*/
isMemberOfGroup(possibleMember: EPerson): Observable<boolean> {
return this.groupDataService.getActiveGroup().pipe(take(1),
mergeMap((group: Group) => {
if (group != null) {
return this.ePersonDataService.findListByHref(group._links.epersons.href, {
currentPage: 1,
elementsPerPage: 9999
})
.pipe(
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
map((listEPeopleInGroup: PaginatedList<EPerson>) => listEPeopleInGroup.page.filter((ePersonInList: EPerson) => ePersonInList.id === possibleMember.id)),
map((epeople: EPerson[]) => epeople.length > 0));
} else {
return observableOf(false);
}
}));
}
/**
* Unsubscribe from a subscription if it's still subscribed, and remove it from the map of
* active subscriptions
@@ -248,14 +207,18 @@ export class MembersListComponent implements OnInit, OnDestroy {
/**
* Deletes a given EPerson from the members list of the group currently being edited
* @param ePerson EPerson we want to delete as member from group that is currently being edited
* @param eperson EPerson we want to delete as member from group that is currently being edited
*/
deleteMemberFromGroup(ePerson: EpersonDtoModel) {
ePerson.memberOfGroup = false;
deleteMemberFromGroup(eperson: EPerson) {
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
if (activeGroup != null) {
const response = this.groupDataService.deleteMemberFromGroup(activeGroup, ePerson.eperson);
this.showNotifications('deleteMember', response, this.dsoNameService.getName(ePerson.eperson), activeGroup);
const response = this.groupDataService.deleteMemberFromGroup(activeGroup, eperson);
this.showNotifications('deleteMember', response, this.dsoNameService.getName(eperson), activeGroup);
// Reload search results (if there is an active query).
// This will potentially add this deleted subgroup into the list of search results.
if (this.currentSearchQuery != null) {
this.search({query: this.currentSearchQuery});
}
} else {
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
}
@@ -264,14 +227,18 @@ export class MembersListComponent implements OnInit, OnDestroy {
/**
* Adds a given EPerson to the members list of the group currently being edited
* @param ePerson EPerson we want to add as member to group that is currently being edited
* @param eperson EPerson we want to add as member to group that is currently being edited
*/
addMemberToGroup(ePerson: EpersonDtoModel) {
ePerson.memberOfGroup = true;
addMemberToGroup(eperson: EPerson) {
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
if (activeGroup != null) {
const response = this.groupDataService.addMemberToGroup(activeGroup, ePerson.eperson);
this.showNotifications('addMember', response, this.dsoNameService.getName(ePerson.eperson), activeGroup);
const response = this.groupDataService.addMemberToGroup(activeGroup, eperson);
this.showNotifications('addMember', response, this.dsoNameService.getName(eperson), activeGroup);
// Reload search results (if there is an active query).
// This will potentially add this deleted subgroup into the list of search results.
if (this.currentSearchQuery != null) {
this.search({query: this.currentSearchQuery});
}
} else {
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
}
@@ -279,37 +246,25 @@ export class MembersListComponent implements OnInit, OnDestroy {
}
/**
* Search in the EPeople by name, email or metadata
* @param data Contains scope and query param
* Search all EPeople who are NOT a member of the current group by name, email or metadata
* @param data Contains query param
*/
search(data: any) {
this.unsubFrom(SubKey.SearchResultsDTO);
this.subs.set(SubKey.SearchResultsDTO,
this.unsubFrom(SubKey.SearchResults);
this.subs.set(SubKey.SearchResults,
this.paginationService.getCurrentPagination(this.configSearch.id, this.configSearch).pipe(
switchMap((paginationOptions) => {
const query: string = data.query;
const scope: string = data.scope;
if (query != null && this.currentSearchQuery !== query && this.groupBeingEdited) {
this.router.navigate([], {
queryParamsHandling: 'merge'
});
this.currentSearchQuery = query;
this.paginationService.resetPage(this.configSearch.id);
}
if (scope != null && this.currentSearchScope !== scope && this.groupBeingEdited) {
this.router.navigate([], {
queryParamsHandling: 'merge'
});
this.currentSearchScope = scope;
this.paginationService.resetPage(this.configSearch.id);
}
this.searchDone = true;
return this.ePersonDataService.searchByScope(this.currentSearchScope, this.currentSearchQuery, {
return this.ePersonDataService.searchNonMembers(this.currentSearchQuery, this.groupBeingEdited.id, {
currentPage: paginationOptions.currentPage,
elementsPerPage: paginationOptions.pageSize
});
}, false, true);
}),
getAllCompletedRemoteData(),
map((rd: RemoteData<any>) => {
@@ -319,23 +274,9 @@ export class MembersListComponent implements OnInit, OnDestroy {
return rd;
}
}),
switchMap((epersonListRD: RemoteData<PaginatedList<EPerson>>) => {
const dtos$ = observableCombineLatest([...epersonListRD.payload.page.map((member: EPerson) => {
const dto$: Observable<EpersonDtoModel> = observableCombineLatest(
this.isMemberOfGroup(member), (isMember: ObservedValueOf<Observable<boolean>>) => {
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
epersonDtoModel.eperson = member;
epersonDtoModel.memberOfGroup = isMember;
return epersonDtoModel;
});
return dto$;
})]);
return dtos$.pipe(defaultIfEmpty([]), map((dtos: EpersonDtoModel[]) => {
return buildPaginatedList(epersonListRD.payload.pageInfo, dtos);
}));
}))
.subscribe((paginatedListOfDTOs: PaginatedList<EpersonDtoModel>) => {
this.ePeopleSearchDtos.next(paginatedListOfDTOs);
getRemoteDataPayload())
.subscribe((paginatedListOfEPersons: PaginatedList<EPerson>) => {
this.ePeopleSearch.next(paginatedListOfEPersons);
}));
}

View File

@@ -1,100 +1,10 @@
<ng-container>
<h3 class="border-bottom pb-2">{{messagePrefix + '.head' | translate}}</h3>
<h4 id="search" class="border-bottom pb-2">
<span *dsContextHelp="{
content: 'admin.access-control.groups.form.tooltip.editGroup.addSubgroups',
id: 'edit-group-add-subgroups',
iconPlacement: 'right',
tooltipPlacement: ['top', 'right', 'bottom']
}"
>
{{messagePrefix + '.search.head' | translate}}
</span>
</h4>
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
<div class="flex-grow-1 mr-3">
<div class="form-group input-group mr-3">
<input type="text" name="query" id="query" formControlName="query"
class="form-control" aria-label="Search input">
<span class="input-group-append">
<button type="submit" class="search-button btn btn-primary">
<i class="fas fa-search"></i> {{ messagePrefix + '.search.button' | translate }}
</button>
</span>
</div>
</div>
<div>
<button (click)="clearFormAndResetResult();" class="btn btn-secondary float-right">
{{messagePrefix + '.button.see-all' | translate}}
</button>
</div>
</form>
<ds-pagination *ngIf="(searchResults$ | async)?.payload?.totalElements > 0"
[paginationOptions]="configSearch"
[pageInfoState]="(searchResults$ | async)?.payload"
[collectionSize]="(searchResults$ | async)?.payload?.totalElements"
[hideGear]="true"
[hidePagerWhenSinglePage]="true">
<div class="table-responsive">
<table id="groupsSearch" class="table table-striped table-hover table-bordered">
<thead>
<tr>
<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 class="align-middle">{{group.id}}</td>
<td class="align-middle">
<a (click)="groupDataService.startEditingNewGroup(group)"
[routerLink]="[groupDataService.getGroupEditPageRouterLink(group)]">
{{ dsoNameService.getName(group) }}
</a>
</td>
<td class="align-middle">{{ dsoNameService.getName((group.object | async)?.payload) }}</td>
<td class="align-middle">
<div class="btn-group edit-field">
<button *ngIf="(isSubgroupOfGroup(group) | async) && !(isActiveGroup(group) | async)"
(click)="deleteSubgroupFromGroup(group)"
class="btn btn-outline-danger btn-sm deleteButton"
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(group) } }}">
<i class="fas fa-trash-alt fa-fw"></i>
</button>
<span *ngIf="(isActiveGroup(group) | async)">{{ messagePrefix + '.table.edit.currentGroup' | translate }}</span>
<button *ngIf="!(isSubgroupOfGroup(group) | async) && !(isActiveGroup(group) | async)"
(click)="addSubgroupToGroup(group)"
class="btn btn-outline-primary btn-sm addButton"
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(group) } }}">
<i class="fas fa-plus fa-fw"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</ds-pagination>
<div *ngIf="(searchResults$ | async)?.payload?.totalElements == 0 && searchDone" class="alert alert-info w-100 mb-2"
role="alert">
{{messagePrefix + '.no-items' | translate}}
</div>
<h4>{{messagePrefix + '.headSubgroups' | translate}}</h4>
<ds-pagination *ngIf="(subGroups$ | async)?.payload?.totalElements > 0"
[paginationOptions]="config"
[pageInfoState]="(subGroups$ | async)?.payload"
[collectionSize]="(subGroups$ | async)?.payload?.totalElements"
[hideGear]="true"
[hidePagerWhenSinglePage]="true">
@@ -139,4 +49,82 @@
{{messagePrefix + '.no-subgroups-yet' | translate}}
</div>
<h4 id="search" class="border-bottom pb-2">
<span *dsContextHelp="{
content: 'admin.access-control.groups.form.tooltip.editGroup.addSubgroups',
id: 'edit-group-add-subgroups',
iconPlacement: 'right',
tooltipPlacement: ['top', 'right', 'bottom']
}"
>
{{messagePrefix + '.search.head' | translate}}
</span>
</h4>
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
<div class="flex-grow-1 mr-3">
<div class="form-group input-group mr-3">
<input type="text" name="query" id="query" formControlName="query"
class="form-control" aria-label="Search input">
<span class="input-group-append">
<button type="submit" class="search-button btn btn-primary">
<i class="fas fa-search"></i> {{ messagePrefix + '.search.button' | translate }}
</button>
</span>
</div>
</div>
<div>
<button (click)="clearFormAndResetResult();" class="btn btn-secondary float-right">
{{messagePrefix + '.button.see-all' | translate}}
</button>
</div>
</form>
<ds-pagination *ngIf="(searchResults$ | async)?.payload?.totalElements > 0"
[paginationOptions]="configSearch"
[collectionSize]="(searchResults$ | async)?.payload?.totalElements"
[hideGear]="true"
[hidePagerWhenSinglePage]="true">
<div class="table-responsive">
<table id="groupsSearch" class="table table-striped table-hover table-bordered">
<thead>
<tr>
<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 class="align-middle">{{group.id}}</td>
<td class="align-middle">
<a (click)="groupDataService.startEditingNewGroup(group)"
[routerLink]="[groupDataService.getGroupEditPageRouterLink(group)]">
{{ dsoNameService.getName(group) }}
</a>
</td>
<td class="align-middle">{{ dsoNameService.getName((group.object | async)?.payload) }}</td>
<td class="align-middle">
<div class="btn-group edit-field">
<button (click)="addSubgroupToGroup(group)"
class="btn btn-outline-primary btn-sm addButton"
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(group) } }}">
<i class="fas fa-plus fa-fw"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</ds-pagination>
<div *ngIf="(searchResults$ | async)?.payload?.totalElements == 0 && searchDone" class="alert alert-info w-100 mb-2"
role="alert">
{{messagePrefix + '.no-items' | translate}}
</div>
</ng-container>

View File

@@ -1,12 +1,12 @@
import { CommonModule } from '@angular/common';
import { NO_ERRORS_SCHEMA, DebugElement } from '@angular/core';
import { ComponentFixture, fakeAsync, flush, inject, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, flush, inject, TestBed, waitForAsync } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule, By } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
import { Observable, of as observableOf, BehaviorSubject } from 'rxjs';
import { Observable, of as observableOf } from 'rxjs';
import { RestResponse } from '../../../../core/cache/response.models';
import { buildPaginatedList, PaginatedList } from '../../../../core/data/paginated-list.model';
import { RemoteData } from '../../../../core/data/remote-data';
@@ -18,19 +18,18 @@ import { NotificationsService } from '../../../../shared/notifications/notificat
import { GroupMock, GroupMock2 } from '../../../../shared/testing/group-mock';
import { SubgroupsListComponent } from './subgroups-list.component';
import {
createSuccessfulRemoteDataObject$,
createSuccessfulRemoteDataObject
createSuccessfulRemoteDataObject$
} from '../../../../shared/remote-data.utils';
import { RouterMock } from '../../../../shared/mocks/router.mock';
import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock';
import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock';
import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock';
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
import { map } from 'rxjs/operators';
import { PaginationService } from '../../../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../../shared/mocks/dso-name.service.mock';
import { EPersonMock2 } from 'src/app/shared/testing/eperson.mock';
describe('SubgroupsListComponent', () => {
let component: SubgroupsListComponent;
@@ -39,44 +38,70 @@ describe('SubgroupsListComponent', () => {
let builderService: FormBuilderService;
let ePersonDataServiceStub: any;
let groupsDataServiceStub: any;
let activeGroup;
let activeGroup: Group;
let subgroups: Group[];
let allGroups: Group[];
let groupNonMembers: Group[];
let routerStub;
let paginationService;
// Define a new mock activegroup for all tests below
let mockActiveGroup: Group = Object.assign(new Group(), {
handle: null,
subgroups: [GroupMock2],
epersons: [EPersonMock2],
selfRegistered: false,
permanent: false,
_links: {
self: {
href: 'https://rest.api/server/api/eperson/groups/activegroupid',
},
subgroups: { href: 'https://rest.api/server/api/eperson/groups/activegroupid/subgroups' },
object: { href: 'https://rest.api/server/api/eperson/groups/activegroupid/object' },
epersons: { href: 'https://rest.api/server/api/eperson/groups/activegroupid/epersons' }
},
_name: 'activegroupname',
id: 'activegroupid',
uuid: 'activegroupid',
type: 'group',
});
beforeEach(waitForAsync(() => {
activeGroup = GroupMock;
activeGroup = mockActiveGroup;
subgroups = [GroupMock2];
allGroups = [GroupMock, GroupMock2];
groupNonMembers = [GroupMock];
ePersonDataServiceStub = {};
groupsDataServiceStub = {
activeGroup: activeGroup,
subgroups$: new BehaviorSubject(subgroups),
subgroups: subgroups,
groupNonMembers: groupNonMembers,
getActiveGroup(): Observable<Group> {
return observableOf(this.activeGroup);
},
getSubgroups(): Group {
return this.activeGroup;
return this.subgroups;
},
// This method is used to get all the current subgroups
findListByHref(_href: string): Observable<RemoteData<PaginatedList<Group>>> {
return this.subgroups$.pipe(
map((currentGroups: Group[]) => {
return createSuccessfulRemoteDataObject(buildPaginatedList<Group>(new PageInfo(), currentGroups));
})
);
return createSuccessfulRemoteDataObject$(buildPaginatedList<Group>(new PageInfo(), groupsDataServiceStub.getSubgroups()));
},
getGroupEditPageRouterLink(group: Group): string {
return '/access-control/groups/' + group.id;
},
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
// This method is used to get all groups which are NOT currently a subgroup member
searchNonMemberGroups(query: string, group: string): Observable<RemoteData<PaginatedList<Group>>> {
if (query === '') {
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), allGroups));
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), groupNonMembers));
}
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []));
},
addSubGroupToGroup(parentGroup, subgroup: Group): Observable<RestResponse> {
this.subgroups$.next([...this.subgroups$.getValue(), subgroup]);
addSubGroupToGroup(parentGroup, subgroupToAdd: Group): Observable<RestResponse> {
// Add group to list of subgroups
this.subgroups = [...this.subgroups, subgroupToAdd];
// Remove group from list of non-members
this.groupNonMembers.forEach( (group: Group, index: number) => {
if (group.id === subgroupToAdd.id) {
this.groupNonMembers.splice(index, 1);
}
});
return observableOf(new RestResponse(true, 200, 'Success'));
},
clearGroupsRequests() {
@@ -85,12 +110,15 @@ describe('SubgroupsListComponent', () => {
clearGroupLinkRequests() {
// empty
},
deleteSubGroupFromGroup(parentGroup, subgroup: Group): Observable<RestResponse> {
this.subgroups$.next(this.subgroups$.getValue().filter((group: Group) => {
if (group.id !== subgroup.id) {
return group;
deleteSubGroupFromGroup(parentGroup, subgroupToDelete: Group): Observable<RestResponse> {
// Remove group from list of subgroups
this.subgroups.forEach( (group: Group, index: number) => {
if (group.id === subgroupToDelete.id) {
this.subgroups.splice(index, 1);
}
}));
});
// Add group to list of non-members
this.groupNonMembers = [...this.groupNonMembers, subgroupToDelete];
return observableOf(new RestResponse(true, 200, 'Success'));
}
};
@@ -99,7 +127,7 @@ describe('SubgroupsListComponent', () => {
translateService = getMockTranslateService();
paginationService = new PaginationServiceStub();
TestBed.configureTestingModule({
return TestBed.configureTestingModule({
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
TranslateModule.forRoot({
loader: {
@@ -137,30 +165,38 @@ describe('SubgroupsListComponent', () => {
expect(comp).toBeDefined();
}));
it('should show list of subgroups of current active group', () => {
const groupIdsFound = fixture.debugElement.queryAll(By.css('#subgroupsOfGroup tr td:first-child'));
expect(groupIdsFound.length).toEqual(1);
activeGroup.subgroups.map((group: Group) => {
expect(groupIdsFound.find((foundEl) => {
return (foundEl.nativeElement.textContent.trim() === group.uuid);
})).toBeTruthy();
});
});
describe('if first group delete button is pressed', () => {
let groupsFound: DebugElement[];
beforeEach(fakeAsync(() => {
const addButton = fixture.debugElement.query(By.css('#subgroupsOfGroup tbody .deleteButton'));
addButton.triggerEventHandler('click', {
preventDefault: () => {/**/
}
describe('current subgroup list', () => {
it('should show list of subgroups of current active group', () => {
const groupIdsFound = fixture.debugElement.queryAll(By.css('#subgroupsOfGroup tr td:first-child'));
expect(groupIdsFound.length).toEqual(1);
subgroups.map((group: Group) => {
expect(groupIdsFound.find((foundEl) => {
return (foundEl.nativeElement.textContent.trim() === group.uuid);
})).toBeTruthy();
});
});
it('should show a delete button next to each subgroup', () => {
const subgroupsFound = fixture.debugElement.queryAll(By.css('#subgroupsOfGroup tbody tr'));
subgroupsFound.map((foundGroupRowElement: DebugElement) => {
const addButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-plus'));
const deleteButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-trash-alt'));
expect(addButton).toBeNull();
expect(deleteButton).not.toBeNull();
});
});
describe('if first group delete button is pressed', () => {
let groupsFound: DebugElement[];
beforeEach(() => {
const deleteButton = fixture.debugElement.query(By.css('#subgroupsOfGroup tbody .deleteButton'));
deleteButton.nativeElement.click();
fixture.detectChanges();
});
it('then no subgroup remains as a member of the active group', () => {
groupsFound = fixture.debugElement.queryAll(By.css('#subgroupsOfGroup tbody tr'));
expect(groupsFound.length).toEqual(0);
});
tick();
fixture.detectChanges();
}));
it('one less subgroup in list from 1 to 0 (of 2 total groups)', () => {
groupsFound = fixture.debugElement.queryAll(By.css('#subgroupsOfGroup tbody tr'));
expect(groupsFound.length).toEqual(0);
});
});
@@ -169,54 +205,38 @@ describe('SubgroupsListComponent', () => {
let groupsFound: DebugElement[];
beforeEach(fakeAsync(() => {
component.search({ query: '' });
fixture.detectChanges();
groupsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr'));
}));
it('should display all groups', () => {
fixture.detectChanges();
groupsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr'));
expect(groupsFound.length).toEqual(2);
groupsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr'));
it('should display only non-member groups (i.e. groups that are not a subgroup)', () => {
const groupIdsFound: DebugElement[] = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr td:first-child'));
allGroups.map((group: Group) => {
expect(groupIdsFound.length).toEqual(1);
groupNonMembers.map((group: Group) => {
expect(groupIdsFound.find((foundEl: DebugElement) => {
return (foundEl.nativeElement.textContent.trim() === group.uuid);
})).toBeTruthy();
});
});
describe('if group is already a subgroup', () => {
it('should have delete button, else it should have add button', () => {
it('should display an add button next to non-member groups, not a delete button', () => {
groupsFound.map((foundGroupRowElement: DebugElement) => {
const addButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-plus'));
const deleteButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-trash-alt'));
expect(addButton).not.toBeNull();
expect(deleteButton).toBeNull();
});
});
describe('if first add button is pressed', () => {
beforeEach(() => {
const addButton: DebugElement = fixture.debugElement.query(By.css('#groupsSearch tbody .fa-plus'));
addButton.nativeElement.click();
fixture.detectChanges();
});
it('then all (two) Groups are subgroups of the active group. No non-members left', () => {
groupsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr'));
const getSubgroups = groupsDataServiceStub.getSubgroups().subgroups;
if (getSubgroups !== undefined && getSubgroups.length > 0) {
groupsFound.map((foundGroupRowElement: DebugElement) => {
const groupId: DebugElement = foundGroupRowElement.query(By.css('td:first-child'));
const addButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-plus'));
const deleteButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-trash-alt'));
expect(addButton).toBeNull();
if (activeGroup.id === groupId.nativeElement.textContent) {
expect(deleteButton).toBeNull();
} else {
expect(deleteButton).not.toBeNull();
}
});
} else {
const subgroupIds: string[] = activeGroup.subgroups.map((group: Group) => group.id);
groupsFound.map((foundGroupRowElement: DebugElement) => {
const groupId: DebugElement = foundGroupRowElement.query(By.css('td:first-child'));
const addButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-plus'));
const deleteButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-trash-alt'));
if (subgroupIds.includes(groupId.nativeElement.textContent)) {
expect(addButton).toBeNull();
expect(deleteButton).not.toBeNull();
} else {
expect(deleteButton).toBeNull();
expect(addButton).not.toBeNull();
}
});
}
expect(groupsFound.length).toEqual(0);
});
});
});

View File

@@ -2,16 +2,15 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { UntypedFormBuilder } from '@angular/forms';
import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs';
import { map, mergeMap, switchMap, take } from 'rxjs/operators';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { map, switchMap, take } from 'rxjs/operators';
import { PaginatedList } from '../../../../core/data/paginated-list.model';
import { RemoteData } from '../../../../core/data/remote-data';
import { GroupDataService } from '../../../../core/eperson/group-data.service';
import { Group } from '../../../../core/eperson/models/group.model';
import {
getFirstCompletedRemoteData,
getFirstSucceededRemoteData,
getRemoteDataPayload
getAllCompletedRemoteData,
getFirstCompletedRemoteData
} from '../../../../core/shared/operators';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
@@ -103,6 +102,7 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
if (activeGroup != null) {
this.groupBeingEdited = activeGroup;
this.retrieveSubGroups();
this.search({query: ''});
}
}));
}
@@ -131,47 +131,6 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
}));
}
/**
* Whether or not the given group is a subgroup of the group currently being edited
* @param possibleSubgroup Group that is a possible subgroup (being tested) of the group currently being edited
*/
isSubgroupOfGroup(possibleSubgroup: Group): Observable<boolean> {
return this.groupDataService.getActiveGroup().pipe(take(1),
mergeMap((activeGroup: Group) => {
if (activeGroup != null) {
if (activeGroup.uuid === possibleSubgroup.uuid) {
return observableOf(false);
} else {
return this.groupDataService.findListByHref(activeGroup._links.subgroups.href, {
currentPage: 1,
elementsPerPage: 9999
})
.pipe(
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
map((listTotalGroups: PaginatedList<Group>) => listTotalGroups.page.filter((groupInList: Group) => groupInList.id === possibleSubgroup.id)),
map((groups: Group[]) => groups.length > 0));
}
} else {
return observableOf(false);
}
}));
}
/**
* Whether or not the given group is the current group being edited
* @param group Group that is possibly the current group being edited
*/
isActiveGroup(group: Group): Observable<boolean> {
return this.groupDataService.getActiveGroup().pipe(take(1),
mergeMap((activeGroup: Group) => {
if (activeGroup != null && activeGroup.uuid === group.uuid) {
return observableOf(true);
}
return observableOf(false);
}));
}
/**
* Deletes given subgroup from the group currently being edited
* @param subgroup Group we want to delete from the subgroups of the group currently being edited
@@ -181,6 +140,11 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
if (activeGroup != null) {
const response = this.groupDataService.deleteSubGroupFromGroup(activeGroup, subgroup);
this.showNotifications('deleteSubgroup', response, this.dsoNameService.getName(subgroup), activeGroup);
// Reload search results (if there is an active query).
// This will potentially add this deleted subgroup into the list of search results.
if (this.currentSearchQuery != null) {
this.search({query: this.currentSearchQuery});
}
} else {
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
}
@@ -197,6 +161,11 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
if (activeGroup.uuid !== subgroup.uuid) {
const response = this.groupDataService.addSubGroupToGroup(activeGroup, subgroup);
this.showNotifications('addSubgroup', response, this.dsoNameService.getName(subgroup), activeGroup);
// Reload search results (if there is an active query).
// This will potentially remove this added subgroup from search results.
if (this.currentSearchQuery != null) {
this.search({query: this.currentSearchQuery});
}
} else {
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.subgroupToAddIsActiveGroup'));
}
@@ -207,28 +176,38 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
}
/**
* Search in the groups (searches by group name and by uuid exact match)
* Search all non-member groups (searches by group name and by uuid exact match). Used to search for
* groups that could be added to current group as a subgroup.
* @param data Contains query param
*/
search(data: any) {
const query: string = data.query;
if (query != null && this.currentSearchQuery !== query) {
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLink(this.groupBeingEdited));
this.currentSearchQuery = query;
this.configSearch.currentPage = 1;
}
this.searchDone = true;
this.unsubFrom(SubKey.SearchResults);
this.subs.set(SubKey.SearchResults, this.paginationService.getCurrentPagination(this.configSearch.id, this.configSearch).pipe(
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);
}));
this.subs.set(SubKey.SearchResults,
this.paginationService.getCurrentPagination(this.configSearch.id, this.configSearch).pipe(
switchMap((paginationOptions) => {
const query: string = data.query;
if (query != null && this.currentSearchQuery !== query && this.groupBeingEdited) {
this.currentSearchQuery = query;
this.paginationService.resetPage(this.configSearch.id);
}
this.searchDone = true;
return this.groupDataService.searchNonMemberGroups(this.currentSearchQuery, this.groupBeingEdited.id, {
currentPage: paginationOptions.currentPage,
elementsPerPage: paginationOptions.pageSize
}, false, true, followLink('object'));
}),
getAllCompletedRemoteData(),
map((rd: RemoteData<any>) => {
if (rd.hasFailed) {
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure', { cause: rd.errorMessage }));
} else {
return rd;
}
}))
.subscribe((rd: RemoteData<PaginatedList<Group>>) => {
this.searchResults$.next(rd);
}));
}
/**

View File

@@ -2,17 +2,17 @@
<div class="groups-registry row">
<div class="col-12">
<div class="d-flex justify-content-between border-bottom mb-3">
<h2 id="header" class="pb-2">{{messagePrefix + 'head' | translate}}</h2>
<h1 id="header" class="pb-2">{{messagePrefix + 'head' | translate}}</h1>
<div>
<button class="mr-auto btn btn-success"
[routerLink]="['newGroup']">
[routerLink]="'create'">
<i class="fas fa-plus"></i>
<span class="d-none d-sm-inline ml-1">{{messagePrefix + 'button.add' | translate}}</span>
</button>
</div>
</div>
<h3 id="search" class="border-bottom pb-2">{{messagePrefix + 'search.head' | translate}}</h3>
<h2 id="search" class="border-bottom pb-2">{{messagePrefix + 'search.head' | translate}}</h2>
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
<div class="flex-grow-1 mr-3">
<div class="form-group input-group">
@@ -37,7 +37,6 @@
<ds-pagination
*ngIf="(pageInfoState$ | async)?.totalElements > 0 && !(loading$ | async)"
[paginationOptions]="config"
[pageInfoState]="pageInfoState$"
[collectionSize]="(pageInfoState$ | async)?.totalElements"
[hideGear]="true"
[hidePagerWhenSinglePage]="true">

View File

@@ -216,18 +216,28 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
/**
* Get the members (epersons embedded value of a group)
* NOTE: At this time we only grab the *first* member in order to receive the `totalElements` value
* needed for our HTML template.
* @param group
*/
getMembers(group: Group): Observable<RemoteData<PaginatedList<EPerson>>> {
return this.ePersonDataService.findListByHref(group._links.epersons.href).pipe(getFirstSucceededRemoteData());
return this.ePersonDataService.findListByHref(group._links.epersons.href, {
currentPage: 1,
elementsPerPage: 1,
}).pipe(getFirstSucceededRemoteData());
}
/**
* Get the subgroups (groups embedded value of a group)
* NOTE: At this time we only grab the *first* subgroup in order to receive the `totalElements` value
* needed for our HTML template.
* @param group
*/
getSubgroups(group: Group): Observable<RemoteData<PaginatedList<Group>>> {
return this.groupService.findListByHref(group._links.subgroups.href).pipe(getFirstSucceededRemoteData());
return this.groupService.findListByHref(group._links.subgroups.href, {
currentPage: 1,
elementsPerPage: 1,
}).pipe(getFirstSucceededRemoteData());
}
/**

View File

@@ -1,4 +1,4 @@
<div class="container">
<h2>{{'admin.curation-tasks.header' |translate }}</h2>
<h1>{{'admin.curation-tasks.header' |translate }}</h1>
<ds-curation-form></ds-curation-form>
</div>

View File

@@ -1,5 +1,5 @@
<div class="container">
<h2 id="header">{{'admin.batch-import.page.header' | translate}}</h2>
<h1 id="header">{{'admin.batch-import.page.header' | translate}}</h1>
<p>{{'admin.batch-import.page.help' | translate}}</p>
<p *ngIf="dso">
selected collection: <b>{{getDspaceObjectName()}}</b>&nbsp;
@@ -28,7 +28,7 @@
<small class="form-text text-muted">
{{'admin.batch-import.page.toggle.help' | translate}}
</small>
<ds-file-dropzone-no-uploader
*ngIf="isUpload"

View File

@@ -1,5 +1,5 @@
<div class="container">
<h2 id="header">{{'admin.metadata-import.page.header' | translate}}</h2>
<h1 id="header">{{'admin.metadata-import.page.header' | translate}}</h1>
<p>{{'admin.metadata-import.page.help' | translate}}</p>
<div class="form-group">
<div class="form-check">

View File

@@ -1,11 +1,11 @@
<div class="container">
<div class="row">
<div class="col-12 mb-4">
<h2 id="sub-header"
class="border-bottom mb-2">{{ 'admin.registries.bitstream-formats.create.new' | translate }}</h2>
<h1 id="sub-header"
class="border-bottom pb-2">{{ 'admin.registries.bitstream-formats.create.new' | translate }}</h1>
<ds-bitstream-format-form (updatedFormat)="createBitstreamFormat($event)"></ds-bitstream-format-form>
</div>
</div>
</div>
</div>

View File

@@ -2,7 +2,7 @@
<div class="bitstream-formats row">
<div class="col-12">
<h2 id="header" class="border-bottom pb-2 ">{{'admin.registries.bitstream-formats.head' | translate}}</h2>
<h1 id="header" class="border-bottom pb-2">{{'admin.registries.bitstream-formats.head' | translate}}</h1>
<p id="description">{{'admin.registries.bitstream-formats.description' | translate}}</p>
<p id="create-new" class="mb-2"><a [routerLink]="'add'" class="btn btn-success">{{'admin.registries.bitstream-formats.create.new' | translate}}</a></p>
@@ -11,7 +11,6 @@
<ds-pagination
*ngIf="(bitstreamFormats | async)?.payload?.totalElements > 0"
[paginationOptions]="pageConfig"
[pageInfoState]="(bitstreamFormats | async)?.payload"
[collectionSize]="(bitstreamFormats | async)?.payload?.totalElements"
[hideGear]="false"
[hidePagerWhenSinglePage]="true">
@@ -19,7 +18,7 @@
<table id="formats" class="table table-striped table-hover">
<thead>
<tr>
<th scope="col"></th>
<th scope="col"><span class="sr-only">{{'admin.registries.bitstream-formats.table.selected' | translate}}</span></th>
<th scope="col">{{'admin.registries.bitstream-formats.table.id' | translate}}</th>
<th scope="col">{{'admin.registries.bitstream-formats.table.name' | translate}}</th>
<th scope="col">{{'admin.registries.bitstream-formats.table.mimetype' | translate}}</th>
@@ -31,9 +30,11 @@
<td>
<label class="mb-0">
<input type="checkbox"
[attr.aria-label]="'admin.registries.bitstream-formats.select' | translate"
[checked]="isSelected(bitstreamFormat) | async"
(change)="selectBitStreamFormat(bitstreamFormat, $event)"
>
<span class="sr-only">{{'admin.registries.bitstream-formats.select' | translate}}}</span>
</label>
</td>
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.id}}</a></td>

View File

@@ -15,6 +15,7 @@ import { NotificationsService } from '../../../shared/notifications/notification
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../../core/shared/bitstream-format-support-level';
import { XSRFService } from '../../../core/xsrf/xsrf.service';
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
import { TestScheduler } from 'rxjs/testing';
import {
@@ -108,7 +109,8 @@ describe('BitstreamFormatsComponent', () => {
{ provide: BitstreamFormatDataService, useValue: bitstreamFormatService },
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
{ provide: NotificationsService, useValue: notificationsServiceStub },
{ provide: PaginationService, useValue: paginationService }
{ provide: PaginationService, useValue: paginationService },
{ provide: XSRFService, useValue: {} },
]
}).compileComponents();
};

View File

@@ -1,11 +1,11 @@
<div class="container">
<div class="row">
<div class="col-12 mb-4">
<h2 id="sub-header"
class="border-bottom mb-2">{{'admin.registries.bitstream-formats.edit.head' | translate:{format: (bitstreamFormatRD$ | async)?.payload.shortDescription} }}</h2>
<h1 id="sub-header"
class="border-bottom pb-2">{{'admin.registries.bitstream-formats.edit.head' | translate:{format: (bitstreamFormatRD$ | async)?.payload.shortDescription} }}</h1>
<ds-bitstream-format-form [bitstreamFormat]="(bitstreamFormatRD$ | async)?.payload" (updatedFormat)="updateFormat($event)"></ds-bitstream-format-form>
</div>
</div>
</div>
</div>

View File

@@ -2,7 +2,7 @@
<div class="metadata-registry row">
<div class="col-12">
<h2 id="header" class="border-bottom pb-2">{{'admin.registries.metadata.head' | translate}}</h2>
<h1 id="header" class="border-bottom pb-2">{{'admin.registries.metadata.head' | translate}}</h1>
<p id="description" class="pb-2">{{'admin.registries.metadata.description' | translate}}</p>
@@ -19,7 +19,7 @@
<table id="metadata-schemas" class="table table-striped table-hover">
<thead>
<tr>
<th scope="col"></th>
<th scope="col"><span class="sr-only">{{'admin.registries.metadata.schemas.table.selected' | translate}}</span></th>
<th scope="col">{{'admin.registries.metadata.schemas.table.id' | translate}}</th>
<th scope="col">{{'admin.registries.metadata.schemas.table.namespace' | translate}}</th>
<th scope="col">{{'admin.registries.metadata.schemas.table.name' | translate}}</th>
@@ -34,6 +34,7 @@
[checked]="isSelected(schema) | async"
(change)="selectMetadataSchema(schema, $event)"
>
<span class="sr-only">{{((isSelected(schema) | async) ? 'admin.registries.metadata.schemas.deselect' : 'admin.registries.metadata.schemas.select') | translate}}</span>
</label>
</td>
<td class="selectable-row" (click)="editSchema(schema)"><a [routerLink]="[schema.prefix]">{{schema.id}}</a></td>

View File

@@ -1,11 +1,11 @@
<div *ngIf="registryService.getActiveMetadataSchema() | async; then editheader; else createHeader"></div>
<ng-template #createHeader>
<h4>{{messagePrefix + '.create' | translate}}</h4>
<h2>{{messagePrefix + '.create' | translate}}</h2>
</ng-template>
<ng-template #editheader>
<h4>{{messagePrefix + '.edit' | translate}}</h4>
<h2>{{messagePrefix + '.edit' | translate}}</h2>
</ng-template>
<ds-form [formId]="formId"

View File

@@ -8,9 +8,9 @@ import {
import { UntypedFormGroup } from '@angular/forms';
import { RegistryService } from '../../../../core/registry/registry.service';
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
import { take } from 'rxjs/operators';
import { switchMap, take, tap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { combineLatest } from 'rxjs';
import { Observable, combineLatest } from 'rxjs';
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
@Component({
@@ -147,30 +147,48 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy {
* Emit the updated/created schema using the EventEmitter submitForm
*/
onSubmit(): void {
this.registryService.clearMetadataSchemaRequests().subscribe();
this.registryService.getActiveMetadataSchema().pipe(take(1)).subscribe(
(schema: MetadataSchema) => {
const values = {
prefix: this.name.value,
namespace: this.namespace.value
};
if (schema == null) {
this.registryService.createOrUpdateMetadataSchema(Object.assign(new MetadataSchema(), values)).subscribe((newSchema) => {
this.submitForm.emit(newSchema);
this.registryService
.getActiveMetadataSchema()
.pipe(
take(1),
switchMap((schema: MetadataSchema) => {
const metadataValues = {
prefix: this.name.value,
namespace: this.namespace.value,
};
let createOrUpdate$: Observable<MetadataSchema>;
if (schema == null) {
createOrUpdate$ =
this.registryService.createOrUpdateMetadataSchema(
Object.assign(new MetadataSchema(), metadataValues)
);
} else {
const updatedSchema = Object.assign(
new MetadataSchema(),
schema,
{
namespace: metadataValues.namespace,
}
);
createOrUpdate$ =
this.registryService.createOrUpdateMetadataSchema(
updatedSchema
);
}
return createOrUpdate$;
}),
tap(() => {
this.registryService.clearMetadataSchemaRequests().subscribe();
})
)
.subscribe((updatedOrCreatedSchema: MetadataSchema) => {
this.submitForm.emit(updatedOrCreatedSchema);
this.clearFields();
this.registryService.cancelEditMetadataSchema();
});
} else {
this.registryService.createOrUpdateMetadataSchema(Object.assign(new MetadataSchema(), schema, {
id: schema.id,
prefix: schema.prefix,
namespace: values.namespace,
})).subscribe((updatedSchema: MetadataSchema) => {
this.submitForm.emit(updatedSchema);
});
}
this.clearFields();
this.registryService.cancelEditMetadataSchema();
}
);
}
/**

View File

@@ -1,11 +1,11 @@
<div *ngIf="registryService.getActiveMetadataField() | async; then editheader; else createHeader"></div>
<ng-template #createHeader>
<h4>{{messagePrefix + '.create' | translate}}</h4>
<h2>{{messagePrefix + '.create' | translate}}</h2>
</ng-template>
<ng-template #editheader>
<h4>{{messagePrefix + '.edit' | translate}}</h4>
<h2>{{messagePrefix + '.edit' | translate}}</h2>
</ng-template>
<ds-form [formId]="formId"

View File

@@ -3,7 +3,8 @@ import {
DynamicFormControlModel,
DynamicFormGroupModel,
DynamicFormLayout,
DynamicInputModel
DynamicInputModel,
DynamicTextAreaModel
} from '@ng-dynamic-forms/core';
import { UntypedFormGroup } from '@angular/forms';
import { RegistryService } from '../../../../core/registry/registry.service';
@@ -51,7 +52,7 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
/**
* A dynamic input model for the scopeNote field
*/
scopeNote: DynamicInputModel;
scopeNote: DynamicTextAreaModel;
/**
* A list of all dynamic input models
@@ -132,11 +133,12 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
maxLength: 'error.validation.metadata.qualifier.max-length',
},
});
this.scopeNote = new DynamicInputModel({
this.scopeNote = new DynamicTextAreaModel({
id: 'scopeNote',
label: scopenote,
name: 'scopeNote',
required: false,
rows: 5,
});
this.formModel = [
new DynamicFormGroupModel(

View File

@@ -2,7 +2,7 @@
<div class="metadata-schema row">
<div class="col-12" *ngVar="(metadataSchema$ | async) as schema">
<h2 id="header" class="border-bottom pb-2">{{'admin.registries.schema.head' | translate}}: "{{schema?.prefix}}"</h2>
<h1 id="header" class="border-bottom pb-2">{{'admin.registries.schema.head' | translate}}: "{{schema?.prefix}}"</h1>
<p id="description" class="pb-2">{{'admin.registries.schema.description' | translate:{ namespace: schema?.namespace } }}</p>
@@ -10,13 +10,12 @@
[metadataSchema]="schema"
(submitForm)="forceUpdateFields()"></ds-metadata-field-form>
<h3>{{'admin.registries.schema.fields.head' | translate}}</h3>
<h2>{{'admin.registries.schema.fields.head' | translate}}</h2>
<ng-container *ngVar="(metadataFields$ | async)?.payload as fields">
<ds-pagination
*ngIf="fields?.totalElements > 0"
[paginationOptions]="config"
[pageInfoState]="fields"
[collectionSize]="fields?.totalElements"
[hideGear]="false"
[hidePagerWhenSinglePage]="true">
@@ -24,7 +23,7 @@
<table id="metadata-fields" class="table table-striped table-hover">
<thead>
<tr>
<th></th>
<th><span class="sr-only">{{'admin.registries.schema.fields.table.selected' | translate}}</span></th>
<th scope="col">{{'admin.registries.schema.fields.table.id' | translate}}</th>
<th scope="col">{{'admin.registries.schema.fields.table.field' | translate}}</th>
<th scope="col">{{'admin.registries.schema.fields.table.scopenote' | translate}}</th>
@@ -33,15 +32,14 @@
<tbody>
<tr *ngFor="let field of fields?.page"
[ngClass]="{'table-primary' : isActive(field) | async}">
<td>
<label class="mb-0">
<input type="checkbox"
[checked]="isSelected(field) | async"
(change)="selectMetadataField(field, $event)">
</label>
<td *ngVar="(isSelected(field) | async) as selected">
<input type="checkbox"
[attr.aria-label]="(selected ? 'admin.registries.schema.fields.deselect' : 'admin.registries.schema.fields.select') | translate"
[checked]="selected"
(change)="selectMetadataField(field, $event)">
</td>
<td class="selectable-row" (click)="editField(field)">{{field.id}}</td>
<td class="selectable-row" (click)="editField(field)">{{schema?.prefix}}.{{field.element}}<label *ngIf="field.qualifier" class="mb-0">.</label>{{field.qualifier}}</td>
<td class="selectable-row" (click)="editField(field)">{{schema?.prefix}}.{{field.element}}{{field.qualifier ? '.' + field.qualifier : ''}}</td>
<td class="selectable-row" (click)="editField(field)">{{field.scopeNote}}</td>
</tr>
</tbody>

View File

@@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { RegistryService } from '../../../core/registry/registry.service';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute } from '@angular/router';
import {
BehaviorSubject,
combineLatest as observableCombineLatest,
@@ -32,7 +32,7 @@ import { PaginationService } from '../../../core/pagination/pagination.service';
* A component used for managing all existing metadata fields within the current metadata schema.
* The admin can create, edit or delete metadata fields here.
*/
export class MetadataSchemaComponent implements OnInit {
export class MetadataSchemaComponent implements OnInit, OnDestroy {
/**
* The metadata schema
*/
@@ -60,7 +60,6 @@ export class MetadataSchemaComponent implements OnInit {
constructor(private registryService: RegistryService,
private route: ActivatedRoute,
private notificationsService: NotificationsService,
private router: Router,
private paginationService: PaginationService,
private translateService: TranslateService) {
@@ -86,7 +85,7 @@ export class MetadataSchemaComponent implements OnInit {
*/
private updateFields() {
this.metadataFields$ = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe(
switchMap((currentPagination) => combineLatest(this.metadataSchema$, this.needsUpdate$, observableOf(currentPagination))),
switchMap((currentPagination) => combineLatest([this.metadataSchema$, this.needsUpdate$, observableOf(currentPagination)])),
switchMap(([schema, update, currentPagination]: [MetadataSchema, boolean, PaginationComponentOptions]) => {
if (update) {
this.needsUpdate$.next(false);
@@ -193,10 +192,10 @@ export class MetadataSchemaComponent implements OnInit {
showNotification(success: boolean, amount: number) {
const prefix = 'admin.registries.schema.notification';
const suffix = success ? 'success' : 'failure';
const messages = observableCombineLatest(
const messages = observableCombineLatest([
this.translateService.get(success ? `${prefix}.${suffix}` : `${prefix}.${suffix}`),
this.translateService.get(`${prefix}.field.deleted.${suffix}`, { amount: amount })
);
]);
messages.subscribe(([head, content]) => {
if (success) {
this.notificationsService.success(head, content);
@@ -207,6 +206,7 @@ export class MetadataSchemaComponent implements OnInit {
}
ngOnDestroy(): void {
this.paginationService.clearPagination(this.config.id);
this.registryService.deselectAllMetadataField();
}
}

View File

@@ -1,23 +1,21 @@
<div class="sidebar-section">
<a class="nav-item nav-link d-flex flex-row flex-nowrap"
[ngClass]="{ disabled: isDisabled }"
[attr.aria-disabled]="isDisabled"
[attr.aria-labelledby]="'sidebarName-' + section.id"
[title]="('menu.section.icon.' + section.id) | translate"
[routerLink]="itemModel.link"
(keyup.space)="navigate($event)"
(keyup.enter)="navigate($event)"
href="javascript:void(0);"
>
<div class="shortcut-icon">
<i class="fas fa-{{section.icon}} fa-fw"></i>
<div>
<a class="sidebar-section-wrapper"
[ngClass]="{ disabled: isDisabled }"
role="menuitem"
[attr.aria-disabled]="isDisabled"
[attr.aria-labelledby]="adminMenuSectionTitleId(section.id)"
[routerLink]="itemModel.link"
(keyup.space)="navigate($event)"
(keyup.enter)="navigate($event)"
href="javascript:void(0);"
>
<div class="sidebar-fixed-element-wrapper" data-test="sidebar-section-icon" aria-hidden="true">
<i class="fas fa-{{section.icon}} fa-fw"></i>
</div>
<div class="sidebar-collapsible-element-outer-wrapper">
<div class="sidebar-collapsible-element-inner-wrapper sidebar-item">
<span [id]="adminMenuSectionTitleId(section.id)">{{itemModel.text | translate}}</span>
</div>
<div class="sidebar-collapsible">
<div class="toggle">
<span id="sidebarName-{{section.id}}" class="section-header-text">
{{itemModel.text | translate}}
</span>
</div>
</div>
</a>
</div>
</a>
</div>

View File

@@ -48,7 +48,7 @@ describe('AdminSidebarSectionComponent', () => {
});
it('should set the right icon', () => {
const icon = fixture.debugElement.query(By.css('.shortcut-icon')).query(By.css('i.fas'));
const icon = fixture.debugElement.query(By.css('[data-test="sidebar-section-icon"]')).query(By.css('i.fas'));
expect(icon.nativeElement.getAttribute('class')).toContain('fa-' + iconString);
});
it('should not contain the disabled class', () => {
@@ -88,7 +88,7 @@ describe('AdminSidebarSectionComponent', () => {
});
it('should set the right icon', () => {
const icon = fixture.debugElement.query(By.css('.shortcut-icon')).query(By.css('i.fas'));
const icon = fixture.debugElement.query(By.css('[data-test="sidebar-section-icon"]')).query(By.css('i.fas'));
expect(icon.nativeElement.getAttribute('class')).toContain('fa-' + iconString);
});
it('should contain the disabled class', () => {

View File

@@ -12,8 +12,7 @@ import { Router } from '@angular/router';
* Represents a non-expandable section in the admin sidebar
*/
@Component({
/* eslint-disable @angular-eslint/component-selector */
selector: 'li[ds-admin-sidebar-section]',
selector: 'ds-admin-sidebar-section',
templateUrl: './admin-sidebar-section.component.html',
styleUrls: ['./admin-sidebar-section.component.scss'],
@@ -53,4 +52,12 @@ export class AdminSidebarSectionComponent extends MenuSectionComponent implement
this.router.navigate(this.itemModel.link);
}
}
adminMenuSectionId(sectionId: string) {
return `admin-menu-section-${sectionId}`;
}
adminMenuSectionTitleId(sectionId: string) {
return `admin-menu-section-${sectionId}-title`;
}
}

View File

@@ -1,55 +1,63 @@
<nav class="navbar navbar-dark p-0"
[ngClass]="{'active': sidebarOpen, 'inactive': sidebarClosed}"
<nav class="navbar navbar-dark p-0 vh-100"
id="admin-sidebar"
[attr.aria-label]="'menu.header.nav.description' | translate"
[ngClass]="{'expanded': sidebarOpen, 'collapsed': sidebarClosed, 'transitioning': sidebarTransitioning}"
[@slideSidebar]="{
value: (!(sidebarExpanded | async) ? 'collapsed' : 'expanded'),
params: {sidebarWidth: (sidebarWidth | async)}
params: { collapsedWidth: (collapsedSidebarWidth$ | async), expandedWidth: (expandedSidebarWidth$ | async) }
}" (@slideSidebar.done)="finishSlide($event)" (@slideSidebar.start)="startSlide($event)"
*ngIf="menuVisible | async"
(mouseenter)="handleMouseEnter($event)"
(mouseleave)="handleMouseLeave($event)"
role="navigation" [attr.aria-label]="'menu.header.admin.description' |translate">
<div class="sidebar-top-level-items">
<ul class="navbar-nav">
<li class="admin-menu-header">
<div class="sidebar-section">
<div href="javascript:void(0);" class="nav-item d-flex flex-row flex-nowrap py-0">
<div class="shortcut-icon navbar-brand logo-wrapper">
<img class="admin-logo" src="assets/images/dspace-logo-mini.svg"
[alt]="('menu.header.image.logo') | translate">
</div>
<div class="sidebar-collapsible navbar-brand">
<div class="mr-0">
<h4 class="section-header-text mb-0">{{ 'menu.header.admin' | translate }}</h4>
</div>
</div>
</div>
</div>
</li>
(mouseleave)="handleMouseLeave($event)">
<ng-container *ngFor="let section of (sections | async)">
<ng-container
*ngComponentOutlet="(sectionMap$ | async).get(section.id).component; injector: (sectionMap$ | async).get(section.id).injector;"></ng-container>
</ng-container>
</ul>
</div>
<div class="navbar-nav">
<div class="sidebar-section" id="sidebar-collapse-toggle">
<a class="nav-item nav-link sidebar-section d-flex flex-row flex-nowrap"
href="javascript:void(0);"
(click)="toggle($event)"
(keyup.space)="toggle($event)"
>
<div class="shortcut-icon">
<i *ngIf="(menuCollapsed | async)" class="fas fa-fw fa-angle-double-right"
[title]="'menu.section.icon.pin' | translate"></i>
<i *ngIf="!(menuCollapsed | async)" class="fas fa-fw fa-angle-double-left"
[title]="'menu.section.icon.unpin' | translate"></i>
</div>
<div class="sidebar-collapsible">
<span *ngIf="menuCollapsed | async" class="section-header-text">{{'menu.section.pin' | translate }}</span>
<span *ngIf="!(menuCollapsed | async)" class="section-header-text">{{'menu.section.unpin' | translate }}</span>
</div>
</a>
<!-- HEADER -->
<div class="sidebar-full-width-container" id="sidebar-header-container" aria-hidden="true">
<div class="sidebar-section-wrapper">
<div class="sidebar-fixed-element-wrapper">
<img id="admin-sidebar-logo" src="assets/images/dspace-logo-mini.svg" [alt]="('menu.header.image.logo') | translate" aria-hidden="true">
</div>
<div class="sidebar-collapsible-element-outer-wrapper">
<div class="sidebar-collapsible-element-inner-wrapper sidebar-item">
<h4 class="my-1">{{ 'menu.header.admin' | translate }}</h4>
</div>
</div>
</div>
</div>
<!-- ITEMS -->
<div class="sidebar-full-width-container" id="sidebar-top-level-items-container">
<div class="sidebar-full-width-container" id="sidebar-top-level-items" role="menubar"
[attr.aria-label]="'menu.header.admin.description' |translate">
<ng-container *ngFor="let section of (sections | async)">
<ng-container
*ngComponentOutlet="(sectionMap$ | async).get(section.id).component; injector: (sectionMap$ | async).get(section.id).injector;"></ng-container>
</ng-container>
</div>
</div>
<!-- TOGGLER -->
<div class="sidebar-full-width-container" id="sidebar-collapse-toggle-container">
<a class="sidebar-section-wrapper sidebar-full-width-container"
id="sidebar-collapse-toggle"
href="javascript:void(0);"
(click)="toggle($event)"
(keyup.space)="toggle($event)"
>
<div class="sidebar-fixed-element-wrapper">
<i *ngIf="(menuCollapsed | async)" class="fas fa-fw fa-angle-double-right"
[title]="'menu.section.icon.pin' | translate"></i>
<i *ngIf="!(menuCollapsed | async)" class="fas fa-fw fa-angle-double-left"
[title]="'menu.section.icon.unpin' | translate"></i>
</div>
<div class="sidebar-collapsible-element-outer-wrapper">
<div class="sidebar-collapsible-element-inner-wrapper sidebar-item">
{{ ((menuCollapsed | async) ? 'menu.section.pin' : 'menu.section.unpin' ) | translate }}
</div>
</div>
</a>
</div>
</nav>

View File

@@ -1,123 +1,140 @@
:host {
--ds-icon-z-index: 10;
left: 0;
top: 0;
height: 100vh;
flex: 1 1 auto;
nav {
background-color: var(--ds-admin-sidebar-bg);
height: 100%;
flex-direction: column;
> div {
width: 100%;
&.sidebar-top-level-items {
flex: 1;
overflow: auto;
@include dark-scrollbar;
}
/* SIDEBAR SIZE AND POSITION */
/* Sidebar hierarchy:
§ nav
§ .sidebar-full-width-container (any OPTIONAL full width element with no horizontal margin or padding - it can be nested)
§ .sidebar-section-wrapper
§ .sidebar-fixed-element-wrapper
§ .sidebar-collapsible-element-outer-wrapper
§ .sidebar-collapsible-element-inner-wrapper
§ .sidebar-item
*/
// Sidebar position
position: fixed;
left: 0;
top: 0;
z-index: var(--ds-sidebar-z-index);
// Sidebar size and content position
nav#admin-sidebar {
max-width: var(--ds-admin-sidebar-fixed-element-width); // Sidebar collapsed width
display: flex;
flex-direction: column;
flex-wrap: nowrap;
div#sidebar-top-level-items-container {
flex: 1 1 auto; // Fill available vertical space
overflow-x: hidden;
overflow-y: auto;
@include dark-scrollbar;
}
img#admin-sidebar-logo {
height: var(--ds-admin-sidebar-logo-height);
}
::ng-deep {
// This class must be applied to any nested wrapper containing a sidebar section
.sidebar-full-width-container {
width: 100%;
padding-left: 0;
padding-right: 0;
margin-left: 0;
margin-right: 0;
}
// This class must be applied to the innermost block element containing a section or subsection link
// (it can be applied together with `sidebar-collapsible-element-inner-wrapper`)
.sidebar-item {
padding-top: var(--ds-admin-sidebar-item-padding);
padding-bottom: var(--ds-admin-sidebar-item-padding);
}
// These classes handle the collapsing behavior
.sidebar-section-wrapper {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: stretch;
// These elements have fixed width and determine the width of the collapsed sidebar
& > .sidebar-fixed-element-wrapper {
min-width: var(--ds-admin-sidebar-fixed-element-width);
flex: 1 1 auto; // Fill available space
// Align the icons
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
&.inactive ::ng-deep .sidebar-collapsible {
margin-left: calc(-1 * var(--ds-sidebar-items-width));
}
.navbar-nav {
.admin-menu-header {
background-color: var(--ds-admin-sidebar-header-bg);
.sidebar-section {
background-color: inherit;
}
.logo-wrapper {
img {
height: 20px;
}
}
.section-header-text {
line-height: 1.5;
}
.navbar-brand {
margin-right: 0;
}
}
}
::ng-deep {
.navbar-nav {
.sidebar-section {
display: flex;
align-content: stretch;
background-color: var(--ds-admin-sidebar-bg);
overflow-x: visible;
.nav-item {
padding-top: var(--bs-spacer);
padding-bottom: var(--bs-spacer);
background-color: inherit;
&:focus-visible {
// since links fill the whole sidebar, we _inset_ the outline
outline-offset: -4px;
// replace padding with margins so it doesn't extend over the :focus-visible outline
// → can't remove the padding altogether; the icon needs to fill out
// the collapsed width of the sidebar for the slide animation to look decent.
.shortcut-icon {
padding-left: 0;
padding-right: 0;
margin-left: var(--ds-icon-padding);
margin-right: var(--ds-icon-padding);
}
.logo-wrapper {
margin-right: var(--bs-navbar-padding-x) !important;
}
.navbar-brand {
padding-top: 0;
padding-bottom: 0;
margin-top: var(--bs-navbar-brand-padding-y);
margin-bottom: var(--bs-navbar-brand-padding-y);
}
}
}
.shortcut-icon {
background-color: inherit;
padding-left: var(--ds-icon-padding);
padding-right: var(--ds-icon-padding);
z-index: var(--ds-icon-z-index);
align-self: baseline;
}
.sidebar-collapsible {
padding-left: 0;
padding-right: var(--bs-spacer);
width: var(--ds-sidebar-items-width);
position: relative;
.toggle {
width: 100%;
}
ul {
padding-top: var(--bs-spacer);
li a {
padding-left: var(--bs-spacer);
}
}
}
&.active > .sidebar-collapsible > .nav-link {
color: var(--bs-navbar-dark-active-color);
}
}
}
& > .sidebar-collapsible-element-outer-wrapper {
display: flex;
flex-wrap: wrap;
justify-content: flex-end; // make inner wrapper slide on the left when collapsing
max-width: calc(100% - var(--ds-admin-sidebar-fixed-element-width)); // fill available space
padding-left: var(--ds-dark-scrollbar-width); // leave room for scrollbar
overflow-x: hidden; // hide inner wrapper when sidebar is collapsed
// These elements have fixed width and slide on the left when the sidebar is collapsed
// Their content should fill all available space
& > .sidebar-collapsible-element-inner-wrapper {
min-width: calc(var(--ds-admin-sidebar-collapsible-element-width) - var(--ds-dark-scrollbar-width));
height: 100%;
padding-right: var(--ds-admin-sidebar-item-padding);
}
}
}
}
// Set here any style that depends on the sidebar status
&.collapsed, &.transitioning, &.expanded { }
}
/* SIDEBAR STYLE */
nav#admin-sidebar {
background-color: var(--ds-admin-sidebar-bg);
::ng-deep {
color: white;
// Set here the style of the *-menu-item nested components
.ds-menu-item {
}
a {
color: var(--ds-admin-sidebar-link-color);
text-decoration: none;
&:hover, &:focus {
color: var(--ds-admin-sidebar-link-hover-color);
}
}
}
div#sidebar-header-container {
background-color: var(--ds-admin-sidebar-header-bg);
.sidebar-fixed-element-wrapper {
background-color: var(--ds-admin-sidebar-header-bg);
}
}
div#sidebar-collapse-toggle-container {
.sidebar-collapsible-element-inner-wrapper {
}
}
}
}

View File

@@ -143,7 +143,7 @@ describe('AdminSidebarComponent', () => {
describe('when the collapse link is clicked', () => {
beforeEach(() => {
spyOn(menuService, 'toggleMenu');
const sidebarToggler = fixture.debugElement.query(By.css('#sidebar-collapse-toggle > a'));
const sidebarToggler = fixture.debugElement.query(By.css('#sidebar-collapse-toggle-container > a'));
sidebarToggler.triggerEventHandler('click', {
preventDefault: () => {/**/
}

View File

@@ -1,4 +1,4 @@
import { Component, HostListener, Injector, OnInit } from '@angular/core';
import { Component, HostListener, Injector, Input, OnInit } from '@angular/core';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { debounceTime, distinctUntilChanged, first, map, withLatestFrom } from 'rxjs/operators';
import { AuthService } from '../../core/auth/auth.service';
@@ -28,9 +28,14 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
menuID = MenuID.ADMIN;
/**
* Observable that emits the width of the collapsible menu sections
* Observable that emits the width of the sidebar when expanded
*/
sidebarWidth: Observable<string>;
@Input() expandedSidebarWidth$: Observable<string>;
/**
* Observable that emits the width of the sidebar when collapsed
*/
@Input() collapsedSidebarWidth$: Observable<string>;
/**
* Is true when the sidebar is open, is false when the sidebar is animating or closed
@@ -44,6 +49,12 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
*/
sidebarClosed = !this.sidebarOpen; // Closed in UI, animation finished
/**
* Is true when the sidebar is opening or closing
* @type {boolean}
*/
sidebarTransitioning = !this.sidebarOpen; // Animation in progress
/**
* Emits true when either the menu OR the menu's preview is expanded, else emits false
*/
@@ -69,7 +80,6 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
*/
ngOnInit(): void {
super.ngOnInit();
this.sidebarWidth = this.variableService.getVariable('--ds-sidebar-items-width');
this.authService.isAuthenticated()
.subscribe((loggedIn: boolean) => {
if (loggedIn) {
@@ -134,6 +144,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
* @param event The animation event
*/
startSlide(event: any): void {
this.sidebarTransitioning = true;
if (event.toState === 'expanded') {
this.sidebarClosed = false;
} else if (event.toState === 'collapsed') {
@@ -146,6 +157,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
* @param event The animation event
*/
finishSlide(event: any): void {
this.sidebarTransitioning = false;
if (event.fromState === 'expanded') {
this.sidebarClosed = true;
} else if (event.fromState === 'collapsed') {

View File

@@ -1,37 +1,46 @@
<div class="sidebar-section" [ngClass]="{'expanded': (expanded | async)}"
<div [ngClass]="{'expanded': (isExpanded$ | async)}"
[@bgColor]="{
value: ((expanded | async) ? 'endBackground' : 'startBackground'),
params: {endColor: (sidebarActiveBg | async)}}">
<div class="nav-item nav-link d-flex flex-row flex-nowrap"
role="button" tabindex="0"
[attr.aria-labelledby]="'sidebarName-' + section.id"
[attr.aria-expanded]="expanded | async"
[title]="('menu.section.icon.' + section.id) | translate"
[class.disabled]="section.model?.disabled"
value: ((isExpanded$ | async) ? 'endBackground' : 'startBackground'),
params: {endColor: (sidebarActiveBg$ | async)}
}">
<a class="sidebar-section-wrapper"
role="menuitem" tabindex="0"
aria-haspopup="menu"
[attr.aria-controls]="adminMenuSectionId(section.id)"
[attr.aria-expanded]="isExpanded$ | async"
[attr.aria-label]="('menu.section.toggle.' + section.id) | translate"
[class.disabled]="section.model?.disabled"
(click)="toggleSection($event)"
(keyup.space)="toggleSection($event)"
(keyup.enter)="toggleSection($event)"
href="javascript:void(0);"
>
<div class="shortcut-icon h-100">
<div class="sidebar-fixed-element-wrapper" data-test="sidebar-section-icon" aria-hidden="true">
<i class="fas fa-{{section.icon}} fa-fw"></i>
</div>
<div class="sidebar-collapsible">
<div class="toggle">
<span id="sidebarName-{{section.id}}" class="section-header-text">
<div class="sidebar-collapsible-element-outer-wrapper">
<div class="sidebar-collapsible-element-inner-wrapper sidebar-item toggler-wrapper">
<span [id]="adminMenuSectionTitleId(section.id)">
<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 class="fas fa-chevron-right fa-xs" aria-hidden="true"
[@rotate]="(isExpanded$ | async) ? 'expanded' : 'collapsed'"
></i>
</div>
<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>
</a>
<div class="sidebar-section-wrapper subsection" @slide *ngIf="(isExpanded$ | async)">
<div class="sidebar-fixed-element-wrapper"></div>
<div class="sidebar-collapsible-element-outer-wrapper">
<div class="sidebar-collapsible-element-inner-wrapper">
<div class="sidebar-sub-level-item-list" role="menu" [id]="adminMenuSectionId(section.id)" [attr.aria-label]="('menu.section.' + section.id) | translate">
<div class="sidebar-item" *ngFor="let subSection of (subSections$ | async)">
{{ (sectionMap$ | async).get(subSection.id).component | json}}
<ng-container
*ngComponentOutlet="(sectionMap$ | async).get(subSection.id).component; injector: (sectionMap$ | async).get(subSection.id).injector;"></ng-container>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,23 +1,24 @@
:host ::ng-deep {
.fa-chevron-right {
padding-left: calc(var(--bs-spacer) / 2);
font-size: 0.5rem;
line-height: 3;
:host {
.sidebar-sub-level-item-list {
list-style: none;
margin: 0;
padding: 0;
::ng-deep .ds-menu-item {
margin-left: var(--ds-admin-sidebar-section-indent);
}
}
.sidebar-sub-level-items {
list-style: disc;
color: var(--bs-navbar-dark-color);
overflow: hidden;
margin-bottom: calc(-1 * var(--bs-spacer)); // the bottom-most nav-item is padded, no need for double spacing
}
.toggler-wrapper {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: space-between;
}
.sidebar-collapsible {
display: flex;
flex-direction: column;
}
li.sidebar-section.expanded {
background-color: var(--ds-admin-sidebar-active-bg) !important;
.subsection {
.sidebar-collapsible-element-inner-wrapper {
overflow-y: hidden;
}
}
}

View File

@@ -49,14 +49,14 @@ describe('ExpandableAdminSidebarSectionComponent', () => {
});
it('should set the right icon', () => {
const icon = fixture.debugElement.query(By.css('.shortcut-icon > i.fas'));
const icon = fixture.debugElement.query(By.css('[data-test="sidebar-section-icon"] > i.fas'));
expect(icon.nativeElement.getAttribute('class')).toContain('fa-' + iconString);
});
describe('when the header text is clicked', () => {
beforeEach(() => {
spyOn(menuService, 'toggleActiveSection');
const sidebarToggler = fixture.debugElement.query(By.css('.sidebar-section > div.nav-item'));
const sidebarToggler = fixture.debugElement.query(By.css('a.sidebar-section-wrapper'));
sidebarToggler.triggerEventHandler('click', {
preventDefault: () => {/**/
}

View File

@@ -15,8 +15,7 @@ import { Router } from '@angular/router';
* Represents a expandable section in the sidebar
*/
@Component({
/* eslint-disable @angular-eslint/component-selector */
selector: 'li[ds-expandable-admin-sidebar-section]',
selector: 'ds-expandable-admin-sidebar-section',
templateUrl: './expandable-admin-sidebar-section.component.html',
styleUrls: ['./expandable-admin-sidebar-section.component.scss'],
animations: [rotate, slide, bgColor]
@@ -32,23 +31,23 @@ export class ExpandableAdminSidebarSectionComponent extends AdminSidebarSectionC
/**
* The background color of the section when it's active
*/
sidebarActiveBg;
sidebarActiveBg$: Observable<string>;
/**
* Emits true when the sidebar is currently collapsed, true when it's expanded
*/
sidebarCollapsed: Observable<boolean>;
isSidebarCollapsed$: Observable<boolean>;
/**
* Emits true when the sidebar's preview is currently collapsed, true when it's expanded
*/
sidebarPreviewCollapsed: Observable<boolean>;
isSidebarPreviewCollapsed$: Observable<boolean>;
/**
* Emits true when the menu section is expanded, else emits false
* This is true when the section is active AND either the sidebar or it's preview is open
*/
expanded: Observable<boolean>;
isExpanded$: Observable<boolean>;
constructor(
@Inject('sectionDataProvider') menuSection,
@@ -65,12 +64,20 @@ export class ExpandableAdminSidebarSectionComponent extends AdminSidebarSectionC
*/
ngOnInit(): void {
super.ngOnInit();
this.sidebarActiveBg = this.variableService.getVariable('--ds-admin-sidebar-active-bg');
this.sidebarCollapsed = this.menuService.isMenuCollapsed(this.menuID);
this.sidebarPreviewCollapsed = this.menuService.isMenuPreviewCollapsed(this.menuID);
this.expanded = combineLatestObservable(this.active, this.sidebarCollapsed, this.sidebarPreviewCollapsed)
.pipe(
map(([active, sidebarCollapsed, sidebarPreviewCollapsed]) => (active && (!sidebarCollapsed || !sidebarPreviewCollapsed)))
);
this.sidebarActiveBg$ = this.variableService.getVariable('--ds-admin-sidebar-active-bg');
this.isSidebarCollapsed$ = this.menuService.isMenuCollapsed(this.menuID);
this.isSidebarPreviewCollapsed$ = this.menuService.isMenuPreviewCollapsed(this.menuID);
this.isExpanded$ = combineLatestObservable([this.active, this.isSidebarCollapsed$, this.isSidebarPreviewCollapsed$]).pipe(
map(([active, sidebarCollapsed, sidebarPreviewCollapsed]) => (active && (!sidebarCollapsed || !sidebarPreviewCollapsed)))
);
}
toggleSection($event: Event) {
this.menuService.expandMenuPreview(this.menuID); // fixes accessibility issue
super.toggleSection($event);
}
adminMenuSubsectionId(sectionId: string, subsectionId: string) {
return `admin-menu-section-${sectionId}-${subsectionId}`;
}
}

View File

@@ -1,6 +1,7 @@
import { Component } from '@angular/core';
import { Component, Input } from '@angular/core';
import { ThemedComponent } from '../../shared/theme-support/themed.component';
import { AdminSidebarComponent } from './admin-sidebar.component';
import { Observable } from 'rxjs';
/**
* Themed wrapper for AdminSidebarComponent
@@ -11,6 +12,19 @@ import { AdminSidebarComponent } from './admin-sidebar.component';
templateUrl: '../../shared/theme-support/themed.component.html',
})
export class ThemedAdminSidebarComponent extends ThemedComponent<AdminSidebarComponent> {
/**
* Observable that emits the width of the sidebar when expanded
*/
@Input() expandedSidebarWidth$: Observable<string>;
/**
* Observable that emits the width of the sidebar when collapsed
*/
@Input() collapsedSidebarWidth$: Observable<string>;
protected inAndOutputNames: (keyof AdminSidebarComponent & keyof this)[] = ['collapsedSidebarWidth$', 'expandedSidebarWidth$'];
protected getComponentName(): string {
return 'AdminSidebarComponent';
}

View File

@@ -9,6 +9,7 @@ import { CollectionElementLinkType } from '../../../../../shared/object-collecti
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { RouterTestingModule } from '@angular/router/testing';
import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model';
import { XSRFService } from '../../../../../core/xsrf/xsrf.service';
import {
WorkflowItemSearchResultAdminWorkflowListElementComponent
} from './workflow-item-search-result-admin-workflow-list-element.component';
@@ -58,7 +59,8 @@ describe('WorkflowItemSearchResultAdminWorkflowListElementComponent', () => {
{ provide: TruncatableService, useValue: mockTruncatableService },
{ provide: LinkService, useValue: linkService },
{ provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environment }
{ provide: APP_CONFIG, useValue: environment },
{ provide: XSRFService, useValue: {} },
],
schemas: [NO_ERRORS_SCHEMA]
})

View File

@@ -1,4 +1,4 @@
import { distinctUntilChanged, take, withLatestFrom } from 'rxjs/operators';
import { distinctUntilChanged, take, withLatestFrom, delay } from 'rxjs/operators';
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
import {
AfterViewInit,
@@ -114,7 +114,10 @@ export class AppComponent implements OnInit, AfterViewInit {
}
ngAfterViewInit() {
this.router.events.subscribe((event) => {
this.router.events.pipe(
// delay(0) to prevent "Expression has changed after it was checked" errors
delay(0)
).subscribe((event) => {
if (event instanceof NavigationStart) {
distinctNext(this.isRouteLoading$, true);
} else if (

View File

@@ -1,5 +1,5 @@
<div class="container">
<h3>{{'bitstream.download.page' | translate:{ bitstream: dsoNameService.getName((bitstream$ | async)) } }}</h3>
<h1 class="h2">{{'bitstream.download.page' | translate:{ bitstream: dsoNameService.getName((bitstream$ | async)) } }}</h1>
<div class="pt-3">
<button (click)="back()" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> {{'bitstream.download.page.back' | translate}}

View File

@@ -8,7 +8,7 @@ import { ResourcePolicyCreateComponent } from '../shared/resource-policies/creat
import { ResourcePolicyResolver } from '../shared/resource-policies/resolvers/resource-policy.resolver';
import { ResourcePolicyEditComponent } from '../shared/resource-policies/edit/resource-policy-edit.component';
import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bitstream-authorizations.component';
import { LegacyBitstreamUrlResolver } from './legacy-bitstream-url.resolver';
import { legacyBitstreamURLRedirectGuard } from './legacy-bitstream-url-redirect.guard';
import { BitstreamBreadcrumbResolver } from '../core/breadcrumbs/bitstream-breadcrumb.resolver';
import { BitstreamBreadcrumbsService } from '../core/breadcrumbs/bitstream-breadcrumbs.service';
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
@@ -27,17 +27,13 @@ const EDIT_BITSTREAM_AUTHORIZATIONS_PATH = ':id/authorizations';
// Resolve XMLUI bitstream download URLs
path: 'handle/:prefix/:suffix/:filename',
component: BitstreamDownloadPageComponent,
resolve: {
bitstream: LegacyBitstreamUrlResolver
},
canActivate: [legacyBitstreamURLRedirectGuard],
},
{
// Resolve JSPUI bitstream download URLs
path: ':prefix/:suffix/:sequence_id/:filename',
component: BitstreamDownloadPageComponent,
resolve: {
bitstream: LegacyBitstreamUrlResolver
},
canActivate: [legacyBitstreamURLRedirectGuard],
},
{
// Resolve angular bitstream download URLs

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