diff --git a/.eslintrc.json b/.eslintrc.json index af1b97849b6..cb5775ef1ff 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,7 +8,10 @@ "eslint-plugin-deprecation", "unused-imports", "eslint-plugin-lodash", - "eslint-plugin-jsonc" + "eslint-plugin-jsonc", + "eslint-plugin-rxjs", + "eslint-plugin-simple-import-sort", + "eslint-plugin-import-newlines" ], "overrides": [ { @@ -27,17 +30,29 @@ "plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended-requiring-type-checking", "plugin:@angular-eslint/recommended", - "plugin:@angular-eslint/template/process-inline-templates" + "plugin:@angular-eslint/template/process-inline-templates", + "plugin:rxjs/recommended" ], "rules": { + "indent": [ + "error", + 2, + { + "SwitchCase": 1 + } + ], "max-classes-per-file": [ "error", 1 ], "comma-dangle": [ - "off", + "error", "always-multiline" ], + "object-curly-spacing": [ + "error", + "always" + ], "eol-last": [ "error", "always" @@ -104,15 +119,13 @@ "allowTernary": true } ], - "prefer-const": "off", // todo: re-enable & fix errors (more strict than it used to be in TSLint) + "prefer-const": "error", + "no-case-declarations": "error", + "no-extra-boolean-cast": "error", "prefer-spread": "off", "no-underscore-dangle": "off", - - // todo: disabled rules from eslint:recommended, consider re-enabling & fixing "no-prototype-builtins": "off", "no-useless-escape": "off", - "no-case-declarations": "off", - "no-extra-boolean-cast": "off", "@angular-eslint/directive-selector": [ "error", @@ -183,7 +196,7 @@ ], "@typescript-eslint/type-annotation-spacing": "error", "@typescript-eslint/unified-signatures": "error", - "@typescript-eslint/ban-types": "warn", // todo: deal with {} type issues & re-enable + "@typescript-eslint/ban-types": "error", "@typescript-eslint/no-floating-promises": "warn", "@typescript-eslint/no-misused-promises": "warn", "@typescript-eslint/restrict-plus-operands": "warn", @@ -203,14 +216,45 @@ "deprecation/deprecation": "warn", + "simple-import-sort/imports": "error", + "simple-import-sort/exports": "error", "import/order": "off", + "import/first": "error", + "import/newline-after-import": "error", + "import/no-duplicates": "error", "import/no-deprecated": "warn", "import/no-namespace": "error", + "import-newlines/enforce": [ + "error", + { + "items": 1, + "semi": true, + "forceSingleLine": true + } + ], + "unused-imports/no-unused-imports": "error", "lodash/import-scope": [ "error", "method" - ] + ], + + "rxjs/no-nested-subscribe": "off" // todo: go over _all_ cases + } + }, + { + "files": [ + "*.spec.ts" + ], + "parserOptions": { + "project": [ + "./tsconfig.json", + "./cypress/tsconfig.json" + ], + "createDefaultProgram": true + }, + "rules": { + "prefer-const": "off" } }, { @@ -219,12 +263,7 @@ ], "extends": [ "plugin:@angular-eslint/template/recommended" - ], - "rules": { - // todo: re-enable & fix errors - "@angular-eslint/template/no-negated-async": "off", - "@angular-eslint/template/eqeqeq": "off" - } + ] }, { "files": [ diff --git a/config/config.example.yml b/config/config.example.yml index 0933619e013..36d6a009d35 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -131,12 +131,16 @@ submission: # NOTE: after how many time (milliseconds) submission is saved automatically # eg. timer: 5 * (1000 * 60); // 5 minutes timer: 0 + # Always show the duplicate detection section if enabled, even if there are no potential duplicates detected + # (a message will be displayed to indicate no matches were found) + duplicateDetection: + alwaysShowSection: false icons: metadata: # NOTE: example of configuration # # NOTE: metadata name # - name: dc.author - # # NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used + # # NOTE: fontawesome (v6.x) icon classes and bootstrap utility classes can be used # style: fas fa-user - name: dc.author style: fas fa-user @@ -147,18 +151,40 @@ submission: confidence: # NOTE: example of configuration # # NOTE: confidence value - # - name: dc.author - # # NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used - # style: fa-user + # - value: 600 + # # NOTE: fontawesome (v6.x) icon classes and bootstrap utility classes can be used + # style: text-success + # icon: fa-circle-check + # # NOTE: the class configured in property style is used by default, the icon property could be used in component + # configured to use a 'icon mode' display (mainly in edit-item page) - value: 600 style: text-success + icon: fa-circle-check - value: 500 style: text-info + icon: fa-gear - value: 400 style: text-warning + icon: fa-circle-question + - value: 300 + style: text-muted + icon: fa-thumbs-down + - value: 200 + style: text-muted + icon: fa-circle-exclamation + - value: 100 + style: text-muted + icon: fa-circle-stop + - value: 0 + style: text-muted + icon: fa-ban + - value: -1 + style: text-muted + icon: fa-circle-xmark # default configuration - value: default style: text-muted + icon: fa-circle-xmark # Default Language in which the UI will be rendered if the user's browser language is not an active language defaultLanguage: en @@ -272,6 +298,8 @@ homePage: # No. of communities to list per page on the home page # This will always round to the nearest number from the list of page sizes. e.g. if you set it to 7 it'll use 10 pageSize: 5 + # Enable or disable the Discover filters on the homepage + showDiscoverFilters: false # Item Config item: @@ -400,3 +428,70 @@ comcolSelectionSort: # suggestion: # - collectionId: 8f7df5ca-f9c2-47a4-81ec-8a6393d6e5af # source: "openaire" + + +# Search settings +search: + # Settings to enable/disable or configure advanced search filters. + advancedFilters: + enabled: false + # List of filters to enable in "Advanced Search" dropdown + filter: [ 'title', 'author', 'subject', 'entityType' ] + + +# Notify metrics +# Configuration for Notify Admin Dashboard for metrics visualization +notifyMetrics: + # Configuration for received messages +- title: 'admin-notify-dashboard.received-ldn' + boxes: + - color: '#B8DAFF' + title: 'admin-notify-dashboard.NOTIFY.incoming.accepted' + config: 'NOTIFY.incoming.accepted' + description: 'admin-notify-dashboard.NOTIFY.incoming.accepted.description' + - color: '#D4EDDA' + title: 'admin-notify-dashboard.NOTIFY.incoming.processed' + config: 'NOTIFY.incoming.processed' + description: 'admin-notify-dashboard.NOTIFY.incoming.processed.description' + - color: '#FDBBC7' + title: 'admin-notify-dashboard.NOTIFY.incoming.failure' + config: 'NOTIFY.incoming.failure' + description: 'admin-notify-dashboard.NOTIFY.incoming.failure.description' + - color: '#FDBBC7' + title: 'admin-notify-dashboard.NOTIFY.incoming.untrusted' + config: 'NOTIFY.incoming.untrusted' + description: 'admin-notify-dashboard.NOTIFY.incoming.untrusted.description' + - color: '#43515F' + title: 'admin-notify-dashboard.NOTIFY.incoming.involvedItems' + textColor: '#fff' + config: 'NOTIFY.incoming.involvedItems' + description: 'admin-notify-dashboard.NOTIFY.incoming.involvedItems.description' +# Configuration for outgoing messages +- title: 'admin-notify-dashboard.generated-ldn' + boxes: + - color: '#B8DAFF' + title: 'admin-notify-dashboard.NOTIFY.outgoing.queued' + config: 'NOTIFY.outgoing.queued' + description: 'admin-notify-dashboard.NOTIFY.outgoing.queued.description' + - color: '#FDEEBB' + title: 'admin-notify-dashboard.NOTIFY.outgoing.queued_for_retry' + config: 'NOTIFY.outgoing.queued_for_retry' + description: 'admin-notify-dashboard.NOTIFY.outgoing.queued_for_retry.description' + - color: '#FDBBC7' + title: 'admin-notify-dashboard.NOTIFY.outgoing.failure' + config: 'NOTIFY.outgoing.failure' + description: 'admin-notify-dashboard.NOTIFY.outgoing.failure.description' + - color: '#43515F' + title: 'admin-notify-dashboard.NOTIFY.outgoing.involvedItems' + textColor: '#fff' + config: 'NOTIFY.outgoing.involvedItems' + description: 'admin-notify-dashboard.NOTIFY.outgoing.involvedItems.description' + - color: '#D4EDDA' + title: 'admin-notify-dashboard.NOTIFY.outgoing.delivered' + config: 'NOTIFY.outgoing.delivered' + description: 'admin-notify-dashboard.NOTIFY.outgoing.delivered.description' + + + + + diff --git a/cypress/e2e/collection-edit.cy.ts b/cypress/e2e/collection-edit.cy.ts index 3e7ecf61410..63d873db3ec 100644 --- a/cypress/e2e/collection-edit.cy.ts +++ b/cypress/e2e/collection-edit.cy.ts @@ -45,7 +45,7 @@ describe('Edit Collection > Content Source tab', () => { cy.get('#externalSourceCheck').check(); // Wait for the source controls to appear - cy.get('ds-collection-source-controls').should('be.visible'); + // cy.get('ds-collection-source-controls').should('be.visible'); // Analyze entire page for accessibility issues testA11y('ds-collection-source'); diff --git a/cypress/e2e/collection-statistics.cy.ts b/cypress/e2e/collection-statistics.cy.ts index 43bf67ce51f..a08f8cb1987 100644 --- a/cypress/e2e/collection-statistics.cy.ts +++ b/cypress/e2e/collection-statistics.cy.ts @@ -6,7 +6,7 @@ describe('Collection Statistics Page', () => { it('should load if you click on "Statistics" from a Collection page', () => { cy.visit('/collections/'.concat(Cypress.env('DSPACE_TEST_COLLECTION'))); - 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', COLLECTIONSTATISTICSPAGE); }); diff --git a/cypress/e2e/community-statistics.cy.ts b/cypress/e2e/community-statistics.cy.ts index ca306eff5c2..6cafed0350e 100644 --- a/cypress/e2e/community-statistics.cy.ts +++ b/cypress/e2e/community-statistics.cy.ts @@ -6,7 +6,7 @@ describe('Community Statistics Page', () => { it('should load if you click on "Statistics" from a Community page', () => { cy.visit('/communities/'.concat(Cypress.env('DSPACE_TEST_COMMUNITY'))); - 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', COMMUNITYSTATISTICSPAGE); }); diff --git a/cypress/e2e/homepage-statistics.cy.ts b/cypress/e2e/homepage-statistics.cy.ts index ff7dbeb852d..ece38686b93 100644 --- a/cypress/e2e/homepage-statistics.cy.ts +++ b/cypress/e2e/homepage-statistics.cy.ts @@ -5,7 +5,7 @@ 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'); }); diff --git a/cypress/e2e/item-statistics.cy.ts b/cypress/e2e/item-statistics.cy.ts index b856744cba7..6caeacae8e1 100644 --- a/cypress/e2e/item-statistics.cy.ts +++ b/cypress/e2e/item-statistics.cy.ts @@ -6,7 +6,7 @@ describe('Item Statistics Page', () => { it('should load if you click on "Statistics" from an Item/Entity page', () => { cy.visit('/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION'))); - 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', ITEMSTATISTICSPAGE); }); diff --git a/cypress/e2e/login-modal.cy.ts b/cypress/e2e/login-modal.cy.ts index c56b98fd269..673041e9f39 100644 --- a/cypress/e2e/login-modal.cy.ts +++ b/cypress/e2e/login-modal.cy.ts @@ -3,31 +3,31 @@ 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'); @@ -102,10 +102,10 @@ 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'); @@ -119,10 +119,10 @@ 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'); diff --git a/cypress/e2e/search-navbar.cy.ts b/cypress/e2e/search-navbar.cy.ts index 9dd93c7a2dd..28a72bcc786 100644 --- a/cypress/e2e/search-navbar.cy.ts +++ b/cypress/e2e/search-navbar.cy.ts @@ -1,15 +1,15 @@ 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(); } }; diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index f6c68650528..b2255a7da65 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -21,7 +21,6 @@ import './commands'; 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. diff --git a/karma.conf.js b/karma.conf.js index 8418312b1ab..f96558bfaff 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -15,7 +15,10 @@ module.exports = function (config) { ], client: { clearContext: false, // leave Jasmine Spec Runner output visible in browser - captureConsole: false + captureConsole: false, + jasmine: { + failSpecWithNoExpectations: true + } }, coverageIstanbulReporter: { dir: require('path').join(__dirname, './coverage/dspace-angular'), diff --git a/package.json b/package.json index e5347742c8c..c0a3843605e 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "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", @@ -142,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", @@ -170,9 +170,12 @@ "eslint": "^8.39.0", "eslint-plugin-deprecation": "^1.4.1", "eslint-plugin-import": "^2.27.5", - "eslint-plugin-jsdoc": "^39.6.4", + "eslint-plugin-import-newlines": "^1.3.1", + "eslint-plugin-jsdoc": "^45.0.0", "eslint-plugin-jsonc": "^2.6.0", "eslint-plugin-lodash": "^7.4.0", + "eslint-plugin-rxjs": "^5.0.3", + "eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-unused-imports": "^2.0.0", "express-static-gzip": "^2.1.7", "jasmine-core": "^3.8.0", diff --git a/src/app/access-control/access-control-routing-paths.ts b/src/app/access-control/access-control-routing-paths.ts index 31f39f1c47d..06ae0321945 100644 --- a/src/app/access-control/access-control-routing-paths.ts +++ b/src/app/access-control/access-control-routing-paths.ts @@ -1,5 +1,5 @@ -import { URLCombiner } from '../core/url-combiner/url-combiner'; import { getAccessControlModuleRoute } from '../app-routing-paths'; +import { URLCombiner } from '../core/url-combiner/url-combiner'; export const EPERSON_PATH = 'epeople'; diff --git a/src/app/access-control/access-control-routing.module.ts b/src/app/access-control/access-control-routing.module.ts index 97d049ad836..e85961fd138 100644 --- a/src/app/access-control/access-control-routing.module.ts +++ b/src/app/access-control/access-control-routing.module.ts @@ -1,20 +1,20 @@ import { NgModule } from '@angular/core'; 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 { 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 { GroupAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/group-administrator.guard'; +import { SiteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; import { - GroupAdministratorGuard -} from '../core/data/feature-authorization/feature-authorization-guard/group-administrator.guard'; -import { - SiteAdministratorGuard -} from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; + EPERSON_PATH, + GROUP_PATH, +} from './access-control-routing-paths'; import { BulkAccessComponent } from './bulk-access/bulk-access.component'; +import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.component'; import { EPersonFormComponent } from './epeople-registry/eperson-form/eperson-form.component'; import { EPersonResolver } from './epeople-registry/eperson-resolver.service'; +import { GroupFormComponent } from './group-registry/group-form/group-form.component'; +import { GroupPageGuard } from './group-registry/group-page.guard'; +import { GroupsRegistryComponent } from './group-registry/groups-registry.component'; @NgModule({ imports: [ @@ -23,10 +23,10 @@ import { EPersonResolver } from './epeople-registry/eperson-resolver.service'; path: EPERSON_PATH, component: EPeopleRegistryComponent, resolve: { - breadcrumb: I18nBreadcrumbResolver + breadcrumb: I18nBreadcrumbResolver, }, data: { title: 'admin.access-control.epeople.title', breadcrumbKey: 'admin.access-control.epeople' }, - canActivate: [SiteAdministratorGuard] + canActivate: [SiteAdministratorGuard], }, { path: `${EPERSON_PATH}/create`, @@ -51,40 +51,40 @@ import { EPersonResolver } from './epeople-registry/eperson-resolver.service'; path: GROUP_PATH, component: GroupsRegistryComponent, resolve: { - breadcrumb: I18nBreadcrumbResolver + breadcrumb: I18nBreadcrumbResolver, }, data: { title: 'admin.access-control.groups.title', breadcrumbKey: 'admin.access-control.groups' }, - canActivate: [GroupAdministratorGuard] + canActivate: [GroupAdministratorGuard], }, { path: `${GROUP_PATH}/create`, component: GroupFormComponent, resolve: { - breadcrumb: I18nBreadcrumbResolver + breadcrumb: I18nBreadcrumbResolver, }, data: { title: 'admin.access-control.groups.title.addGroup', breadcrumbKey: 'admin.access-control.groups.addGroup' }, - canActivate: [GroupAdministratorGuard] + canActivate: [GroupAdministratorGuard], }, { path: `${GROUP_PATH}/:groupId/edit`, component: GroupFormComponent, resolve: { - breadcrumb: I18nBreadcrumbResolver + breadcrumb: I18nBreadcrumbResolver, }, data: { title: 'admin.access-control.groups.title.singleGroup', breadcrumbKey: 'admin.access-control.groups.singleGroup' }, - canActivate: [GroupPageGuard] + canActivate: [GroupPageGuard], }, { path: 'bulk-access', component: BulkAccessComponent, resolve: { - breadcrumb: I18nBreadcrumbResolver + breadcrumb: I18nBreadcrumbResolver, }, data: { title: 'admin.access-control.bulk-access.title', breadcrumbKey: 'admin.access-control.bulk-access' }, - canActivate: [SiteAdministratorGuard] + canActivate: [SiteAdministratorGuard], }, - ]) - ] + ]), + ], }) /** * Routing module for the AccessControl section of the admin sidebar diff --git a/src/app/access-control/access-control.module.ts b/src/app/access-control/access-control.module.ts index 3dc4b6cedc7..87737987e08 100644 --- a/src/app/access-control/access-control.module.ts +++ b/src/app/access-control/access-control.module.ts @@ -1,23 +1,27 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +import { AbstractControl } from '@angular/forms'; import { RouterModule } from '@angular/router'; +import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; +import { + DYNAMIC_ERROR_MESSAGES_MATCHER, + DynamicErrorMessagesMatcher, +} from '@ng-dynamic-forms/core'; + +import { AccessControlFormModule } from '../shared/access-control-form-container/access-control-form.module'; +import { FormModule } from '../shared/form/form.module'; +import { SearchModule } from '../shared/search/search.module'; import { SharedModule } from '../shared/shared.module'; import { AccessControlRoutingModule } from './access-control-routing.module'; +import { BulkAccessBrowseComponent } from './bulk-access/browse/bulk-access-browse.component'; +import { BulkAccessComponent } from './bulk-access/bulk-access.component'; +import { BulkAccessSettingsComponent } from './bulk-access/settings/bulk-access-settings.component'; import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.component'; import { EPersonFormComponent } from './epeople-registry/eperson-form/eperson-form.component'; import { GroupFormComponent } from './group-registry/group-form/group-form.component'; import { MembersListComponent } from './group-registry/group-form/members-list/members-list.component'; import { SubgroupsListComponent } from './group-registry/group-form/subgroup-list/subgroups-list.component'; import { GroupsRegistryComponent } from './group-registry/groups-registry.component'; -import { FormModule } from '../shared/form/form.module'; -import { DYNAMIC_ERROR_MESSAGES_MATCHER, DynamicErrorMessagesMatcher } from '@ng-dynamic-forms/core'; -import { AbstractControl } from '@angular/forms'; -import { BulkAccessComponent } from './bulk-access/bulk-access.component'; -import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; -import { BulkAccessBrowseComponent } from './bulk-access/browse/bulk-access-browse.component'; -import { BulkAccessSettingsComponent } from './bulk-access/settings/bulk-access-settings.component'; -import { SearchModule } from '../shared/search/search.module'; -import { AccessControlFormModule } from '../shared/access-control-form-container/access-control-form.module'; /** * Condition for displaying error messages on email form field @@ -55,9 +59,9 @@ export const ValidateEmailErrorStateMatcher: DynamicErrorMessagesMatcher = providers: [ { provide: DYNAMIC_ERROR_MESSAGES_MATCHER, - useValue: ValidateEmailErrorStateMatcher + useValue: ValidateEmailErrorStateMatcher, }, - ] + ], }) /** * This module handles all components related to the access control pages diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts index 87b2a8d5684..e99c25a195e 100644 --- a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.spec.ts @@ -1,16 +1,22 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; - -import { of } from 'rxjs'; -import { NgbAccordionModule, NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; +import { + ComponentFixture, + TestBed, + waitForAsync, +} from '@angular/core/testing'; +import { + NgbAccordionModule, + NgbNavModule, +} from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; +import { of } from 'rxjs'; -import { BulkAccessBrowseComponent } from './bulk-access-browse.component'; +import { buildPaginatedList } from '../../../core/data/paginated-list.model'; +import { PageInfo } from '../../../core/shared/page-info.model'; import { SelectableListService } from '../../../shared/object-list/selectable-list/selectable-list.service'; import { SelectableObject } from '../../../shared/object-list/selectable-list/selectable-list.service.spec'; -import { PageInfo } from '../../../core/shared/page-info.model'; -import { buildPaginatedList } from '../../../core/data/paginated-list.model'; import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; +import { BulkAccessBrowseComponent } from './bulk-access-browse.component'; describe('BulkAccessBrowseComponent', () => { let component: BulkAccessBrowseComponent; @@ -31,13 +37,13 @@ describe('BulkAccessBrowseComponent', () => { imports: [ NgbAccordionModule, NgbNavModule, - TranslateModule.forRoot() + TranslateModule.forRoot(), ], declarations: [BulkAccessBrowseComponent], - providers: [ { provide: SelectableListService, useValue: selectableListService }, ], + providers: [ { provide: SelectableListService, useValue: selectableListService } ], schemas: [ - NO_ERRORS_SCHEMA - ] + NO_ERRORS_SCHEMA, + ], }).compileComponents(); })); @@ -72,7 +78,7 @@ describe('BulkAccessBrowseComponent', () => { 'elementsPerPage': 5, 'totalElements': 2, 'totalPages': 1, - 'currentPage': 1 + 'currentPage': 1, }), [selected1, selected2]) ; const rd = createSuccessfulRemoteDataObject(list); diff --git a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts index e806e729c8e..6b221f107e5 100644 --- a/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts +++ b/src/app/access-control/bulk-access/browse/bulk-access-browse.component.ts @@ -1,19 +1,32 @@ -import { Component, Input, OnDestroy, OnInit } from '@angular/core'; - -import { BehaviorSubject, Subscription } from 'rxjs'; -import { distinctUntilChanged, map } from 'rxjs/operators'; - -import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component'; -import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; -import { SelectableListService } from '../../../shared/object-list/selectable-list/selectable-list.service'; -import { SelectableListState } from '../../../shared/object-list/selectable-list/selectable-list.reducer'; +import { + Component, + Input, + OnDestroy, + OnInit, +} from '@angular/core'; +import { + BehaviorSubject, + Subscription, +} from 'rxjs'; +import { + distinctUntilChanged, + map, +} from 'rxjs/operators'; + +import { + buildPaginatedList, + PaginatedList, +} from '../../../core/data/paginated-list.model'; import { RemoteData } from '../../../core/data/remote-data'; -import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model'; -import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model'; -import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; import { PageInfo } from '../../../core/shared/page-info.model'; -import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; +import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component'; import { hasValue } from '../../../shared/empty.util'; +import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model'; +import { SelectableListState } from '../../../shared/object-list/selectable-list/selectable-list.reducer'; +import { SelectableListService } from '../../../shared/object-list/selectable-list/selectable-list.service'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; @Component({ selector: 'ds-bulk-access-browse', @@ -22,9 +35,9 @@ import { hasValue } from '../../../shared/empty.util'; providers: [ { provide: SEARCH_CONFIG_SERVICE, - useClass: SearchConfigurationService - } - ] + useClass: SearchConfigurationService, + }, + ], }) export class BulkAccessBrowseComponent implements OnInit, OnDestroy { @@ -49,7 +62,7 @@ export class BulkAccessBrowseComponent implements OnInit, OnDestroy { paginationOptions$: BehaviorSubject = new BehaviorSubject(Object.assign(new PaginationComponentOptions(), { id: 'bas', pageSize: 5, - currentPage: 1 + currentPage: 1, })); /** @@ -67,20 +80,20 @@ export class BulkAccessBrowseComponent implements OnInit, OnDestroy { this.subs.push( this.selectableListService.getSelectableList(this.listId).pipe( distinctUntilChanged(), - map((list: SelectableListState) => this.generatePaginatedListBySelectedElements(list)) - ).subscribe(this.objectsSelected$) + map((list: SelectableListState) => this.generatePaginatedListBySelectedElements(list)), + ).subscribe(this.objectsSelected$), ); } pageNext() { this.paginationOptions$.next(Object.assign(new PaginationComponentOptions(), this.paginationOptions$.value, { - currentPage: this.paginationOptions$.value.currentPage + 1 + currentPage: this.paginationOptions$.value.currentPage + 1, })); } pagePrev() { this.paginationOptions$.next(Object.assign(new PaginationComponentOptions(), this.paginationOptions$.value, { - currentPage: this.paginationOptions$.value.currentPage - 1 + currentPage: this.paginationOptions$.value.currentPage - 1, })); } @@ -99,12 +112,12 @@ export class BulkAccessBrowseComponent implements OnInit, OnDestroy { elementsPerPage: this.paginationOptions$.value.pageSize, totalElements: list?.selection.length, totalPages: this.calculatePageCount(this.paginationOptions$.value.pageSize, list?.selection.length), - currentPage: this.paginationOptions$.value.currentPage + currentPage: this.paginationOptions$.value.currentPage, }); if (pageInfo.currentPage > pageInfo.totalPages) { pageInfo.currentPage = pageInfo.totalPages; this.paginationOptions$.next(Object.assign(new PaginationComponentOptions(), this.paginationOptions$.value, { - currentPage: pageInfo.currentPage + currentPage: pageInfo.currentPage, })); } return createSuccessfulRemoteDataObject(buildPaginatedList(pageInfo, list?.selection || [])); diff --git a/src/app/access-control/bulk-access/bulk-access.component.spec.ts b/src/app/access-control/bulk-access/bulk-access.component.spec.ts index e9b253147dc..e7ec28c132b 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.spec.ts +++ b/src/app/access-control/bulk-access/bulk-access.component.spec.ts @@ -1,18 +1,20 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; - +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; import { TranslateModule } from '@ngx-translate/core'; import { of } from 'rxjs'; -import { BulkAccessComponent } from './bulk-access.component'; +import { Process } from '../../process-page/processes/process.model'; import { BulkAccessControlService } from '../../shared/access-control-form-container/bulk-access-control.service'; -import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; import { SelectableListState } from '../../shared/object-list/selectable-list/selectable-list.reducer'; +import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; -import { Process } from '../../process-page/processes/process.model'; -import { RouterTestingModule } from '@angular/router/testing'; -import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; +import { BulkAccessComponent } from './bulk-access.component'; describe('BulkAccessComponent', () => { let component: BulkAccessComponent; @@ -31,35 +33,35 @@ describe('BulkAccessComponent', () => { 'startDate': { 'year': 2026, 'month': 5, - 'day': 31 + 'day': 31, }, - 'endDate': null - } + 'endDate': null, + }, ], 'state': { 'item': { 'toggleStatus': true, - 'accessMode': 'replace' + 'accessMode': 'replace', }, 'bitstream': { 'toggleStatus': false, 'accessMode': '', 'changesLimit': '', - 'selectedBitstreams': [] - } - } + 'selectedBitstreams': [], + }, + }, }; const mockFile = { 'uuids': [ - '1234', '5678' + '1234', '5678', ], - 'file': { } + 'file': { }, }; const mockSettings: any = jasmine.createSpyObj('AccessControlFormContainerComponent', { getValue: jasmine.createSpy('getValue'), - reset: jasmine.createSpy('reset') + reset: jasmine.createSpy('reset'), }); const selection: any[] = [{ indexableObject: { uuid: '1234' } }, { indexableObject: { uuid: '5678' } }]; const selectableListState: SelectableListState = { id: 'test', selection }; @@ -71,15 +73,15 @@ describe('BulkAccessComponent', () => { await TestBed.configureTestingModule({ imports: [ RouterTestingModule, - TranslateModule.forRoot() + TranslateModule.forRoot(), ], declarations: [ BulkAccessComponent ], providers: [ { provide: BulkAccessControlService, useValue: bulkAccessControlServiceMock }, { provide: NotificationsService, useValue: NotificationsServiceStub }, - { provide: SelectableListService, useValue: selectableListServiceMock } + { provide: SelectableListService, useValue: selectableListServiceMock }, ], - schemas: [NO_ERRORS_SCHEMA] + schemas: [NO_ERRORS_SCHEMA], }) .compileComponents(); }); diff --git a/src/app/access-control/bulk-access/bulk-access.component.ts b/src/app/access-control/bulk-access/bulk-access.component.ts index 04724614cb6..52faa97dbc7 100644 --- a/src/app/access-control/bulk-access/bulk-access.component.ts +++ b/src/app/access-control/bulk-access/bulk-access.component.ts @@ -1,17 +1,26 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { + Component, + OnInit, + ViewChild, +} from '@angular/core'; +import { + BehaviorSubject, + Subscription, +} from 'rxjs'; +import { + distinctUntilChanged, + map, +} from 'rxjs/operators'; -import { BehaviorSubject, Subscription } from 'rxjs'; -import { distinctUntilChanged, map } from 'rxjs/operators'; - -import { BulkAccessSettingsComponent } from './settings/bulk-access-settings.component'; import { BulkAccessControlService } from '../../shared/access-control-form-container/bulk-access-control.service'; import { SelectableListState } from '../../shared/object-list/selectable-list/selectable-list.reducer'; import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service'; +import { BulkAccessSettingsComponent } from './settings/bulk-access-settings.component'; @Component({ selector: 'ds-bulk-access', templateUrl: './bulk-access.component.html', - styleUrls: ['./bulk-access.component.scss'] + styleUrls: ['./bulk-access.component.scss'], }) export class BulkAccessComponent implements OnInit { @@ -37,7 +46,7 @@ export class BulkAccessComponent implements OnInit { constructor( private bulkAccessControlService: BulkAccessControlService, - private selectableListService: SelectableListService + private selectableListService: SelectableListService, ) { } @@ -45,8 +54,8 @@ export class BulkAccessComponent implements OnInit { this.subs.push( this.selectableListService.getSelectableList(this.listId).pipe( distinctUntilChanged(), - map((list: SelectableListState) => this.generateIdListBySelectedElements(list)) - ).subscribe(this.objectsSelected$) + map((list: SelectableListState) => this.generateIdListBySelectedElements(list)), + ).subscribe(this.objectsSelected$), ); } @@ -74,12 +83,12 @@ export class BulkAccessComponent implements OnInit { const { file } = this.bulkAccessControlService.createPayloadFile({ bitstreamAccess, itemAccess, - state: settings.state + state: settings.state, }); this.bulkAccessControlService.executeScript( this.objectsSelected$.value || [], - file + file, ).subscribe(); } diff --git a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts index 14e0fdefb21..f3c1cad04ab 100644 --- a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts +++ b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.spec.ts @@ -1,8 +1,12 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; + import { BulkAccessSettingsComponent } from './bulk-access-settings.component'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; describe('BulkAccessSettingsComponent', () => { let component: BulkAccessSettingsComponent; @@ -15,35 +19,35 @@ describe('BulkAccessSettingsComponent', () => { 'startDate': { 'year': 2026, 'month': 5, - 'day': 31 + 'day': 31, }, - 'endDate': null - } + 'endDate': null, + }, ], 'state': { 'item': { 'toggleStatus': true, - 'accessMode': 'replace' + 'accessMode': 'replace', }, 'bitstream': { 'toggleStatus': false, 'accessMode': '', 'changesLimit': '', - 'selectedBitstreams': [] - } - } + 'selectedBitstreams': [], + }, + }, }; const mockControl: any = jasmine.createSpyObj('AccessControlFormContainerComponent', { getFormValue: jasmine.createSpy('getFormValue'), - reset: jasmine.createSpy('reset') + reset: jasmine.createSpy('reset'), }); beforeEach(async () => { await TestBed.configureTestingModule({ imports: [NgbAccordionModule, TranslateModule.forRoot()], declarations: [BulkAccessSettingsComponent], - schemas: [NO_ERRORS_SCHEMA] + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts index eecc0162451..d0f95377eb2 100644 --- a/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts +++ b/src/app/access-control/bulk-access/settings/bulk-access-settings.component.ts @@ -1,13 +1,15 @@ -import { Component, ViewChild } from '@angular/core'; import { - AccessControlFormContainerComponent -} from '../../../shared/access-control-form-container/access-control-form-container.component'; + Component, + ViewChild, +} from '@angular/core'; + +import { AccessControlFormContainerComponent } from '../../../shared/access-control-form-container/access-control-form-container.component'; @Component({ selector: 'ds-bulk-access-settings', templateUrl: 'bulk-access-settings.component.html', styleUrls: ['./bulk-access-settings.component.scss'], - exportAs: 'dsBulkSettings' + exportAs: 'dsBulkSettings', }) export class BulkAccessSettingsComponent { diff --git a/src/app/access-control/epeople-registry/epeople-registry.actions.ts b/src/app/access-control/epeople-registry/epeople-registry.actions.ts index a07ea37df29..e6e7608ba3f 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.actions.ts +++ b/src/app/access-control/epeople-registry/epeople-registry.actions.ts @@ -1,5 +1,6 @@ /* eslint-disable max-classes-per-file */ import { Action } from '@ngrx/store'; + import { EPerson } from '../../core/eperson/models/eperson.model'; import { type } from '../../shared/ngrx/type'; diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.html b/src/app/access-control/epeople-registry/epeople-registry.component.html index bf7b9a2060d..92968d2e28b 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.html +++ b/src/app/access-control/epeople-registry/epeople-registry.component.html @@ -43,7 +43,7 @@ -