mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge branch 'DSpace:main' into main
This commit is contained in:
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@@ -15,12 +15,19 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
# The ci step will test the dspace-angular code against DSpace REST.
|
# The ci step will test the dspace-angular code against DSpace REST.
|
||||||
# Direct that step to utilize a DSpace REST service that has been started in docker.
|
# Direct that step to utilize a DSpace REST service that has been started in docker.
|
||||||
|
# NOTE: These settings should be kept in sync with those in [src]/docker/docker-compose-ci.yml
|
||||||
DSPACE_REST_HOST: 127.0.0.1
|
DSPACE_REST_HOST: 127.0.0.1
|
||||||
DSPACE_REST_PORT: 8080
|
DSPACE_REST_PORT: 8080
|
||||||
DSPACE_REST_NAMESPACE: '/server'
|
DSPACE_REST_NAMESPACE: '/server'
|
||||||
DSPACE_REST_SSL: false
|
DSPACE_REST_SSL: false
|
||||||
# Spin up UI on 127.0.0.1 to avoid host resolution issues in e2e tests with Node 18+
|
# Spin up UI on 127.0.0.1 to avoid host resolution issues in e2e tests with Node 18+
|
||||||
DSPACE_UI_HOST: 127.0.0.1
|
DSPACE_UI_HOST: 127.0.0.1
|
||||||
|
DSPACE_UI_PORT: 4000
|
||||||
|
# Ensure all SSR caching is disabled in test environment
|
||||||
|
DSPACE_CACHE_SERVERSIDE_BOTCACHE_MAX: 0
|
||||||
|
DSPACE_CACHE_SERVERSIDE_ANONYMOUSCACHE_MAX: 0
|
||||||
|
# Tell Cypress to run e2e tests using the same UI URL
|
||||||
|
CYPRESS_BASE_URL: http://127.0.0.1:4000
|
||||||
# When Chrome version is specified, we pin to a specific version of Chrome
|
# When Chrome version is specified, we pin to a specific version of Chrome
|
||||||
# Comment this out to use the latest release
|
# Comment this out to use the latest release
|
||||||
#CHROME_VERSION: "90.0.4430.212-1"
|
#CHROME_VERSION: "90.0.4430.212-1"
|
||||||
|
44
cypress.config.ts
Normal file
44
cypress.config.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
videosFolder: 'cypress/videos',
|
||||||
|
screenshotsFolder: 'cypress/screenshots',
|
||||||
|
fixturesFolder: 'cypress/fixtures',
|
||||||
|
retries: {
|
||||||
|
runMode: 2,
|
||||||
|
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.
|
||||||
|
// 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
|
||||||
|
DSPACE_TEST_ADMIN_USER: 'dspacedemo+admin@gmail.com',
|
||||||
|
DSPACE_TEST_ADMIN_PASSWORD: 'dspace',
|
||||||
|
// 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',
|
||||||
|
// Search term (should return results) used in search tests
|
||||||
|
DSPACE_TEST_SEARCH_TERM: 'test',
|
||||||
|
// Collection used for submission tests
|
||||||
|
DSPACE_TEST_SUBMIT_COLLECTION_NAME: 'Sample Collection',
|
||||||
|
DSPACE_TEST_SUBMIT_COLLECTION_UUID: '9d8334e9-25d3-4a67-9cea-3dffdef80144',
|
||||||
|
// Account used to test basic submission process
|
||||||
|
DSPACE_TEST_SUBMIT_USER: 'dspacedemo+submit@gmail.com',
|
||||||
|
DSPACE_TEST_SUBMIT_USER_PASSWORD: 'dspace',
|
||||||
|
},
|
||||||
|
e2e: {
|
||||||
|
// Setup our plugins for e2e tests
|
||||||
|
setupNodeEvents(on, config) {
|
||||||
|
return require('./cypress/plugins/index.ts')(on, config);
|
||||||
|
},
|
||||||
|
// This is the base URL that Cypress will run all tests against
|
||||||
|
// It can be overridden via the CYPRESS_BASE_URL environment variable
|
||||||
|
// (By default we set this to a value which should work in most development environments)
|
||||||
|
baseUrl: 'http://localhost:4000',
|
||||||
|
},
|
||||||
|
});
|
25
cypress.json
25
cypress.json
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"integrationFolder": "cypress/integration",
|
|
||||||
"supportFile": "cypress/support/index.ts",
|
|
||||||
"videosFolder": "cypress/videos",
|
|
||||||
"screenshotsFolder": "cypress/screenshots",
|
|
||||||
"pluginsFile": "cypress/plugins/index.ts",
|
|
||||||
"fixturesFolder": "cypress/fixtures",
|
|
||||||
"baseUrl": "http://127.0.0.1:4000",
|
|
||||||
"retries": {
|
|
||||||
"runMode": 2,
|
|
||||||
"openMode": 0
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"DSPACE_TEST_ADMIN_USER": "dspacedemo+admin@gmail.com",
|
|
||||||
"DSPACE_TEST_ADMIN_PASSWORD": "dspace",
|
|
||||||
"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_SEARCH_TERM": "test",
|
|
||||||
"DSPACE_TEST_SUBMIT_COLLECTION_NAME": "Sample Collection",
|
|
||||||
"DSPACE_TEST_SUBMIT_COLLECTION_UUID": "9d8334e9-25d3-4a67-9cea-3dffdef80144",
|
|
||||||
"DSPACE_TEST_SUBMIT_USER": "dspacedemo+submit@gmail.com",
|
|
||||||
"DSPACE_TEST_SUBMIT_USER_PASSWORD": "dspace"
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,10 +1,10 @@
|
|||||||
import { TEST_ENTITY_PUBLICATION } from 'cypress/support';
|
import { TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e';
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Breadcrumbs', () => {
|
describe('Breadcrumbs', () => {
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
// Visit an Item, as those have more breadcrumbs
|
// Visit an Item, as those have more breadcrumbs
|
||||||
cy.visit('/entities/publication/' + TEST_ENTITY_PUBLICATION);
|
cy.visit('/entities/publication/'.concat(TEST_ENTITY_PUBLICATION));
|
||||||
|
|
||||||
// Wait for breadcrumbs to be visible
|
// Wait for breadcrumbs to be visible
|
||||||
cy.get('ds-breadcrumbs').should('be.visible');
|
cy.get('ds-breadcrumbs').should('be.visible');
|
@@ -1,13 +1,13 @@
|
|||||||
import { TEST_COLLECTION } from 'cypress/support';
|
import { TEST_COLLECTION } from 'cypress/support/e2e';
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Collection Page', () => {
|
describe('Collection Page', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.visit('/collections/' + TEST_COLLECTION);
|
cy.visit('/collections/'.concat(TEST_COLLECTION));
|
||||||
|
|
||||||
// <ds-collection-page> tag must be loaded
|
// <ds-collection-page> tag must be loaded
|
||||||
cy.get('ds-collection-page').should('exist');
|
cy.get('ds-collection-page').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-collection-page> for accessibility issues
|
// Analyze <ds-collection-page> for accessibility issues
|
||||||
testA11y('ds-collection-page');
|
testA11y('ds-collection-page');
|
37
cypress/e2e/collection-statistics.cy.ts
Normal file
37
cypress/e2e/collection-statistics.cy.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { REGEX_MATCH_NON_EMPTY_TEXT, TEST_COLLECTION } from 'cypress/support/e2e';
|
||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Collection Statistics Page', () => {
|
||||||
|
const COLLECTIONSTATISTICSPAGE = '/statistics/collections/'.concat(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.location('pathname').should('eq', COLLECTIONSTATISTICSPAGE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a "Total visits" section', () => {
|
||||||
|
cy.visit(COLLECTIONSTATISTICSPAGE);
|
||||||
|
cy.get('table[data-test="TotalVisits"]').should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
cy.visit(COLLECTIONSTATISTICSPAGE);
|
||||||
|
|
||||||
|
// <ds-collection-statistics-page> tag must be loaded
|
||||||
|
cy.get('ds-collection-statistics-page').should('be.visible');
|
||||||
|
|
||||||
|
// Verify / wait until "Total Visits" table's label is non-empty
|
||||||
|
// (This table loads these labels asynchronously, so we want to wait for them before analyzing page)
|
||||||
|
cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT);
|
||||||
|
|
||||||
|
// Analyze <ds-collection-statistics-page> for accessibility issues
|
||||||
|
testA11y('ds-collection-statistics-page');
|
||||||
|
});
|
||||||
|
});
|
@@ -7,10 +7,10 @@ describe('Community List Page', () => {
|
|||||||
cy.visit('/community-list');
|
cy.visit('/community-list');
|
||||||
|
|
||||||
// <ds-community-list-page> tag must be loaded
|
// <ds-community-list-page> tag must be loaded
|
||||||
cy.get('ds-community-list-page').should('exist');
|
cy.get('ds-community-list-page').should('be.visible');
|
||||||
|
|
||||||
// Open first Community (to show Collections)...that way we scan sub-elements as well
|
// Open every expand button on page, so that we can scan sub-elements as well
|
||||||
cy.get('ds-community-list :nth-child(1) > .btn-group > .btn').click();
|
cy.get('[data-test="expand-button"]').click({ multiple: true });
|
||||||
|
|
||||||
// Analyze <ds-community-list-page> for accessibility issues
|
// Analyze <ds-community-list-page> for accessibility issues
|
||||||
// Disable heading-order checks until it is fixed
|
// Disable heading-order checks until it is fixed
|
@@ -1,13 +1,13 @@
|
|||||||
import { TEST_COMMUNITY } from 'cypress/support';
|
import { TEST_COMMUNITY } from 'cypress/support/e2e';
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Community Page', () => {
|
describe('Community Page', () => {
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.visit('/communities/' + TEST_COMMUNITY);
|
cy.visit('/communities/'.concat(TEST_COMMUNITY));
|
||||||
|
|
||||||
// <ds-community-page> tag must be loaded
|
// <ds-community-page> tag must be loaded
|
||||||
cy.get('ds-community-page').should('exist');
|
cy.get('ds-community-page').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-community-page> for accessibility issues
|
// Analyze <ds-community-page> for accessibility issues
|
||||||
testA11y('ds-community-page',);
|
testA11y('ds-community-page',);
|
37
cypress/e2e/community-statistics.cy.ts
Normal file
37
cypress/e2e/community-statistics.cy.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { REGEX_MATCH_NON_EMPTY_TEXT, TEST_COMMUNITY } from 'cypress/support/e2e';
|
||||||
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
|
describe('Community Statistics Page', () => {
|
||||||
|
const COMMUNITYSTATISTICSPAGE = '/statistics/communities/'.concat(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.location('pathname').should('eq', COMMUNITYSTATISTICSPAGE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a "Total visits" section', () => {
|
||||||
|
cy.visit(COMMUNITYSTATISTICSPAGE);
|
||||||
|
cy.get('table[data-test="TotalVisits"]').should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass accessibility tests', () => {
|
||||||
|
cy.visit(COMMUNITYSTATISTICSPAGE);
|
||||||
|
|
||||||
|
// <ds-community-statistics-page> tag must be loaded
|
||||||
|
cy.get('ds-community-statistics-page').should('be.visible');
|
||||||
|
|
||||||
|
// Verify / wait until "Total Visits" table's label is non-empty
|
||||||
|
// (This table loads these labels asynchronously, so we want to wait for them before analyzing page)
|
||||||
|
cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT);
|
||||||
|
|
||||||
|
// Analyze <ds-community-statistics-page> for accessibility issues
|
||||||
|
testA11y('ds-community-statistics-page');
|
||||||
|
});
|
||||||
|
});
|
31
cypress/e2e/homepage-statistics.cy.ts
Normal file
31
cypress/e2e/homepage-statistics.cy.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { REGEX_MATCH_NON_EMPTY_TEXT, TEST_ENTITY_PUBLICATION } 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.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.visit('/statistics');
|
||||||
|
|
||||||
|
// <ds-site-statistics-page> tag must be visable
|
||||||
|
cy.get('ds-site-statistics-page').should('be.visible');
|
||||||
|
|
||||||
|
// Verify / wait until "Total Visits" table's *last* label is non-empty
|
||||||
|
// (This table loads these labels asynchronously, so we want to wait for them before analyzing page)
|
||||||
|
cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').last().contains(REGEX_MATCH_NON_EMPTY_TEXT);
|
||||||
|
// Wait an extra 500ms, just so all entries in Total Visits have loaded.
|
||||||
|
cy.wait(500);
|
||||||
|
|
||||||
|
// Analyze <ds-site-statistics-page> for accessibility issues
|
||||||
|
testA11y('ds-site-statistics-page');
|
||||||
|
});
|
||||||
|
});
|
@@ -1,10 +1,10 @@
|
|||||||
import { Options } from 'cypress-axe';
|
import { Options } from 'cypress-axe';
|
||||||
import { TEST_ENTITY_PUBLICATION } from 'cypress/support';
|
import { TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e';
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Item Page', () => {
|
describe('Item Page', () => {
|
||||||
const ITEMPAGE = '/items/' + TEST_ENTITY_PUBLICATION;
|
const ITEMPAGE = '/items/'.concat(TEST_ENTITY_PUBLICATION);
|
||||||
const ENTITYPAGE = '/entities/publication/' + TEST_ENTITY_PUBLICATION;
|
const ENTITYPAGE = '/entities/publication/'.concat(TEST_ENTITY_PUBLICATION);
|
||||||
|
|
||||||
// Test that entities will redirect to /entities/[type]/[uuid] when accessed via /items/[uuid]
|
// 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', () => {
|
it('should redirect to the entity page when navigating to an item page', () => {
|
||||||
@@ -16,7 +16,7 @@ describe('Item Page', () => {
|
|||||||
cy.visit(ENTITYPAGE);
|
cy.visit(ENTITYPAGE);
|
||||||
|
|
||||||
// <ds-item-page> tag must be loaded
|
// <ds-item-page> tag must be loaded
|
||||||
cy.get('ds-item-page').should('exist');
|
cy.get('ds-item-page').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-item-page> for accessibility issues
|
// Analyze <ds-item-page> for accessibility issues
|
||||||
// Disable heading-order checks until it is fixed
|
// Disable heading-order checks until it is fixed
|
@@ -1,36 +1,41 @@
|
|||||||
import { TEST_ENTITY_PUBLICATION } from 'cypress/support';
|
import { REGEX_MATCH_NON_EMPTY_TEXT, TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e';
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Item Statistics Page', () => {
|
describe('Item Statistics Page', () => {
|
||||||
const ITEMSTATISTICSPAGE = '/statistics/items/' + TEST_ENTITY_PUBLICATION;
|
const ITEMSTATISTICSPAGE = '/statistics/items/'.concat(TEST_ENTITY_PUBLICATION);
|
||||||
|
|
||||||
it('should load if you click on "Statistics" from an Item/Entity page', () => {
|
it('should load if you click on "Statistics" from an Item/Entity page', () => {
|
||||||
cy.visit('/entities/publication/' + TEST_ENTITY_PUBLICATION);
|
cy.visit('/entities/publication/'.concat(TEST_ENTITY_PUBLICATION));
|
||||||
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
|
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
|
||||||
cy.location('pathname').should('eq', ITEMSTATISTICSPAGE);
|
cy.location('pathname').should('eq', ITEMSTATISTICSPAGE);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain element ds-item-statistics-page when navigating to an item statistics page', () => {
|
it('should contain element ds-item-statistics-page when navigating to an item statistics page', () => {
|
||||||
cy.visit(ITEMSTATISTICSPAGE);
|
cy.visit(ITEMSTATISTICSPAGE);
|
||||||
cy.get('ds-item-statistics-page').should('exist');
|
cy.get('ds-item-statistics-page').should('be.visible');
|
||||||
cy.get('ds-item-page').should('not.exist');
|
cy.get('ds-item-page').should('not.exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain a "Total visits" section', () => {
|
it('should contain a "Total visits" section', () => {
|
||||||
cy.visit(ITEMSTATISTICSPAGE);
|
cy.visit(ITEMSTATISTICSPAGE);
|
||||||
cy.get('.' + TEST_ENTITY_PUBLICATION + '_TotalVisits').should('exist');
|
cy.get('table[data-test="TotalVisits"]').should('be.visible');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain a "Total visits per month" section', () => {
|
it('should contain a "Total visits per month" section', () => {
|
||||||
cy.visit(ITEMSTATISTICSPAGE);
|
cy.visit(ITEMSTATISTICSPAGE);
|
||||||
cy.get('.' + TEST_ENTITY_PUBLICATION + '_TotalVisitsPerMonth').should('exist');
|
// 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');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
cy.visit(ITEMSTATISTICSPAGE);
|
cy.visit(ITEMSTATISTICSPAGE);
|
||||||
|
|
||||||
// <ds-item-statistics-page> tag must be loaded
|
// <ds-item-statistics-page> tag must be loaded
|
||||||
cy.get('ds-item-statistics-page').should('exist');
|
cy.get('ds-item-statistics-page').should('be.visible');
|
||||||
|
|
||||||
|
// Verify / wait until "Total Visits" table's label is non-empty
|
||||||
|
// (This table loads these labels asynchronously, so we want to wait for them before analyzing page)
|
||||||
|
cy.get('table[data-test="TotalVisits"] th[data-test="statistics-label"]').contains(REGEX_MATCH_NON_EMPTY_TEXT);
|
||||||
|
|
||||||
// Analyze <ds-item-statistics-page> for accessibility issues
|
// Analyze <ds-item-statistics-page> for accessibility issues
|
||||||
testA11y('ds-item-statistics-page');
|
testA11y('ds-item-statistics-page');
|
@@ -1,4 +1,4 @@
|
|||||||
import { TEST_ADMIN_PASSWORD, TEST_ADMIN_USER, TEST_ENTITY_PUBLICATION } from 'cypress/support';
|
import { TEST_ADMIN_PASSWORD, TEST_ADMIN_USER, TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e';
|
||||||
|
|
||||||
const page = {
|
const page = {
|
||||||
openLoginMenu() {
|
openLoginMenu() {
|
||||||
@@ -36,7 +36,7 @@ const page = {
|
|||||||
|
|
||||||
describe('Login Modal', () => {
|
describe('Login Modal', () => {
|
||||||
it('should login when clicking button & stay on same page', () => {
|
it('should login when clicking button & stay on same page', () => {
|
||||||
const ENTITYPAGE = '/entities/publication/' + TEST_ENTITY_PUBLICATION;
|
const ENTITYPAGE = '/entities/publication/'.concat(TEST_ENTITY_PUBLICATION);
|
||||||
cy.visit(ENTITYPAGE);
|
cy.visit(ENTITYPAGE);
|
||||||
|
|
||||||
// Login menu should exist
|
// Login menu should exist
|
@@ -1,5 +1,5 @@
|
|||||||
import { Options } from 'cypress-axe';
|
import { Options } from 'cypress-axe';
|
||||||
import { TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD, TEST_SUBMIT_COLLECTION_NAME } from 'cypress/support';
|
import { TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD, TEST_SUBMIT_COLLECTION_NAME } from 'cypress/support/e2e';
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('My DSpace page', () => {
|
describe('My DSpace page', () => {
|
||||||
@@ -9,7 +9,7 @@ describe('My DSpace page', () => {
|
|||||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
// 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(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||||
|
|
||||||
cy.get('ds-my-dspace-page').should('exist');
|
cy.get('ds-my-dspace-page').should('be.visible');
|
||||||
|
|
||||||
// At least one recent submission should be displayed
|
// At least one recent submission should be displayed
|
||||||
cy.get('[data-test="list-object"]').should('be.visible');
|
cy.get('[data-test="list-object"]').should('be.visible');
|
||||||
@@ -42,12 +42,12 @@ describe('My DSpace page', () => {
|
|||||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
// 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(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||||
|
|
||||||
cy.get('ds-my-dspace-page').should('exist');
|
cy.get('ds-my-dspace-page').should('be.visible');
|
||||||
|
|
||||||
// Click button in sidebar to display detailed view
|
// Click button in sidebar to display detailed view
|
||||||
cy.get('ds-search-sidebar [data-test="detail-view"]').click();
|
cy.get('ds-search-sidebar [data-test="detail-view"]').click();
|
||||||
|
|
||||||
cy.get('ds-object-detail').should('exist');
|
cy.get('ds-object-detail').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-search-page> for accessibility issues
|
// Analyze <ds-search-page> for accessibility issues
|
||||||
testA11y('ds-my-dspace-page',
|
testA11y('ds-my-dspace-page',
|
||||||
@@ -80,7 +80,7 @@ describe('My DSpace page', () => {
|
|||||||
cy.get('ds-authorized-collection-selector input[type="search"]').type(TEST_SUBMIT_COLLECTION_NAME);
|
cy.get('ds-authorized-collection-selector input[type="search"]').type(TEST_SUBMIT_COLLECTION_NAME);
|
||||||
|
|
||||||
// Click on the button matching that known Collection name
|
// Click on the button matching that known Collection name
|
||||||
cy.get('ds-authorized-collection-selector button[title="' + TEST_SUBMIT_COLLECTION_NAME + '"]').click();
|
cy.get('ds-authorized-collection-selector button[title="'.concat(TEST_SUBMIT_COLLECTION_NAME).concat('"]')).click();
|
||||||
|
|
||||||
// New URL should include /workspaceitems, as we've started a new submission
|
// New URL should include /workspaceitems, as we've started a new submission
|
||||||
cy.url().should('include', '/workspaceitems');
|
cy.url().should('include', '/workspaceitems');
|
@@ -2,7 +2,7 @@ describe('PageNotFound', () => {
|
|||||||
it('should contain element ds-pagenotfound when navigating to page that doesnt exist', () => {
|
it('should contain element ds-pagenotfound when navigating to page that doesnt exist', () => {
|
||||||
// request an invalid page (UUIDs at root path aren't valid)
|
// request an invalid page (UUIDs at root path aren't valid)
|
||||||
cy.visit('/e9019a69-d4f1-4773-b6a3-bd362caa46f2', { failOnStatusCode: false });
|
cy.visit('/e9019a69-d4f1-4773-b6a3-bd362caa46f2', { failOnStatusCode: false });
|
||||||
cy.get('ds-pagenotfound').should('exist');
|
cy.get('ds-pagenotfound').should('be.visible');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not contain element ds-pagenotfound when navigating to existing page', () => {
|
it('should not contain element ds-pagenotfound when navigating to existing page', () => {
|
@@ -1,4 +1,4 @@
|
|||||||
import { TEST_SEARCH_TERM } from 'cypress/support';
|
import { TEST_SEARCH_TERM } from 'cypress/support/e2e';
|
||||||
|
|
||||||
const page = {
|
const page = {
|
||||||
fillOutQueryInNavBar(query) {
|
fillOutQueryInNavBar(query) {
|
||||||
@@ -27,7 +27,7 @@ describe('Search from Navigation Bar', () => {
|
|||||||
page.fillOutQueryInNavBar(query);
|
page.fillOutQueryInNavBar(query);
|
||||||
page.submitQueryByPressingEnter();
|
page.submitQueryByPressingEnter();
|
||||||
// New URL should include query param
|
// New URL should include query param
|
||||||
cy.url().should('include', 'query=' + query);
|
cy.url().should('include', 'query='.concat(query));
|
||||||
// Wait for search results to come back from the above GET command
|
// Wait for search results to come back from the above GET command
|
||||||
cy.wait('@search-results');
|
cy.wait('@search-results');
|
||||||
// At least one search result should be displayed
|
// At least one search result should be displayed
|
||||||
@@ -42,7 +42,7 @@ describe('Search from Navigation Bar', () => {
|
|||||||
page.fillOutQueryInNavBar(query);
|
page.fillOutQueryInNavBar(query);
|
||||||
page.submitQueryByPressingEnter();
|
page.submitQueryByPressingEnter();
|
||||||
// New URL should include query param
|
// New URL should include query param
|
||||||
cy.url().should('include', 'query=' + query);
|
cy.url().should('include', 'query='.concat(query));
|
||||||
// Wait for search results to come back from the above GET command
|
// Wait for search results to come back from the above GET command
|
||||||
cy.wait('@search-results');
|
cy.wait('@search-results');
|
||||||
// At least one search result should be displayed
|
// At least one search result should be displayed
|
||||||
@@ -57,7 +57,7 @@ describe('Search from Navigation Bar', () => {
|
|||||||
page.fillOutQueryInNavBar(query);
|
page.fillOutQueryInNavBar(query);
|
||||||
page.submitQueryByPressingIcon();
|
page.submitQueryByPressingIcon();
|
||||||
// New URL should include query param
|
// New URL should include query param
|
||||||
cy.url().should('include', 'query=' + query);
|
cy.url().should('include', 'query='.concat(query));
|
||||||
// Wait for search results to come back from the above GET command
|
// Wait for search results to come back from the above GET command
|
||||||
cy.wait('@search-results');
|
cy.wait('@search-results');
|
||||||
// At least one search result should be displayed
|
// At least one search result should be displayed
|
@@ -1,5 +1,5 @@
|
|||||||
import { Options } from 'cypress-axe';
|
import { Options } from 'cypress-axe';
|
||||||
import { TEST_SEARCH_TERM } from 'cypress/support';
|
import { TEST_SEARCH_TERM } from 'cypress/support/e2e';
|
||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('Search Page', () => {
|
describe('Search Page', () => {
|
||||||
@@ -13,11 +13,11 @@ describe('Search Page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should load results and pass accessibility tests', () => {
|
it('should load results and pass accessibility tests', () => {
|
||||||
cy.visit('/search?query=' + TEST_SEARCH_TERM);
|
cy.visit('/search?query='.concat(TEST_SEARCH_TERM));
|
||||||
cy.get('[data-test="search-box"]').should('have.value', TEST_SEARCH_TERM);
|
cy.get('[data-test="search-box"]').should('have.value', TEST_SEARCH_TERM);
|
||||||
|
|
||||||
// <ds-search-page> tag must be loaded
|
// <ds-search-page> tag must be loaded
|
||||||
cy.get('ds-search-page').should('exist');
|
cy.get('ds-search-page').should('be.visible');
|
||||||
|
|
||||||
// At least one search result should be displayed
|
// At least one search result should be displayed
|
||||||
cy.get('[data-test="list-object"]').should('be.visible');
|
cy.get('[data-test="list-object"]').should('be.visible');
|
||||||
@@ -45,13 +45,13 @@ describe('Search Page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have a working grid view that passes accessibility tests', () => {
|
it('should have a working grid view that passes accessibility tests', () => {
|
||||||
cy.visit('/search?query=' + TEST_SEARCH_TERM);
|
cy.visit('/search?query='.concat(TEST_SEARCH_TERM));
|
||||||
|
|
||||||
// Click button in sidebar to display grid view
|
// Click button in sidebar to display grid view
|
||||||
cy.get('ds-search-sidebar [data-test="grid-view"]').click();
|
cy.get('ds-search-sidebar [data-test="grid-view"]').click();
|
||||||
|
|
||||||
// <ds-search-page> tag must be loaded
|
// <ds-search-page> tag must be loaded
|
||||||
cy.get('ds-search-page').should('exist');
|
cy.get('ds-search-page').should('be.visible');
|
||||||
|
|
||||||
// At least one grid object (card) should be displayed
|
// At least one grid object (card) should be displayed
|
||||||
cy.get('[data-test="grid-object"]').should('be.visible');
|
cy.get('[data-test="grid-object"]').should('be.visible');
|
@@ -1,13 +1,11 @@
|
|||||||
import { Options } from 'cypress-axe';
|
import { TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD, TEST_SUBMIT_COLLECTION_NAME, TEST_SUBMIT_COLLECTION_UUID } from 'cypress/support/e2e';
|
||||||
import { TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD, TEST_SUBMIT_COLLECTION_NAME, TEST_SUBMIT_COLLECTION_UUID } from 'cypress/support';
|
|
||||||
import { testA11y } from 'cypress/support/utils';
|
|
||||||
|
|
||||||
describe('New Submission page', () => {
|
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 submissions can be started from MyDSpace in my-dspace.spec.ts
|
||||||
|
|
||||||
it('should create a new submission when using /submit path & pass accessibility', () => {
|
it('should create a new submission when using /submit path & pass accessibility', () => {
|
||||||
// Test that calling /submit with collection & entityType will create a new submission
|
// Test that calling /submit with collection & entityType will create a new submission
|
||||||
cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none');
|
cy.visit('/submit?collection='.concat(TEST_SUBMIT_COLLECTION_UUID).concat('&entityType=none'));
|
||||||
|
|
||||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
// 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(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||||
@@ -35,7 +33,7 @@ describe('New Submission page', () => {
|
|||||||
|
|
||||||
it('should block submission & show errors if required fields are missing', () => {
|
it('should block submission & show errors if required fields are missing', () => {
|
||||||
// Create a new submission
|
// Create a new submission
|
||||||
cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none');
|
cy.visit('/submit?collection='.concat(TEST_SUBMIT_COLLECTION_UUID).concat('&entityType=none'));
|
||||||
|
|
||||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
// 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(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||||
@@ -95,7 +93,7 @@ describe('New Submission page', () => {
|
|||||||
|
|
||||||
it('should allow for deposit if all required fields completed & file uploaded', () => {
|
it('should allow for deposit if all required fields completed & file uploaded', () => {
|
||||||
// Create a new submission
|
// Create a new submission
|
||||||
cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none');
|
cy.visit('/submit?collection='.concat(TEST_SUBMIT_COLLECTION_UUID).concat('&entityType=none'));
|
||||||
|
|
||||||
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
// 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(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||||
@@ -124,8 +122,6 @@ describe('New Submission page', () => {
|
|||||||
|
|
||||||
// Wait for upload to complete before proceeding
|
// Wait for upload to complete before proceeding
|
||||||
cy.wait('@upload');
|
cy.wait('@upload');
|
||||||
// Close the upload success notice
|
|
||||||
cy.get('[data-dismiss="alert"]').click({multiple: true});
|
|
||||||
|
|
||||||
// Wait for deposit button to not be disabled & click it.
|
// Wait for deposit button to not be disabled & click it.
|
||||||
cy.get('button#deposit').should('not.be.disabled').click();
|
cy.get('button#deposit').should('not.be.disabled').click();
|
@@ -1,32 +0,0 @@
|
|||||||
import { TEST_COLLECTION } from 'cypress/support';
|
|
||||||
import { testA11y } from 'cypress/support/utils';
|
|
||||||
|
|
||||||
describe('Collection Statistics Page', () => {
|
|
||||||
const COLLECTIONSTATISTICSPAGE = '/statistics/collections/' + TEST_COLLECTION;
|
|
||||||
|
|
||||||
it('should load if you click on "Statistics" from a Collection page', () => {
|
|
||||||
cy.visit('/collections/' + TEST_COLLECTION);
|
|
||||||
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
|
|
||||||
cy.location('pathname').should('eq', COLLECTIONSTATISTICSPAGE);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should contain a "Total visits" section', () => {
|
|
||||||
cy.visit(COLLECTIONSTATISTICSPAGE);
|
|
||||||
cy.get('.' + TEST_COLLECTION + '_TotalVisits').should('exist');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should contain a "Total visits per month" section', () => {
|
|
||||||
cy.visit(COLLECTIONSTATISTICSPAGE);
|
|
||||||
cy.get('.' + TEST_COLLECTION + '_TotalVisitsPerMonth').should('exist');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
|
||||||
cy.visit(COLLECTIONSTATISTICSPAGE);
|
|
||||||
|
|
||||||
// <ds-collection-statistics-page> tag must be loaded
|
|
||||||
cy.get('ds-collection-statistics-page').should('exist');
|
|
||||||
|
|
||||||
// Analyze <ds-collection-statistics-page> for accessibility issues
|
|
||||||
testA11y('ds-collection-statistics-page');
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,32 +0,0 @@
|
|||||||
import { TEST_COMMUNITY } from 'cypress/support';
|
|
||||||
import { testA11y } from 'cypress/support/utils';
|
|
||||||
|
|
||||||
describe('Community Statistics Page', () => {
|
|
||||||
const COMMUNITYSTATISTICSPAGE = '/statistics/communities/' + TEST_COMMUNITY;
|
|
||||||
|
|
||||||
it('should load if you click on "Statistics" from a Community page', () => {
|
|
||||||
cy.visit('/communities/' + TEST_COMMUNITY);
|
|
||||||
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
|
|
||||||
cy.location('pathname').should('eq', COMMUNITYSTATISTICSPAGE);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should contain a "Total visits" section', () => {
|
|
||||||
cy.visit(COMMUNITYSTATISTICSPAGE);
|
|
||||||
cy.get('.' + TEST_COMMUNITY + '_TotalVisits').should('exist');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should contain a "Total visits per month" section', () => {
|
|
||||||
cy.visit(COMMUNITYSTATISTICSPAGE);
|
|
||||||
cy.get('.' + TEST_COMMUNITY + '_TotalVisitsPerMonth').should('exist');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
|
||||||
cy.visit(COMMUNITYSTATISTICSPAGE);
|
|
||||||
|
|
||||||
// <ds-community-statistics-page> tag must be loaded
|
|
||||||
cy.get('ds-community-statistics-page').should('exist');
|
|
||||||
|
|
||||||
// Analyze <ds-community-statistics-page> for accessibility issues
|
|
||||||
testA11y('ds-community-statistics-page');
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,19 +0,0 @@
|
|||||||
import { testA11y } from 'cypress/support/utils';
|
|
||||||
|
|
||||||
describe('Site Statistics Page', () => {
|
|
||||||
it('should load if you click on "Statistics" from homepage', () => {
|
|
||||||
cy.visit('/');
|
|
||||||
cy.get('ds-navbar ds-link-menu-item a[title="Statistics"]').click();
|
|
||||||
cy.location('pathname').should('eq', '/statistics');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should pass accessibility tests', () => {
|
|
||||||
cy.visit('/statistics');
|
|
||||||
|
|
||||||
// <ds-site-statistics-page> tag must be loaded
|
|
||||||
cy.get('ds-site-statistics-page').should('exist');
|
|
||||||
|
|
||||||
// Analyze <ds-site-statistics-page> for accessibility issues
|
|
||||||
testA11y('ds-site-statistics-page');
|
|
||||||
});
|
|
||||||
});
|
|
@@ -4,12 +4,17 @@
|
|||||||
// ***********************************************
|
// ***********************************************
|
||||||
|
|
||||||
import { AuthTokenInfo, TOKENITEM } from 'src/app/core/auth/models/auth-token-info.model';
|
import { AuthTokenInfo, TOKENITEM } from 'src/app/core/auth/models/auth-token-info.model';
|
||||||
import { FALLBACK_TEST_REST_BASE_URL } from '.';
|
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';
|
||||||
|
|
||||||
// Declare Cypress namespace to help with Intellisense & code completion in IDEs
|
// Declare Cypress namespace to help with Intellisense & code completion in IDEs
|
||||||
// ALL custom commands MUST be listed here for code completion to work
|
// ALL custom commands MUST be listed here for code completion to work
|
||||||
// tslint:disable-next-line:no-namespace
|
|
||||||
declare global {
|
declare global {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
namespace Cypress {
|
namespace Cypress {
|
||||||
interface Chainable<Subject = any> {
|
interface Chainable<Subject = any> {
|
||||||
/**
|
/**
|
||||||
@@ -27,6 +32,15 @@ declare global {
|
|||||||
* @param password password to login as
|
* @param password password to login as
|
||||||
*/
|
*/
|
||||||
loginViaForm(email: string, password: string): typeof loginViaForm;
|
loginViaForm(email: string, password: string): typeof loginViaForm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate view event for given object. Useful for testing statistics pages with
|
||||||
|
* pre-generated statistics. This just generates a single "hit", but can be called multiple times to
|
||||||
|
* generate multiple hits.
|
||||||
|
* @param uuid UUID of object
|
||||||
|
* @param dsoType type of DSpace Object (e.g. "item", "collection", "community")
|
||||||
|
*/
|
||||||
|
generateViewEvent(uuid: string, dsoType: string): typeof generateViewEvent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,22 +67,27 @@ function login(email: string, password: string): void {
|
|||||||
if (!config.rest.baseUrl) {
|
if (!config.rest.baseUrl) {
|
||||||
console.warn("Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL);
|
console.warn("Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL);
|
||||||
} else {
|
} else {
|
||||||
console.log("Found 'rest.baseUrl' in config.json. Using this REST API for login: " + config.rest.baseUrl);
|
//console.log("Found 'rest.baseUrl' in config.json. Using this REST API for login: ".concat(config.rest.baseUrl));
|
||||||
baseRestUrl = config.rest.baseUrl;
|
baseRestUrl = config.rest.baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// To login via REST, first we have to do a GET to obtain a valid CSRF token
|
// Now find domain of our REST API, again with a fallback.
|
||||||
cy.request( baseRestUrl + '/api/authn/status' )
|
let baseDomain = FALLBACK_TEST_REST_DOMAIN;
|
||||||
.then((response) => {
|
if (!config.rest.host) {
|
||||||
// We should receive a CSRF token returned in a response header
|
console.warn("Could not load 'rest.host' from config.json. Falling back to " + FALLBACK_TEST_REST_DOMAIN);
|
||||||
expect(response.headers).to.have.property('dspace-xsrf-token');
|
} else {
|
||||||
const csrfToken = response.headers['dspace-xsrf-token'];
|
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
|
// Now, send login POST request including that CSRF token
|
||||||
cy.request({
|
cy.request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: baseRestUrl + '/api/authn/login',
|
url: baseRestUrl + '/api/authn/login',
|
||||||
headers: { 'X-XSRF-TOKEN' : csrfToken},
|
headers: { [XSRF_REQUEST_HEADER]: csrfToken},
|
||||||
form: true, // indicates the body should be form urlencoded
|
form: true, // indicates the body should be form urlencoded
|
||||||
body: { user: email, password: password }
|
body: { user: email, password: password }
|
||||||
}).then((resp) => {
|
}).then((resp) => {
|
||||||
@@ -85,20 +104,20 @@ function login(email: string, password: string): void {
|
|||||||
// This ensures the UI will recognize we are logged in on next "visit()"
|
// This ensures the UI will recognize we are logged in on next "visit()"
|
||||||
cy.setCookie(TOKENITEM, JSON.stringify(authinfo));
|
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')
|
// Add as a Cypress command (i.e. assign to 'cy.login')
|
||||||
Cypress.Commands.add('login', login);
|
Cypress.Commands.add('login', login);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login user via displayed login form
|
* Login user via displayed login form
|
||||||
* @param email email to login as
|
* @param email email to login as
|
||||||
* @param password password to login as
|
* @param password password to login as
|
||||||
*/
|
*/
|
||||||
function loginViaForm(email: string, password: string): void {
|
function loginViaForm(email: string, password: string): void {
|
||||||
// Enter email
|
// Enter email
|
||||||
cy.get('ds-log-in [data-test="email"]').type(email);
|
cy.get('ds-log-in [data-test="email"]').type(email);
|
||||||
// Enter password
|
// Enter password
|
||||||
@@ -108,3 +127,68 @@ Cypress.Commands.add('login', login);
|
|||||||
}
|
}
|
||||||
// Add as a Cypress command (i.e. assign to 'cy.loginViaForm')
|
// Add as a Cypress command (i.e. assign to 'cy.loginViaForm')
|
||||||
Cypress.Commands.add('loginViaForm', loginViaForm);
|
Cypress.Commands.add('loginViaForm', loginViaForm);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate statistic view event for given object. Useful for testing statistics pages with
|
||||||
|
* pre-generated statistics. This just generates a single "hit", but can be called multiple times to
|
||||||
|
* generate multiple hits.
|
||||||
|
*
|
||||||
|
* NOTE: This requires that "solr-statistics.autoCommit=false" be set on the DSpace backend
|
||||||
|
* (as it is in our docker-compose-ci.yml used in CI).
|
||||||
|
* Otherwise, by default, new statistical events won't be saved until Solr's autocommit next triggers.
|
||||||
|
* @param uuid UUID of object
|
||||||
|
* @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);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
@@ -30,11 +30,11 @@ beforeEach(() => {
|
|||||||
// For better stability between tests, we visit "about:blank" (i.e. blank page) after each test.
|
// 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.
|
// 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/
|
// Borrowed from: https://glebbahmutov.com/blog/visit-blank-page-between-tests/
|
||||||
afterEach(() => {
|
/*afterEach(() => {
|
||||||
cy.window().then((win) => {
|
cy.window().then((win) => {
|
||||||
win.location.href = 'about:blank';
|
win.location.href = 'about:blank';
|
||||||
});
|
});
|
||||||
});
|
});*/
|
||||||
|
|
||||||
|
|
||||||
// Global constants used in tests
|
// Global constants used in tests
|
||||||
@@ -43,10 +43,6 @@ afterEach(() => {
|
|||||||
// https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
// https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
||||||
// (This is the data set used in our CI environment)
|
// (This is the data set used in our CI environment)
|
||||||
|
|
||||||
// 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 'getBaseRESTUrl()' in commands.ts
|
|
||||||
export const FALLBACK_TEST_REST_BASE_URL = 'http://localhost:8080/server';
|
|
||||||
|
|
||||||
// Admin account used for administrative tests
|
// 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_USER = Cypress.env('DSPACE_TEST_ADMIN_USER') || 'dspacedemo+admin@gmail.com';
|
||||||
export const TEST_ADMIN_PASSWORD = Cypress.env('DSPACE_TEST_ADMIN_PASSWORD') || 'dspace';
|
export const TEST_ADMIN_PASSWORD = Cypress.env('DSPACE_TEST_ADMIN_PASSWORD') || 'dspace';
|
||||||
@@ -61,3 +57,10 @@ export const TEST_SUBMIT_COLLECTION_NAME = Cypress.env('DSPACE_TEST_SUBMIT_COLLE
|
|||||||
export const TEST_SUBMIT_COLLECTION_UUID = Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID') || '9d8334e9-25d3-4a67-9cea-3dffdef80144';
|
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 = Cypress.env('DSPACE_TEST_SUBMIT_USER') || 'dspacedemo+submit@gmail.com';
|
||||||
export const TEST_SUBMIT_USER_PASSWORD = Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD') || 'dspace';
|
export const TEST_SUBMIT_USER_PASSWORD = Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD') || 'dspace';
|
||||||
|
|
||||||
|
|
||||||
|
// USEFUL REGEX for testing
|
||||||
|
|
||||||
|
// Match any string that contains at least one non-space character
|
||||||
|
// Can be used with "contains()" to determine if an element has a non-empty text value
|
||||||
|
export const REGEX_MATCH_NON_EMPTY_TEXT = /^(?!\s*$).+/;
|
@@ -30,6 +30,9 @@ services:
|
|||||||
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
|
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
|
||||||
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
||||||
solr__P__server: http://dspacesolr:8983/solr
|
solr__P__server: http://dspacesolr:8983/solr
|
||||||
|
# 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'
|
||||||
depends_on:
|
depends_on:
|
||||||
- dspacedb
|
- dspacedb
|
||||||
image: dspace/dspace:dspace-7_x-test
|
image: dspace/dspace:dspace-7_x-test
|
||||||
|
@@ -163,8 +163,8 @@
|
|||||||
"compression-webpack-plugin": "^9.2.0",
|
"compression-webpack-plugin": "^9.2.0",
|
||||||
"copy-webpack-plugin": "^6.4.1",
|
"copy-webpack-plugin": "^6.4.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cypress": "9.7.0",
|
"cypress": "12.9.0",
|
||||||
"cypress-axe": "^0.14.0",
|
"cypress-axe": "^1.1.0",
|
||||||
"deep-freeze": "0.0.1",
|
"deep-freeze": "0.0.1",
|
||||||
"eslint": "^8.2.0",
|
"eslint": "^8.2.0",
|
||||||
"eslint-plugin-deprecation": "^1.3.2",
|
"eslint-plugin-deprecation": "^1.3.2",
|
||||||
|
@@ -22,6 +22,7 @@ import { PaginationService } from '../../core/pagination/pagination.service';
|
|||||||
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
||||||
import { APP_CONFIG } from '../../../config/app-config.interface';
|
import { APP_CONFIG } from '../../../config/app-config.interface';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
|
import { SortDirection } from '../../core/cache/models/sort-options.model';
|
||||||
|
|
||||||
describe('BrowseByDatePageComponent', () => {
|
describe('BrowseByDatePageComponent', () => {
|
||||||
let comp: BrowseByDatePageComponent;
|
let comp: BrowseByDatePageComponent;
|
||||||
@@ -49,11 +50,21 @@ describe('BrowseByDatePageComponent', () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const lastItem = Object.assign(new Item(), {
|
||||||
|
id: 'last-item-id',
|
||||||
|
metadata: {
|
||||||
|
'dc.date.issued': [
|
||||||
|
{
|
||||||
|
value: '1960-01-01'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const mockBrowseService = {
|
const mockBrowseService = {
|
||||||
getBrowseEntriesFor: (options: BrowseEntrySearchOptions) => toRemoteData([]),
|
getBrowseEntriesFor: (options: BrowseEntrySearchOptions) => toRemoteData([]),
|
||||||
getBrowseItemsFor: (value: string, options: BrowseEntrySearchOptions) => toRemoteData([firstItem]),
|
getBrowseItemsFor: (value: string, options: BrowseEntrySearchOptions) => toRemoteData([firstItem]),
|
||||||
getFirstItemFor: () => createSuccessfulRemoteDataObject$(firstItem)
|
getFirstItemFor: (definition: string, scope?: string, sortDirection?: SortDirection) => null
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockDsoService = {
|
const mockDsoService = {
|
||||||
@@ -91,9 +102,14 @@ describe('BrowseByDatePageComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(BrowseByDatePageComponent);
|
fixture = TestBed.createComponent(BrowseByDatePageComponent);
|
||||||
|
const browseService = fixture.debugElement.injector.get(BrowseService);
|
||||||
|
spyOn(browseService, 'getFirstItemFor')
|
||||||
|
// ok to expect the default browse as first param since we just need the mock items obtained via sort direction.
|
||||||
|
.withArgs('author', undefined, SortDirection.ASC).and.returnValue(createSuccessfulRemoteDataObject$(firstItem))
|
||||||
|
.withArgs('author', undefined, SortDirection.DESC).and.returnValue(createSuccessfulRemoteDataObject$(lastItem));
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
|
||||||
route = (comp as any).route;
|
route = (comp as any).route;
|
||||||
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should initialize the list of items', () => {
|
it('should initialize the list of items', () => {
|
||||||
@@ -107,6 +123,7 @@ describe('BrowseByDatePageComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should create a list of startsWith options with the current year first', () => {
|
it('should create a list of startsWith options with the current year first', () => {
|
||||||
expect(comp.startsWithOptions[0]).toEqual(new Date().getUTCFullYear());
|
//expect(comp.startsWithOptions[0]).toEqual(new Date().getUTCFullYear());
|
||||||
|
expect(comp.startsWithOptions[0]).toEqual(1960);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
import { ChangeDetectorRef, Component, Inject } from '@angular/core';
|
import { ChangeDetectorRef, Component, Inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
BrowseByMetadataPageComponent,
|
BrowseByMetadataPageComponent,
|
||||||
browseParamsToOptions, getBrowseSearchOptions
|
browseParamsToOptions,
|
||||||
|
getBrowseSearchOptions
|
||||||
} from '../browse-by-metadata-page/browse-by-metadata-page.component';
|
} from '../browse-by-metadata-page/browse-by-metadata-page.component';
|
||||||
import { combineLatest as observableCombineLatest } from 'rxjs';
|
import { combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
|
||||||
import { Item } from '../../core/shared/item.model';
|
|
||||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||||
import { BrowseService } from '../../core/browse/browse.service';
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
@@ -16,7 +15,9 @@ import { map } from 'rxjs/operators';
|
|||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
import { isValidDate } from '../../shared/date.util';
|
import { isValidDate } from '../../shared/date.util';
|
||||||
import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface';
|
import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-browse-by-date-page',
|
selector: 'ds-browse-by-date-page',
|
||||||
@@ -72,30 +73,24 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the StartsWith options
|
* Update the StartsWith options
|
||||||
* In this implementation, it creates a list of years starting from now, going all the way back to the earliest
|
* In this implementation, it creates a list of years starting from the most recent item or the current year, going
|
||||||
* date found on an item within this scope. The further back in time, the bigger the change in years become to avoid
|
* all the way back to the earliest date found on an item within this scope. The further back in time, the bigger
|
||||||
* extremely long lists with a one-year difference.
|
* the change in years become to avoid extremely long lists with a one-year difference.
|
||||||
* To determine the change in years, the config found under GlobalConfig.BrowseBy is used for this.
|
* To determine the change in years, the config found under GlobalConfig.BrowseBy is used for this.
|
||||||
* @param definition The metadata definition to fetch the first item for
|
* @param definition The metadata definition to fetch the first item for
|
||||||
* @param metadataKeys The metadata fields to fetch the earliest date from (expects a date field)
|
* @param metadataKeys The metadata fields to fetch the earliest date from (expects a date field)
|
||||||
* @param scope The scope under which to fetch the earliest item for
|
* @param scope The scope under which to fetch the earliest item for
|
||||||
*/
|
*/
|
||||||
updateStartsWithOptions(definition: string, metadataKeys: string[], scope?: string) {
|
updateStartsWithOptions(definition: string, metadataKeys: string[], scope?: string) {
|
||||||
|
const firstItemRD = this.browseService.getFirstItemFor(definition, scope, SortDirection.ASC);
|
||||||
|
const lastItemRD = this.browseService.getFirstItemFor(definition, scope, SortDirection.DESC);
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
this.browseService.getFirstItemFor(definition, scope).subscribe((firstItemRD: RemoteData<Item>) => {
|
observableCombineLatest([firstItemRD, lastItemRD]).subscribe(([firstItem, lastItem]) => {
|
||||||
let lowerLimit = this.appConfig.browseBy.defaultLowerLimit;
|
let lowerLimit = this.getLimit(firstItem, metadataKeys, this.appConfig.browseBy.defaultLowerLimit);
|
||||||
if (hasValue(firstItemRD.payload)) {
|
let upperLimit = this.getLimit(lastItem, metadataKeys, new Date().getUTCFullYear());
|
||||||
const date = firstItemRD.payload.firstMetadataValue(metadataKeys);
|
|
||||||
if (isNotEmpty(date) && isValidDate(date)) {
|
|
||||||
const dateObj = new Date(date);
|
|
||||||
// TODO: it appears that getFullYear (based on local time) is sometimes unreliable. Switching to UTC.
|
|
||||||
lowerLimit = isNaN(dateObj.getUTCFullYear()) ? lowerLimit : dateObj.getUTCFullYear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const options = [];
|
const options = [];
|
||||||
const currentYear = new Date().getUTCFullYear();
|
const oneYearBreak = Math.floor((upperLimit - this.appConfig.browseBy.oneYearLimit) / 5) * 5;
|
||||||
const oneYearBreak = Math.floor((currentYear - this.appConfig.browseBy.oneYearLimit) / 5) * 5;
|
const fiveYearBreak = Math.floor((upperLimit - this.appConfig.browseBy.fiveYearLimit) / 10) * 10;
|
||||||
const fiveYearBreak = Math.floor((currentYear - this.appConfig.browseBy.fiveYearLimit) / 10) * 10;
|
|
||||||
if (lowerLimit <= fiveYearBreak) {
|
if (lowerLimit <= fiveYearBreak) {
|
||||||
lowerLimit -= 10;
|
lowerLimit -= 10;
|
||||||
} else if (lowerLimit <= oneYearBreak) {
|
} else if (lowerLimit <= oneYearBreak) {
|
||||||
@@ -103,7 +98,7 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
} else {
|
} else {
|
||||||
lowerLimit -= 1;
|
lowerLimit -= 1;
|
||||||
}
|
}
|
||||||
let i = currentYear;
|
let i = upperLimit;
|
||||||
while (i > lowerLimit) {
|
while (i > lowerLimit) {
|
||||||
options.push(i);
|
options.push(i);
|
||||||
if (i <= fiveYearBreak) {
|
if (i <= fiveYearBreak) {
|
||||||
@@ -121,4 +116,24 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the year from the item metadata field or the limit.
|
||||||
|
* @param itemRD the item remote data
|
||||||
|
* @param metadataKeys The metadata fields to fetch the earliest date from (expects a date field)
|
||||||
|
* @param limit the limit to use if the year can't be found in metadata
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private getLimit(itemRD: RemoteData<Item>, metadataKeys: string[], limit: number): number {
|
||||||
|
if (hasValue(itemRD.payload)) {
|
||||||
|
const date = itemRD.payload.firstMetadataValue(metadataKeys);
|
||||||
|
if (isNotEmpty(date) && isValidDate(date)) {
|
||||||
|
const dateObj = new Date(date);
|
||||||
|
// TODO: it appears that getFullYear (based on local time) is sometimes unreliable. Switching to UTC.
|
||||||
|
return isNaN(dateObj.getUTCFullYear()) ? limit : dateObj.getUTCFullYear();
|
||||||
|
} else {
|
||||||
|
return new Date().getUTCFullYear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -154,6 +154,8 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
if (typeof params.value === 'string'){
|
if (typeof params.value === 'string'){
|
||||||
this.value = params.value.trim();
|
this.value = params.value.trim();
|
||||||
|
} else {
|
||||||
|
this.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof params.startsWith === 'string'){
|
if (typeof params.startsWith === 'string'){
|
||||||
|
@@ -28,7 +28,8 @@
|
|||||||
[title]="'toggle ' + node.name"
|
[title]="'toggle ' + node.name"
|
||||||
[attr.aria-label]="'toggle ' + node.name"
|
[attr.aria-label]="'toggle ' + node.name"
|
||||||
(click)="toggleExpanded(node)"
|
(click)="toggleExpanded(node)"
|
||||||
[ngClass]="(hasChild(null, node)| async) ? 'visible' : 'invisible'">
|
[ngClass]="(hasChild(null, node)| async) ? 'visible' : 'invisible'"
|
||||||
|
[attr.data-test]="(hasChild(null, node)| async) ? 'expand-button' : ''">
|
||||||
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
||||||
aria-hidden="true"></span>
|
aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
|
@@ -8,7 +8,7 @@ import { PostRequest } from '../data/request.models';
|
|||||||
import {
|
import {
|
||||||
XSRF_REQUEST_HEADER,
|
XSRF_REQUEST_HEADER,
|
||||||
XSRF_RESPONSE_HEADER
|
XSRF_RESPONSE_HEADER
|
||||||
} from '../xsrf/xsrf.interceptor';
|
} from '../xsrf/xsrf.constants';
|
||||||
|
|
||||||
describe(`ServerAuthRequestService`, () => {
|
describe(`ServerAuthRequestService`, () => {
|
||||||
let href: string;
|
let href: string;
|
||||||
|
@@ -13,7 +13,7 @@ import {
|
|||||||
XSRF_REQUEST_HEADER,
|
XSRF_REQUEST_HEADER,
|
||||||
XSRF_RESPONSE_HEADER,
|
XSRF_RESPONSE_HEADER,
|
||||||
DSPACE_XSRF_COOKIE
|
DSPACE_XSRF_COOKIE
|
||||||
} from '../xsrf/xsrf.interceptor';
|
} from '../xsrf/xsrf.constants';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
@@ -22,6 +22,7 @@ import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
|
|||||||
import { HrefOnlyDataService } from '../data/href-only-data.service';
|
import { HrefOnlyDataService } from '../data/href-only-data.service';
|
||||||
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { BrowseDefinitionDataService } from './browse-definition-data.service';
|
import { BrowseDefinitionDataService } from './browse-definition-data.service';
|
||||||
|
import { SortDirection } from '../cache/models/sort-options.model';
|
||||||
|
|
||||||
|
|
||||||
export const BROWSE_LINKS_TO_FOLLOW: FollowLinkConfig<BrowseEntry | Item>[] = [
|
export const BROWSE_LINKS_TO_FOLLOW: FollowLinkConfig<BrowseEntry | Item>[] = [
|
||||||
@@ -160,8 +161,9 @@ export class BrowseService {
|
|||||||
* Get the first item for a metadata definition in an optional scope
|
* Get the first item for a metadata definition in an optional scope
|
||||||
* @param definition
|
* @param definition
|
||||||
* @param scope
|
* @param scope
|
||||||
|
* @param sortDirection optional sort parameter
|
||||||
*/
|
*/
|
||||||
getFirstItemFor(definition: string, scope?: string): Observable<RemoteData<Item>> {
|
getFirstItemFor(definition: string, scope?: string, sortDirection?: SortDirection): Observable<RemoteData<Item>> {
|
||||||
const href$ = this.getBrowseDefinitions().pipe(
|
const href$ = this.getBrowseDefinitions().pipe(
|
||||||
getBrowseDefinitionLinks(definition),
|
getBrowseDefinitionLinks(definition),
|
||||||
hasValueOperator(),
|
hasValueOperator(),
|
||||||
@@ -177,6 +179,9 @@ export class BrowseService {
|
|||||||
}
|
}
|
||||||
args.push('page=0');
|
args.push('page=0');
|
||||||
args.push('size=1');
|
args.push('size=1');
|
||||||
|
if (sortDirection) {
|
||||||
|
args.push('sort=default,' + sortDirection);
|
||||||
|
}
|
||||||
if (isNotEmpty(args)) {
|
if (isNotEmpty(args)) {
|
||||||
href = new URLCombiner(href, `?${args.join('&')}`).toString();
|
href = new URLCombiner(href, `?${args.join('&')}`).toString();
|
||||||
}
|
}
|
||||||
|
33
src/app/core/xsrf/xsrf.constants.ts
Normal file
33
src/app/core/xsrf/xsrf.constants.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* XSRF / CSRF related constants
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of CSRF/XSRF header we (client) may SEND in requests to backend.
|
||||||
|
* (This is a standard header name for XSRF/CSRF defined by Angular)
|
||||||
|
*/
|
||||||
|
export const XSRF_REQUEST_HEADER = 'X-XSRF-TOKEN';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of CSRF/XSRF header we (client) may RECEIVE in responses from backend
|
||||||
|
* This header is defined by DSpace backend, see https://github.com/DSpace/RestContract/blob/main/csrf-tokens.md
|
||||||
|
*/
|
||||||
|
export const XSRF_RESPONSE_HEADER = 'DSPACE-XSRF-TOKEN';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of client-side Cookie where we store the CSRF/XSRF token between requests.
|
||||||
|
* This cookie is only available to client, and should be updated whenever a new XSRF_RESPONSE_HEADER
|
||||||
|
* is found in a response from the backend.
|
||||||
|
*/
|
||||||
|
export const XSRF_COOKIE = 'XSRF-TOKEN';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of server-side cookie the backend expects the XSRF token to be in.
|
||||||
|
* When the backend receives a modifying request, it will validate the CSRF/XSRF token by looking
|
||||||
|
* for a match between the XSRF_REQUEST_HEADER and this Cookie. For more details see
|
||||||
|
* https://github.com/DSpace/RestContract/blob/main/csrf-tokens.md
|
||||||
|
*
|
||||||
|
* NOTE: This Cookie is NOT readable to the client/UI. It is only readable to the backend and will
|
||||||
|
* be sent along automatically by the user's browser.
|
||||||
|
*/
|
||||||
|
export const DSPACE_XSRF_COOKIE = 'DSPACE-XSRF-COOKIE';
|
@@ -12,15 +12,7 @@ import { Observable, throwError } from 'rxjs';
|
|||||||
import { tap, catchError } from 'rxjs/operators';
|
import { tap, catchError } from 'rxjs/operators';
|
||||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||||
import { CookieService } from '../services/cookie.service';
|
import { CookieService } from '../services/cookie.service';
|
||||||
|
import { XSRF_COOKIE, XSRF_REQUEST_HEADER, XSRF_RESPONSE_HEADER } from './xsrf.constants';
|
||||||
// Name of XSRF header we may send in requests to backend (this is a standard name defined by Angular)
|
|
||||||
export const XSRF_REQUEST_HEADER = 'X-XSRF-TOKEN';
|
|
||||||
// Name of XSRF header we may receive in responses from backend
|
|
||||||
export const XSRF_RESPONSE_HEADER = 'DSPACE-XSRF-TOKEN';
|
|
||||||
// Name of cookie where we store the XSRF token
|
|
||||||
export const XSRF_COOKIE = 'XSRF-TOKEN';
|
|
||||||
// Name of cookie the backend expects the XSRF token to be in
|
|
||||||
export const DSPACE_XSRF_COOKIE = 'DSPACE-XSRF-COOKIE';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom Http Interceptor intercepting Http Requests & Responses to
|
* Custom Http Interceptor intercepting Http Requests & Responses to
|
||||||
|
@@ -1,6 +1,20 @@
|
|||||||
.ngx-gallery {
|
:host ::ng-deep {
|
||||||
display: inline-block;
|
.ngx-gallery {
|
||||||
margin-bottom: 20px;
|
width: unset !important;
|
||||||
width: 340px !important;
|
height: unset !important;
|
||||||
height: 279px !important;
|
}
|
||||||
|
|
||||||
|
ngx-gallery-image {
|
||||||
|
max-width: 340px !important;
|
||||||
|
|
||||||
|
.ngx-gallery-image {
|
||||||
|
background-position: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngx-gallery-image:after {
|
||||||
|
padding-top: 75%;
|
||||||
|
display: block;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,10 @@
|
|||||||
video {
|
video {
|
||||||
width: 340px;
|
width: 100%;
|
||||||
height: 279px;
|
height: auto;
|
||||||
|
max-width: 340px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: .25rem;
|
||||||
}
|
}
|
||||||
|
@@ -20,9 +20,9 @@
|
|||||||
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
|
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
|
||||||
</ds-metadata-field-wrapper>
|
</ds-metadata-field-wrapper>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="mediaViewer.image">
|
<div *ngIf="mediaViewer.image" class="mb-2">
|
||||||
<ds-themed-media-viewer [item]="object" [videoOptions]="mediaViewer.video"></ds-themed-media-viewer>
|
<ds-themed-media-viewer [item]="object" [videoOptions]="mediaViewer.video"></ds-themed-media-viewer>
|
||||||
</ng-container>
|
</div>
|
||||||
<ds-themed-item-page-file-section [item]="object"></ds-themed-item-page-file-section>
|
<ds-themed-item-page-file-section [item]="object"></ds-themed-item-page-file-section>
|
||||||
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
||||||
<ds-themed-metadata-representation-list class="ds-item-page-mixed-author-field"
|
<ds-themed-metadata-representation-list class="ds-item-page-mixed-author-field"
|
||||||
|
@@ -21,9 +21,9 @@
|
|||||||
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
|
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
|
||||||
</ds-metadata-field-wrapper>
|
</ds-metadata-field-wrapper>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="mediaViewer.image">
|
<div *ngIf="mediaViewer.image" class="mb-2">
|
||||||
<ds-themed-media-viewer [item]="object" [videoOptions]="mediaViewer.video"></ds-themed-media-viewer>
|
<ds-themed-media-viewer [item]="object" [videoOptions]="mediaViewer.video"></ds-themed-media-viewer>
|
||||||
</ng-container>
|
</div>
|
||||||
<ds-themed-item-page-file-section [item]="object"></ds-themed-item-page-file-section>
|
<ds-themed-item-page-file-section [item]="object"></ds-themed-item-page-file-section>
|
||||||
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
||||||
<ds-themed-metadata-representation-list class="ds-item-page-mixed-author-field"
|
<ds-themed-metadata-representation-list class="ds-item-page-mixed-author-field"
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
<ng-container #componentViewContainer></ng-container>
|
<ng-container #componentViewContainer></ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<small *ngIf="hasHint && ((!model.repeatable && (isRelationship === false || value?.value === null)) || (model.repeatable === true && context?.index === context?.context?.groups?.length - 1)) && (!showErrorMessages || errorMessages.length === 0)"
|
<small *ngIf="hasHint && (formBuilderService.hasArrayGroupValue(model) || (!model.repeatable && (isRelationship === false || value?.value === null)) || (model.repeatable === true && context?.index === context?.context?.groups?.length - 1)) && (!showErrorMessages || errorMessages.length === 0)"
|
||||||
class="text-muted ds-hint" [innerHTML]="model.hint | translate" [ngClass]="getClass('element', 'hint')"></small>
|
class="text-muted ds-hint" [innerHTML]="model.hint | translate" [ngClass]="getClass('element', 'hint')"></small>
|
||||||
<!-- In case of repeatable fields show empty space for all elements except the first -->
|
<!-- In case of repeatable fields show empty space for all elements except the first -->
|
||||||
<div *ngIf="context?.index !== null
|
<div *ngIf="context?.index !== null
|
||||||
|
@@ -147,12 +147,14 @@ describe('DsDynamicFormControlContainerComponent test suite', () => {
|
|||||||
new DynamicListCheckboxGroupModel({
|
new DynamicListCheckboxGroupModel({
|
||||||
id: 'checkboxList',
|
id: 'checkboxList',
|
||||||
vocabularyOptions: vocabularyOptions,
|
vocabularyOptions: vocabularyOptions,
|
||||||
repeatable: true
|
repeatable: true,
|
||||||
|
required: false,
|
||||||
}),
|
}),
|
||||||
new DynamicListRadioGroupModel({
|
new DynamicListRadioGroupModel({
|
||||||
id: 'radioList',
|
id: 'radioList',
|
||||||
vocabularyOptions: vocabularyOptions,
|
vocabularyOptions: vocabularyOptions,
|
||||||
repeatable: false
|
repeatable: false,
|
||||||
|
required: false,
|
||||||
}),
|
}),
|
||||||
new DynamicRelationGroupModel({
|
new DynamicRelationGroupModel({
|
||||||
submissionId: '1234',
|
submissionId: '1234',
|
||||||
|
@@ -259,7 +259,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
private submissionObjectService: SubmissionObjectDataService,
|
private submissionObjectService: SubmissionObjectDataService,
|
||||||
private ref: ChangeDetectorRef,
|
private ref: ChangeDetectorRef,
|
||||||
private formService: FormService,
|
private formService: FormService,
|
||||||
private formBuilderService: FormBuilderService,
|
public formBuilderService: FormBuilderService,
|
||||||
private submissionService: SubmissionService,
|
private submissionService: SubmissionService,
|
||||||
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
) {
|
) {
|
||||||
|
@@ -15,8 +15,10 @@ export interface DynamicListCheckboxGroupModelConfig extends DynamicFormGroupMod
|
|||||||
vocabularyOptions: VocabularyOptions;
|
vocabularyOptions: VocabularyOptions;
|
||||||
groupLength?: number;
|
groupLength?: number;
|
||||||
repeatable: boolean;
|
repeatable: boolean;
|
||||||
value?: any;
|
value?: VocabularyEntry[];
|
||||||
typeBindRelations?: DynamicFormControlRelation[];
|
typeBindRelations?: DynamicFormControlRelation[];
|
||||||
|
required: boolean;
|
||||||
|
hint?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DynamicListCheckboxGroupModel extends DynamicCheckboxGroupModel {
|
export class DynamicListCheckboxGroupModel extends DynamicCheckboxGroupModel {
|
||||||
@@ -26,6 +28,8 @@ export class DynamicListCheckboxGroupModel extends DynamicCheckboxGroupModel {
|
|||||||
@serializable() groupLength: number;
|
@serializable() groupLength: number;
|
||||||
@serializable() _value: VocabularyEntry[];
|
@serializable() _value: VocabularyEntry[];
|
||||||
@serializable() typeBindRelations: DynamicFormControlRelation[];
|
@serializable() typeBindRelations: DynamicFormControlRelation[];
|
||||||
|
@serializable() required: boolean;
|
||||||
|
@serializable() hint: string;
|
||||||
isListGroup = true;
|
isListGroup = true;
|
||||||
valueUpdates: Subject<any>;
|
valueUpdates: Subject<any>;
|
||||||
|
|
||||||
@@ -36,6 +40,8 @@ export class DynamicListCheckboxGroupModel extends DynamicCheckboxGroupModel {
|
|||||||
this.groupLength = config.groupLength || 5;
|
this.groupLength = config.groupLength || 5;
|
||||||
this._value = [];
|
this._value = [];
|
||||||
this.repeatable = config.repeatable;
|
this.repeatable = config.repeatable;
|
||||||
|
this.required = config.required;
|
||||||
|
this.hint = config.hint;
|
||||||
|
|
||||||
this.valueUpdates = new Subject<any>();
|
this.valueUpdates = new Subject<any>();
|
||||||
this.valueUpdates.subscribe((value: VocabularyEntry | VocabularyEntry[]) => this.value = value);
|
this.valueUpdates.subscribe((value: VocabularyEntry | VocabularyEntry[]) => this.value = value);
|
||||||
@@ -56,9 +62,8 @@ export class DynamicListCheckboxGroupModel extends DynamicCheckboxGroupModel {
|
|||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
this._value = value;
|
this._value = value;
|
||||||
} else {
|
} else {
|
||||||
// _value is non extendible so assign it a new array
|
// _value is non-extendable so assign it a new array
|
||||||
const newValue = (this.value as VocabularyEntry[]).concat([value]);
|
this._value = (this.value as VocabularyEntry[]).concat([value]);
|
||||||
this._value = newValue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,12 +6,15 @@ import {
|
|||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
import { VocabularyOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
import { VocabularyOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||||
import { hasValue } from '../../../../../empty.util';
|
import { hasValue } from '../../../../../empty.util';
|
||||||
|
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||||
|
|
||||||
export interface DynamicListModelConfig extends DynamicRadioGroupModelConfig<any> {
|
export interface DynamicListModelConfig extends DynamicRadioGroupModelConfig<any> {
|
||||||
vocabularyOptions: VocabularyOptions;
|
vocabularyOptions: VocabularyOptions;
|
||||||
groupLength?: number;
|
groupLength?: number;
|
||||||
repeatable: boolean;
|
repeatable: boolean;
|
||||||
value?: any;
|
value?: VocabularyEntry[];
|
||||||
|
required: boolean;
|
||||||
|
hint?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DynamicListRadioGroupModel extends DynamicRadioGroupModel<any> {
|
export class DynamicListRadioGroupModel extends DynamicRadioGroupModel<any> {
|
||||||
@@ -19,6 +22,8 @@ export class DynamicListRadioGroupModel extends DynamicRadioGroupModel<any> {
|
|||||||
@serializable() vocabularyOptions: VocabularyOptions;
|
@serializable() vocabularyOptions: VocabularyOptions;
|
||||||
@serializable() repeatable: boolean;
|
@serializable() repeatable: boolean;
|
||||||
@serializable() groupLength: number;
|
@serializable() groupLength: number;
|
||||||
|
@serializable() required: boolean;
|
||||||
|
@serializable() hint: string;
|
||||||
isListGroup = true;
|
isListGroup = true;
|
||||||
|
|
||||||
constructor(config: DynamicListModelConfig, layout?: DynamicFormControlLayout) {
|
constructor(config: DynamicListModelConfig, layout?: DynamicFormControlLayout) {
|
||||||
@@ -27,6 +32,8 @@ export class DynamicListRadioGroupModel extends DynamicRadioGroupModel<any> {
|
|||||||
this.vocabularyOptions = config.vocabularyOptions;
|
this.vocabularyOptions = config.vocabularyOptions;
|
||||||
this.groupLength = config.groupLength || 5;
|
this.groupLength = config.groupLength || 5;
|
||||||
this.repeatable = config.repeatable;
|
this.repeatable = config.repeatable;
|
||||||
|
this.required = config.required;
|
||||||
|
this.hint = config.hint;
|
||||||
this.value = config.value;
|
this.value = config.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,7 +17,6 @@
|
|||||||
[id]="item.id"
|
[id]="item.id"
|
||||||
[formControlName]="item.id"
|
[formControlName]="item.id"
|
||||||
[name]="model.name"
|
[name]="model.name"
|
||||||
[required]="model.required"
|
|
||||||
[value]="item.value"
|
[value]="item.value"
|
||||||
(blur)="onBlur($event)"
|
(blur)="onBlur($event)"
|
||||||
(change)="onChange($event)"
|
(change)="onChange($event)"
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
import { FormGroup } from '@angular/forms';
|
import { FormGroup, ValidatorFn, ValidationErrors, AbstractControl } from '@angular/forms';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DynamicCheckboxModel,
|
DynamicCheckboxModel,
|
||||||
DynamicFormControlComponent,
|
DynamicFormControlComponent,
|
||||||
@@ -110,6 +109,9 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
|
|||||||
protected setOptionsFromVocabulary() {
|
protected setOptionsFromVocabulary() {
|
||||||
if (this.model.vocabularyOptions.name && this.model.vocabularyOptions.name.length > 0) {
|
if (this.model.vocabularyOptions.name && this.model.vocabularyOptions.name.length > 0) {
|
||||||
const listGroup = this.group.controls[this.model.id] as FormGroup;
|
const listGroup = this.group.controls[this.model.id] as FormGroup;
|
||||||
|
if (this.model.repeatable && this.model.required) {
|
||||||
|
listGroup.addValidators(this.hasAtLeastOneVocabularyEntry());
|
||||||
|
}
|
||||||
const pageInfo: PageInfo = new PageInfo({
|
const pageInfo: PageInfo = new PageInfo({
|
||||||
elementsPerPage: 9999, currentPage: 1
|
elementsPerPage: 9999, currentPage: 1
|
||||||
} as PageInfo);
|
} as PageInfo);
|
||||||
@@ -121,7 +123,7 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
|
|||||||
let tempList: ListItem[] = [];
|
let tempList: ListItem[] = [];
|
||||||
this.optionsList = entries.page;
|
this.optionsList = entries.page;
|
||||||
// Make a list of available options (checkbox/radio) and split in groups of 'model.groupLength'
|
// Make a list of available options (checkbox/radio) and split in groups of 'model.groupLength'
|
||||||
entries.page.forEach((option, key) => {
|
entries.page.forEach((option: VocabularyEntry, key: number) => {
|
||||||
const value = option.authority || option.value;
|
const value = option.authority || option.value;
|
||||||
const checked: boolean = isNotEmpty(findKey(
|
const checked: boolean = isNotEmpty(findKey(
|
||||||
this.model.value,
|
this.model.value,
|
||||||
@@ -156,4 +158,13 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if at least one {@link VocabularyEntry} has been selected.
|
||||||
|
*/
|
||||||
|
hasAtLeastOneVocabularyEntry(): ValidatorFn {
|
||||||
|
return (control: AbstractControl): ValidationErrors | null => {
|
||||||
|
return control && control.value && Object.values(control.value).find((checked: boolean) => checked === true) ? null : this.model.errorMessages;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -235,10 +235,16 @@ describe('FormBuilderService test suite', () => {
|
|||||||
new DynamicListCheckboxGroupModel({
|
new DynamicListCheckboxGroupModel({
|
||||||
id: 'testCheckboxList',
|
id: 'testCheckboxList',
|
||||||
vocabularyOptions: vocabularyOptions,
|
vocabularyOptions: vocabularyOptions,
|
||||||
repeatable: true
|
repeatable: true,
|
||||||
|
required: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new DynamicListRadioGroupModel({ id: 'testRadioList', vocabularyOptions: vocabularyOptions, repeatable: false }),
|
new DynamicListRadioGroupModel({
|
||||||
|
id: 'testRadioList',
|
||||||
|
vocabularyOptions: vocabularyOptions,
|
||||||
|
repeatable: false,
|
||||||
|
required: false,
|
||||||
|
}),
|
||||||
|
|
||||||
new DynamicRelationGroupModel({
|
new DynamicRelationGroupModel({
|
||||||
submissionId,
|
submissionId,
|
||||||
|
@@ -9,7 +9,7 @@ import { UploaderOptions } from './uploader-options.model';
|
|||||||
import { hasValue, isNotEmpty, isUndefined } from '../../empty.util';
|
import { hasValue, isNotEmpty, isUndefined } from '../../empty.util';
|
||||||
import { UploaderProperties } from './uploader-properties.model';
|
import { UploaderProperties } from './uploader-properties.model';
|
||||||
import { HttpXsrfTokenExtractor } from '@angular/common/http';
|
import { HttpXsrfTokenExtractor } from '@angular/common/http';
|
||||||
import { XSRF_COOKIE, XSRF_REQUEST_HEADER, XSRF_RESPONSE_HEADER } from '../../../core/xsrf/xsrf.interceptor';
|
import { XSRF_COOKIE, XSRF_REQUEST_HEADER, XSRF_RESPONSE_HEADER } from '../../../core/xsrf/xsrf.constants';
|
||||||
import { CookieService } from '../../../core/services/cookie.service';
|
import { CookieService } from '../../../core/services/cookie.service';
|
||||||
import { DragService } from '../../../core/drag.service';
|
import { DragService } from '../../../core/drag.service';
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
{{ 'statistics.table.title.' + report.reportType | translate }}
|
{{ 'statistics.table.title.' + report.reportType | translate }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<table class="table table-striped">
|
<table class="table table-striped" [attr.data-test]="report.reportType">
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
<tr *ngFor="let point of report.points"
|
<tr *ngFor="let point of report.points"
|
||||||
class="{{point.id}}-data">
|
class="{{point.id}}-data">
|
||||||
<th scope="row">
|
<th scope="row" data-test="statistics-label">
|
||||||
{{ getLabel(point) | async }}
|
{{ getLabel(point) | async }}
|
||||||
</th>
|
</th>
|
||||||
<td *ngFor="let header of headers"
|
<td *ngFor="let header of headers"
|
||||||
|
@@ -3,8 +3,10 @@ import { Point, UsageReport } from '../../core/statistics/models/usage-report.mo
|
|||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../core/shared/operators';
|
import { getRemoteDataPayload, getFinishedRemoteData } from '../../core/shared/operators';
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { isEmpty } from '../../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component representing a statistics table for a given usage report.
|
* Component representing a statistics table for a given usage report.
|
||||||
@@ -35,6 +37,7 @@ export class StatisticsTableComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
protected dsoService: DSpaceObjectDataService,
|
protected dsoService: DSpaceObjectDataService,
|
||||||
protected nameService: DSONameService,
|
protected nameService: DSONameService,
|
||||||
|
private translateService: TranslateService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -54,9 +57,9 @@ export class StatisticsTableComponent implements OnInit {
|
|||||||
switch (this.report.reportType) {
|
switch (this.report.reportType) {
|
||||||
case 'TotalVisits':
|
case 'TotalVisits':
|
||||||
return this.dsoService.findById(point.id).pipe(
|
return this.dsoService.findById(point.id).pipe(
|
||||||
getFirstSucceededRemoteData(),
|
getFinishedRemoteData(),
|
||||||
getRemoteDataPayload(),
|
getRemoteDataPayload(),
|
||||||
map((item) => this.nameService.getName(item)),
|
map((item) => !isEmpty(item) ? this.nameService.getName(item) : this.translateService.instant('statistics.table.no-name')),
|
||||||
);
|
);
|
||||||
case 'TopCities':
|
case 'TopCities':
|
||||||
case 'topCountries':
|
case 'topCountries':
|
||||||
|
@@ -5,5 +5,6 @@
|
|||||||
[submissionDefinition]="submissionDefinition"
|
[submissionDefinition]="submissionDefinition"
|
||||||
[submissionErrors]="submissionErrors"
|
[submissionErrors]="submissionErrors"
|
||||||
[item]="item"
|
[item]="item"
|
||||||
|
[collectionModifiable]="collectionModifiable"
|
||||||
[submissionId]="submissionId"></ds-submission-form>
|
[submissionId]="submissionId"></ds-submission-form>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -36,6 +36,13 @@ export class SubmissionEditComponent implements OnDestroy, OnInit {
|
|||||||
*/
|
*/
|
||||||
public collectionId: string;
|
public collectionId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the collection can be modifiable by the user
|
||||||
|
* @type {booelan}
|
||||||
|
*/
|
||||||
|
public collectionModifiable: boolean | null = null;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of submission's sections
|
* The list of submission's sections
|
||||||
* @type {WorkspaceitemSectionsObject}
|
* @type {WorkspaceitemSectionsObject}
|
||||||
@@ -109,6 +116,9 @@ export class SubmissionEditComponent implements OnDestroy, OnInit {
|
|||||||
* Retrieve workspaceitem/workflowitem from server and initialize all instance variables
|
* Retrieve workspaceitem/workflowitem from server and initialize all instance variables
|
||||||
*/
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
||||||
|
this.collectionModifiable = this.route.snapshot.data?.collectionModifiable ?? null;
|
||||||
|
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
this.route.paramMap.pipe(
|
this.route.paramMap.pipe(
|
||||||
switchMap((params: ParamMap) => this.submissionService.retrieveSubmission(params.get('id'))),
|
switchMap((params: ParamMap) => this.submissionService.retrieveSubmission(params.get('id'))),
|
||||||
|
@@ -25,7 +25,7 @@
|
|||||||
class="btn btn-outline-primary"
|
class="btn btn-outline-primary"
|
||||||
(blur)="onClose()"
|
(blur)="onClose()"
|
||||||
(click)="onClose()"
|
(click)="onClose()"
|
||||||
[disabled]="(processingChange$ | async)"
|
[disabled]="(processingChange$ | async) || collectionModifiable == false"
|
||||||
ngbDropdownToggle>
|
ngbDropdownToggle>
|
||||||
<span *ngIf="(processingChange$ | async)"><i class='fas fa-circle-notch fa-spin'></i></span>
|
<span *ngIf="(processingChange$ | async)"><i class='fas fa-circle-notch fa-spin'></i></span>
|
||||||
<span *ngIf="!(processingChange$ | async)">{{ selectedCollectionName$ | async }}</span>
|
<span *ngIf="!(processingChange$ | async)">{{ selectedCollectionName$ | async }}</span>
|
||||||
|
@@ -52,6 +52,12 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit {
|
|||||||
*/
|
*/
|
||||||
@Input() currentDefinition: string;
|
@Input() currentDefinition: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the collection can be modifiable by the user
|
||||||
|
* @type {booelan}
|
||||||
|
*/
|
||||||
|
@Input() collectionModifiable: boolean | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The submission id
|
* The submission id
|
||||||
* @type {string}
|
* @type {string}
|
||||||
|
@@ -11,6 +11,7 @@
|
|||||||
<ds-submission-form-collection [currentCollectionId]="collectionId"
|
<ds-submission-form-collection [currentCollectionId]="collectionId"
|
||||||
[currentDefinition]="definitionId"
|
[currentDefinition]="definitionId"
|
||||||
[submissionId]="submissionId"
|
[submissionId]="submissionId"
|
||||||
|
[collectionModifiable]="collectionModifiable"
|
||||||
(collectionChange)="onCollectionChange($event)">
|
(collectionChange)="onCollectionChange($event)">
|
||||||
</ds-submission-form-collection>
|
</ds-submission-form-collection>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -34,8 +34,16 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
@Input() collectionId: string;
|
@Input() collectionId: string;
|
||||||
|
|
||||||
@Input() item: Item;
|
@Input() item: Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the collection can be modifiable by the user
|
||||||
|
* @type {booelan}
|
||||||
|
*/
|
||||||
|
@Input() collectionModifiable: boolean | null = null;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of submission's sections
|
* The list of submission's sections
|
||||||
* @type {WorkspaceitemSectionsObject}
|
* @type {WorkspaceitemSectionsObject}
|
||||||
|
@@ -34,7 +34,11 @@ import {
|
|||||||
resolve: {
|
resolve: {
|
||||||
breadcrumb: I18nBreadcrumbResolver
|
breadcrumb: I18nBreadcrumbResolver
|
||||||
},
|
},
|
||||||
data: { title: 'workflow-item.edit.title', breadcrumbKey: 'workflow-item.edit' }
|
data: {
|
||||||
|
title: 'workflow-item.edit.title',
|
||||||
|
breadcrumbKey: 'workflow-item.edit',
|
||||||
|
collectionModifiable: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
canActivate: [AuthenticatedGuard],
|
canActivate: [AuthenticatedGuard],
|
||||||
|
@@ -2881,7 +2881,7 @@
|
|||||||
|
|
||||||
"itemtemplate.edit.metadata.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button",
|
"itemtemplate.edit.metadata.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button",
|
||||||
|
|
||||||
"itemtemplate.edit.metadata.notifications.discarded.title": "Changed discarded",
|
"itemtemplate.edit.metadata.notifications.discarded.title": "Changes discarded",
|
||||||
|
|
||||||
"itemtemplate.edit.metadata.notifications.error.title": "An error occurred",
|
"itemtemplate.edit.metadata.notifications.error.title": "An error occurred",
|
||||||
|
|
||||||
@@ -2891,7 +2891,7 @@
|
|||||||
|
|
||||||
"itemtemplate.edit.metadata.notifications.outdated.content": "The item template you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts",
|
"itemtemplate.edit.metadata.notifications.outdated.content": "The item template you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts",
|
||||||
|
|
||||||
"itemtemplate.edit.metadata.notifications.outdated.title": "Changed outdated",
|
"itemtemplate.edit.metadata.notifications.outdated.title": "Changes outdated",
|
||||||
|
|
||||||
"itemtemplate.edit.metadata.notifications.saved.content": "Your changes to this item template's metadata were saved.",
|
"itemtemplate.edit.metadata.notifications.saved.content": "Your changes to this item template's metadata were saved.",
|
||||||
|
|
||||||
@@ -4244,6 +4244,8 @@
|
|||||||
|
|
||||||
"statistics.table.header.views": "Views",
|
"statistics.table.header.views": "Views",
|
||||||
|
|
||||||
|
"statistics.table.no-name": "(object name could not be loaded)",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"submission.edit.breadcrumbs": "Edit Submission",
|
"submission.edit.breadcrumbs": "Edit Submission",
|
||||||
|
@@ -156,7 +156,8 @@
|
|||||||
|
|
||||||
// "admin.registries.bitstream-formats.table.name": "Name",
|
// "admin.registries.bitstream-formats.table.name": "Name",
|
||||||
"admin.registries.bitstream-formats.table.name": "Nimi",
|
"admin.registries.bitstream-formats.table.name": "Nimi",
|
||||||
// TODO New key - Add a translation
|
|
||||||
|
// "admin.registries.bitstream-formats.table.id" : "ID",
|
||||||
"admin.registries.bitstream-formats.table.id" : "ID",
|
"admin.registries.bitstream-formats.table.id" : "ID",
|
||||||
|
|
||||||
// "admin.registries.bitstream-formats.table.return": "Return",
|
// "admin.registries.bitstream-formats.table.return": "Return",
|
||||||
@@ -207,7 +208,7 @@
|
|||||||
"admin.registries.metadata.schemas.table.delete": "Poista valittu",
|
"admin.registries.metadata.schemas.table.delete": "Poista valittu",
|
||||||
|
|
||||||
// "admin.registries.metadata.schemas.table.id": "ID",
|
// "admin.registries.metadata.schemas.table.id": "ID",
|
||||||
"admin.registries.metadata.schemas.table.id": "ID-tunnus",
|
"admin.registries.metadata.schemas.table.id": "ID",
|
||||||
|
|
||||||
// "admin.registries.metadata.schemas.table.name": "Name",
|
// "admin.registries.metadata.schemas.table.name": "Name",
|
||||||
"admin.registries.metadata.schemas.table.name": "Nimi",
|
"admin.registries.metadata.schemas.table.name": "Nimi",
|
||||||
@@ -237,7 +238,8 @@
|
|||||||
|
|
||||||
// "admin.registries.schema.fields.table.field": "Field",
|
// "admin.registries.schema.fields.table.field": "Field",
|
||||||
"admin.registries.schema.fields.table.field": "Kenttä",
|
"admin.registries.schema.fields.table.field": "Kenttä",
|
||||||
// TODO New key - Add a translation
|
|
||||||
|
// "admin.registries.schema.fields.table.id" : "ID",
|
||||||
"admin.registries.schema.fields.table.id" : "ID",
|
"admin.registries.schema.fields.table.id" : "ID",
|
||||||
|
|
||||||
// "admin.registries.schema.fields.table.scopenote": "Scope Note",
|
// "admin.registries.schema.fields.table.scopenote": "Scope Note",
|
||||||
@@ -408,7 +410,7 @@
|
|||||||
"admin.access-control.epeople.form.groupsEPersonIsMemberOf": "Jäsenenä näissä ryhmissä:",
|
"admin.access-control.epeople.form.groupsEPersonIsMemberOf": "Jäsenenä näissä ryhmissä:",
|
||||||
|
|
||||||
// "admin.access-control.epeople.form.table.id": "ID",
|
// "admin.access-control.epeople.form.table.id": "ID",
|
||||||
"admin.access-control.epeople.form.table.id": "ID-tunnus",
|
"admin.access-control.epeople.form.table.id": "ID",
|
||||||
|
|
||||||
// "admin.access-control.epeople.form.table.name": "Name",
|
// "admin.access-control.epeople.form.table.name": "Name",
|
||||||
"admin.access-control.epeople.form.table.name": "Nimi",
|
"admin.access-control.epeople.form.table.name": "Nimi",
|
||||||
@@ -2898,7 +2900,7 @@
|
|||||||
"item.edit.tabs.status.buttons.reinstate.label": "Palauta tietue arkistoon",
|
"item.edit.tabs.status.buttons.reinstate.label": "Palauta tietue arkistoon",
|
||||||
|
|
||||||
// "item.edit.tabs.status.buttons.withdraw.button": "Withdraw this item",
|
// "item.edit.tabs.status.buttons.withdraw.button": "Withdraw this item",
|
||||||
"item.edit.tabs.status.buttons.withdraw.button": "poista tämä kohde",
|
"item.edit.tabs.status.buttons.withdraw.button": "Poista tämä kohde",
|
||||||
|
|
||||||
// "item.edit.tabs.status.buttons.withdraw.label": "Withdraw item from the repository",
|
// "item.edit.tabs.status.buttons.withdraw.label": "Withdraw item from the repository",
|
||||||
"item.edit.tabs.status.buttons.withdraw.label": "Poista tietue käytöstä",
|
"item.edit.tabs.status.buttons.withdraw.label": "Poista tietue käytöstä",
|
||||||
@@ -2913,7 +2915,7 @@
|
|||||||
"item.edit.tabs.status.labels.handle": "Handle-tunnus",
|
"item.edit.tabs.status.labels.handle": "Handle-tunnus",
|
||||||
|
|
||||||
// "item.edit.tabs.status.labels.id": "Item Internal ID",
|
// "item.edit.tabs.status.labels.id": "Item Internal ID",
|
||||||
"item.edit.tabs.status.labels.id": "Tietueen sisäinen ID-tunnus",
|
"item.edit.tabs.status.labels.id": "Tietueen sisäinen ID",
|
||||||
|
|
||||||
// "item.edit.tabs.status.labels.itemPage": "Item Page",
|
// "item.edit.tabs.status.labels.itemPage": "Item Page",
|
||||||
"item.edit.tabs.status.labels.itemPage": "Tietueen tiedot",
|
"item.edit.tabs.status.labels.itemPage": "Tietueen tiedot",
|
||||||
@@ -3773,7 +3775,7 @@
|
|||||||
"orgunit.page.edit": "Muokkaa tietuetta",
|
"orgunit.page.edit": "Muokkaa tietuetta",
|
||||||
|
|
||||||
// "orgunit.page.id": "ID",
|
// "orgunit.page.id": "ID",
|
||||||
"orgunit.page.id": "ID-tunnus",
|
"orgunit.page.id": "ID",
|
||||||
|
|
||||||
// "orgunit.page.titleprefix": "Organizational Unit: ",
|
// "orgunit.page.titleprefix": "Organizational Unit: ",
|
||||||
"orgunit.page.titleprefix": "Organisaatioyksikkö: ",
|
"orgunit.page.titleprefix": "Organisaatioyksikkö: ",
|
||||||
@@ -3825,7 +3827,7 @@
|
|||||||
"person.page.orcid": "ORCID-tunniste",
|
"person.page.orcid": "ORCID-tunniste",
|
||||||
|
|
||||||
// "person.page.staffid": "Staff ID",
|
// "person.page.staffid": "Staff ID",
|
||||||
"person.page.staffid": "Henkilökunnan ID-tunnus",
|
"person.page.staffid": "Henkilökunnan ID",
|
||||||
|
|
||||||
// "person.page.titleprefix": "Person: ",
|
// "person.page.titleprefix": "Person: ",
|
||||||
"person.page.titleprefix": "Käyttäjä: ",
|
"person.page.titleprefix": "Käyttäjä: ",
|
||||||
@@ -4077,7 +4079,7 @@
|
|||||||
"project.page.funder": "Rahoittajat",
|
"project.page.funder": "Rahoittajat",
|
||||||
|
|
||||||
// "project.page.id": "ID",
|
// "project.page.id": "ID",
|
||||||
"project.page.id": "ID-tunnus",
|
"project.page.id": "ID",
|
||||||
|
|
||||||
// "project.page.keyword": "Keywords",
|
// "project.page.keyword": "Keywords",
|
||||||
"project.page.keyword": "Asiasanat",
|
"project.page.keyword": "Asiasanat",
|
||||||
@@ -4361,7 +4363,7 @@
|
|||||||
"resource-policies.form.eperson-group-list.table.headers.action": "Toimenpide",
|
"resource-policies.form.eperson-group-list.table.headers.action": "Toimenpide",
|
||||||
|
|
||||||
// "resource-policies.form.eperson-group-list.table.headers.id": "ID",
|
// "resource-policies.form.eperson-group-list.table.headers.id": "ID",
|
||||||
"resource-policies.form.eperson-group-list.table.headers.id": "ID-tunnus",
|
"resource-policies.form.eperson-group-list.table.headers.id": "ID",
|
||||||
|
|
||||||
// "resource-policies.form.eperson-group-list.table.headers.name": "Name",
|
// "resource-policies.form.eperson-group-list.table.headers.name": "Name",
|
||||||
"resource-policies.form.eperson-group-list.table.headers.name": "Nimi",
|
"resource-policies.form.eperson-group-list.table.headers.name": "Nimi",
|
||||||
@@ -4409,7 +4411,7 @@
|
|||||||
"resource-policies.table.headers.group": "Ryhmä",
|
"resource-policies.table.headers.group": "Ryhmä",
|
||||||
|
|
||||||
// "resource-policies.table.headers.id": "ID",
|
// "resource-policies.table.headers.id": "ID",
|
||||||
"resource-policies.table.headers.id": "ID-tunnus",
|
"resource-policies.table.headers.id": "ID",
|
||||||
|
|
||||||
// "resource-policies.table.headers.name": "Name",
|
// "resource-policies.table.headers.name": "Name",
|
||||||
"resource-policies.table.headers.name": "Nimi",
|
"resource-policies.table.headers.name": "Nimi",
|
||||||
@@ -5336,8 +5338,8 @@
|
|||||||
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
|
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
|
||||||
"submission.sections.upload.header.policy.default.withlist": "Yksittäisten tiedostojen pääsyrajoitusten lisäksi {{collectionName}}-kokoelmaan ladatut tiedostot ovat seuraavien ryhmien saatavilla:",
|
"submission.sections.upload.header.policy.default.withlist": "Yksittäisten tiedostojen pääsyrajoitusten lisäksi {{collectionName}}-kokoelmaan ladatut tiedostot ovat seuraavien ryhmien saatavilla:",
|
||||||
|
|
||||||
// "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or <strong>upload additional files by dragging & dropping them anywhere on the page.</strong>",
|
// "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or <strong>upload additional files just dragging & dropping them everywhere in the page</strong>",
|
||||||
"submission.sections.upload.info": "Tietueen kaikki tiedostot on lueteltu tässä. Voit päivittää tiedoston metadataa ja pääsyehtoja tai <strong>ladata lisää tiedostoja raahaamalla ne mihin hyvänsä sivun kohtaan</strong>",
|
"submission.sections.upload.info": "Tietueen kaikki tiedostot on lueteltu tässä. Voit päivittää tiedoston metadataa ja pääsyehtoja tai <strong>ladata lisää tiedostoja raahaamalla ne mihin hyvänsä sivun kohtaan.</strong>",
|
||||||
|
|
||||||
// "submission.sections.upload.no-entry": "No",
|
// "submission.sections.upload.no-entry": "No",
|
||||||
"submission.sections.upload.no-entry": "Ei",
|
"submission.sections.upload.no-entry": "Ei",
|
||||||
@@ -5488,8 +5490,7 @@
|
|||||||
"uploader.or": " tai",
|
"uploader.or": " tai",
|
||||||
|
|
||||||
// "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)",
|
// "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)",
|
||||||
// TODO Source message changed - Revise the translation
|
"uploader.processing": "Käsitellään ladattuja tiedostoja... (voit sulkea tämän sivun)",
|
||||||
"uploader.processing": "Käsitellään",
|
|
||||||
|
|
||||||
// "uploader.queue-length": "Queue length",
|
// "uploader.queue-length": "Queue length",
|
||||||
"uploader.queue-length": "Jonon pituus",
|
"uploader.queue-length": "Jonon pituus",
|
||||||
|
@@ -1246,6 +1246,9 @@
|
|||||||
// "bitstream-request-a-copy.submit.error": "Something went wrong with submitting the item request.",
|
// "bitstream-request-a-copy.submit.error": "Something went wrong with submitting the item request.",
|
||||||
"bitstream-request-a-copy.submit.error": "Un problème est survenu lors de la soumission de la demande d'Item",
|
"bitstream-request-a-copy.submit.error": "Un problème est survenu lors de la soumission de la demande d'Item",
|
||||||
|
|
||||||
|
//"browse.back.all-results": "All browse results",
|
||||||
|
"browse.back.all-results": "Tous les résultats",
|
||||||
|
|
||||||
// "browse.comcol.by.author": "By Author",
|
// "browse.comcol.by.author": "By Author",
|
||||||
"browse.comcol.by.author": "Auteur",
|
"browse.comcol.by.author": "Auteur",
|
||||||
|
|
||||||
@@ -1363,9 +1366,18 @@
|
|||||||
// "browse.title": "Browsing {{ collection }} by {{ field }} {{ value }}",
|
// "browse.title": "Browsing {{ collection }} by {{ field }} {{ value }}",
|
||||||
"browse.title": "Parcourir la collection {{ collection }} par {{ field }} {{ value }}",
|
"browse.title": "Parcourir la collection {{ collection }} par {{ field }} {{ value }}",
|
||||||
|
|
||||||
|
//"browse.title.page": "Browsing {{ collection }} by {{ field }} {{ value }}",
|
||||||
|
"browse.title.page": "Parcourir la collection {{ collection }} par {{ field }} {{ value }}",
|
||||||
|
|
||||||
|
//"search.browse.item-back": "Back to Results",
|
||||||
|
"search.browse.item-back": "Retour aux résultats",
|
||||||
|
|
||||||
// "chips.remove": "Remove chip",
|
// "chips.remove": "Remove chip",
|
||||||
"chips.remove": "Supprimer fragment",
|
"chips.remove": "Supprimer fragment",
|
||||||
|
|
||||||
|
//"claimed-approved-search-result-list-element.title": "Approved",
|
||||||
|
"claimed-approved-search-result-list-element.title": "Approuvé",
|
||||||
|
|
||||||
// "collection.create.head": "Create a Collection",
|
// "collection.create.head": "Create a Collection",
|
||||||
"collection.create.head": "Créer une collection",
|
"collection.create.head": "Créer une collection",
|
||||||
|
|
||||||
@@ -2013,6 +2025,9 @@
|
|||||||
// "cookies.consent.decline": "Decline",
|
// "cookies.consent.decline": "Decline",
|
||||||
"cookies.consent.decline": "Refuser",
|
"cookies.consent.decline": "Refuser",
|
||||||
|
|
||||||
|
//"cookies.consent.ok": "That's ok",
|
||||||
|
"cookies.consent.ok": "Accepter",
|
||||||
|
|
||||||
// "cookies.consent.content-notice.description": "We collect and process your personal information for the following purposes: <strong>Authentication, Preferences, Acknowledgement and Statistics</strong>. <br/> To learn more, please read our {privacyPolicy}.",
|
// "cookies.consent.content-notice.description": "We collect and process your personal information for the following purposes: <strong>Authentication, Preferences, Acknowledgement and Statistics</strong>. <br/> To learn more, please read our {privacyPolicy}.",
|
||||||
"cookies.consent.content-notice.description": "Vos données personnelles sont récupérées et utilisées dans les contextes suivants : <strong>authentification, préférences, consentement et statistiques</strong>. <br/> Pour plus d'informations, veuillez vous référer à la {privacyPolicy}.",
|
"cookies.consent.content-notice.description": "Vos données personnelles sont récupérées et utilisées dans les contextes suivants : <strong>authentification, préférences, consentement et statistiques</strong>. <br/> Pour plus d'informations, veuillez vous référer à la {privacyPolicy}.",
|
||||||
|
|
||||||
@@ -2178,6 +2193,9 @@
|
|||||||
// "dso-selector.set-scope.community.button": "Search all of DSpace",
|
// "dso-selector.set-scope.community.button": "Search all of DSpace",
|
||||||
"dso-selector.set-scope.community.button": "Chercher dans toutes les collections",
|
"dso-selector.set-scope.community.button": "Chercher dans toutes les collections",
|
||||||
|
|
||||||
|
//"dso-selector.set-scope.community.or-divider": "or",
|
||||||
|
"dso-selector.set-scope.community.or-divider": "ou",
|
||||||
|
|
||||||
// "dso-selector.set-scope.community.input-header": "Search for a community or collection",
|
// "dso-selector.set-scope.community.input-header": "Search for a community or collection",
|
||||||
"dso-selector.set-scope.community.input-header": "Chercher une communauté ou une collection",
|
"dso-selector.set-scope.community.input-header": "Chercher une communauté ou une collection",
|
||||||
|
|
||||||
@@ -2790,6 +2808,71 @@
|
|||||||
// "item.edit.tabs.item-mapper.title": "Item Edit - Collection Mapper",
|
// "item.edit.tabs.item-mapper.title": "Item Edit - Collection Mapper",
|
||||||
"item.edit.tabs.item-mapper.title": "Édition d'Item - Association de collection",
|
"item.edit.tabs.item-mapper.title": "Édition d'Item - Association de collection",
|
||||||
|
|
||||||
|
// "item.edit.identifiers.doi.status.UNKNOWN": "Unknown",
|
||||||
|
"item.edit.identifiers.doi.status.UNKNOWN": "Inconnu",
|
||||||
|
|
||||||
|
// "item.edit.identifiers.doi.status.TO_BE_REGISTERED": "Queued for registration",
|
||||||
|
"item.edit.identifiers.doi.status.TO_BE_REGISTERED": "En attente d'inscription",
|
||||||
|
|
||||||
|
// "item.edit.identifiers.doi.status.TO_BE_RESERVED": "Queued for reservation",
|
||||||
|
"item.edit.identifiers.doi.status.TO_BE_RESERVED": "En attente de réservation",
|
||||||
|
|
||||||
|
// "item.edit.identifiers.doi.status.IS_REGISTERED": "Registered",
|
||||||
|
"item.edit.identifiers.doi.status.IS_REGISTERED": "Inscrit",
|
||||||
|
|
||||||
|
// "item.edit.identifiers.doi.status.IS_RESERVED": "Reserved",
|
||||||
|
"item.edit.identifiers.doi.status.IS_RESERVED": "Réservé",
|
||||||
|
|
||||||
|
// "item.edit.identifiers.doi.status.UPDATE_RESERVED": "Reserved (update queued)",
|
||||||
|
"item.edit.identifiers.doi.status.UPDATE_RESERVED": "Réservé (en attente)",
|
||||||
|
|
||||||
|
// "item.edit.identifiers.doi.status.UPDATE_REGISTERED": "Registered (update queued)",
|
||||||
|
"item.edit.identifiers.doi.status.UPDATE_REGISTERED": "Inscrit (en attente)",
|
||||||
|
|
||||||
|
// "item.edit.identifiers.doi.status.UPDATE_BEFORE_REGISTRATION": "Queued for update and registration",
|
||||||
|
"item.edit.identifiers.doi.status.UPDATE_BEFORE_REGISTRATION": "En attente pour la mise à jour et l'inscription",
|
||||||
|
|
||||||
|
// "item.edit.identifiers.doi.status.TO_BE_DELETED": "Queued for deletion",
|
||||||
|
"item.edit.identifiers.doi.status.TO_BE_DELETED": "En attente pour la suppression",
|
||||||
|
|
||||||
|
// "item.edit.identifiers.doi.status.DELETED": "Deleted",
|
||||||
|
"item.edit.identifiers.doi.status.DELETED": "Supprimé",
|
||||||
|
|
||||||
|
// "item.edit.identifiers.doi.status.PENDING": "Pending (not registered)",
|
||||||
|
"item.edit.identifiers.doi.status.PENDING": "En attente (non inscrit)",
|
||||||
|
|
||||||
|
// "item.edit.identifiers.doi.status.MINTED": "Minted (not registered)",
|
||||||
|
"item.edit.identifiers.doi.status.MINTED": "Émis (non inscrit)",
|
||||||
|
|
||||||
|
// "item.edit.tabs.status.buttons.register-doi.label": "Register a new or pending DOI",
|
||||||
|
"item.edit.tabs.status.buttons.register-doi.label": "Inscrire un nouveau DOI ou un DOI en attente",
|
||||||
|
|
||||||
|
// "item.edit.tabs.status.buttons.register-doi.button": "Register DOI...",
|
||||||
|
"item.edit.tabs.status.buttons.register-doi.button": "Inscrire le DOI...",
|
||||||
|
|
||||||
|
// "item.edit.register-doi.header": "Register a new or pending DOI",
|
||||||
|
"item.edit.register-doi.header": "Inscrire un nouveau DOI ou un DOI en attente",
|
||||||
|
|
||||||
|
// "item.edit.register-doi.description": "Review any pending identifiers and item metadata below and click Confirm to proceed with DOI registration, or Cancel to back out",
|
||||||
|
"item.edit.register-doi.description": "Réviser les identifiants en attente et les métadonnées ci-dessous puis cliquer sur Confirmer afin de lancer l'inscription du DOI ou sur Annuler pour interrompre l'inscription",
|
||||||
|
|
||||||
|
// "item.edit.register-doi.confirm": "Confirm",
|
||||||
|
"item.edit.register-doi.confirm": "Confirmer",
|
||||||
|
|
||||||
|
// "item.edit.register-doi.cancel": "Cancel",
|
||||||
|
"item.edit.register-doi.cancel": "Annuler",
|
||||||
|
|
||||||
|
// "item.edit.register-doi.success": "DOI queued for registration successfully.",
|
||||||
|
"item.edit.register-doi.success": "DOI mis en attente pour l'inscription.",
|
||||||
|
|
||||||
|
// "item.edit.register-doi.error": "Error registering DOI",
|
||||||
|
"item.edit.register-doi.error": "Erreur lors de l'inscription du DOI",
|
||||||
|
|
||||||
|
// "item.edit.register-doi.to-update": "The following DOI has already been minted and will be queued for registration online",
|
||||||
|
"item.edit.register-doi.to-update": "Le DOI suivant a déjà été généré et sera mis en attente pour être inscrit",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// "item.edit.item-mapper.buttons.add": "Map item to selected collections",
|
// "item.edit.item-mapper.buttons.add": "Map item to selected collections",
|
||||||
"item.edit.item-mapper.buttons.add": "Associer l'Item aux collections sélectionnées",
|
"item.edit.item-mapper.buttons.add": "Associer l'Item aux collections sélectionnées",
|
||||||
|
|
||||||
@@ -4048,6 +4131,9 @@
|
|||||||
// "mydspace.show.workspace": "Your Submissions",
|
// "mydspace.show.workspace": "Your Submissions",
|
||||||
"mydspace.show.workspace": "Vos dépôts",
|
"mydspace.show.workspace": "Vos dépôts",
|
||||||
|
|
||||||
|
//"mydspace.show.supervisedWorkspace": "Supervised items",
|
||||||
|
"mydspace.show.supervisedWorkspace": "Items supervisés",
|
||||||
|
|
||||||
// "mydspace.status.archived": "Archived",
|
// "mydspace.status.archived": "Archived",
|
||||||
"mydspace.status.archived": "Archivés",
|
"mydspace.status.archived": "Archivés",
|
||||||
|
|
||||||
@@ -4114,6 +4200,9 @@
|
|||||||
// "nav.stop-impersonating": "Stop impersonating EPerson",
|
// "nav.stop-impersonating": "Stop impersonating EPerson",
|
||||||
"nav.stop-impersonating": "Retour à votre propre EPerson",
|
"nav.stop-impersonating": "Retour à votre propre EPerson",
|
||||||
|
|
||||||
|
//"nav.subscriptions" : "Subscriptions",
|
||||||
|
"nav.subscriptions" : "Abonnements",
|
||||||
|
|
||||||
// "nav.toggle": "Toggle navigation",
|
// "nav.toggle": "Toggle navigation",
|
||||||
"nav.toggle": "Basculer la navigation",
|
"nav.toggle": "Basculer la navigation",
|
||||||
|
|
||||||
@@ -5370,6 +5459,9 @@
|
|||||||
// "submission.import-external.source.crossref": "CrossRef",
|
// "submission.import-external.source.crossref": "CrossRef",
|
||||||
"submission.import-external.source.crossref": "CrossRef (DOI)",
|
"submission.import-external.source.crossref": "CrossRef (DOI)",
|
||||||
|
|
||||||
|
//"submission.import-external.source.datacite": "DataCite",
|
||||||
|
"submission.import-external.source.datacite": "DataCite (DOI)",
|
||||||
|
|
||||||
// "submission.import-external.source.scielo": "SciELO",
|
// "submission.import-external.source.scielo": "SciELO",
|
||||||
"submission.import-external.source.scielo": "SciELO",
|
"submission.import-external.source.scielo": "SciELO",
|
||||||
|
|
||||||
@@ -5385,6 +5477,9 @@
|
|||||||
// "submission.import-external.source.orcidWorks": "ORCID",
|
// "submission.import-external.source.orcidWorks": "ORCID",
|
||||||
"submission.import-external.source.orcidWorks": "ORCID",
|
"submission.import-external.source.orcidWorks": "ORCID",
|
||||||
|
|
||||||
|
//"submission.import-external.source.epo": "European Patent Office (EPO)",
|
||||||
|
"submission.import-external.source.epo": "Office Européen des brevets (OEB)",
|
||||||
|
|
||||||
// "submission.import-external.source.loading": "Loading ...",
|
// "submission.import-external.source.loading": "Loading ...",
|
||||||
"submission.import-external.source.loading": "En cours de chargement ...",
|
"submission.import-external.source.loading": "En cours de chargement ...",
|
||||||
|
|
||||||
@@ -6300,8 +6395,15 @@
|
|||||||
// "virtual-metadata.delete-relationship.modal-head": "Select the items for which you want to save the virtual metadata as real metadata",
|
// "virtual-metadata.delete-relationship.modal-head": "Select the items for which you want to save the virtual metadata as real metadata",
|
||||||
"virtual-metadata.delete-relationship.modal-head": "Sélectionner les Items pour lesquels vous souhaitez sauvegarder les métadonnées virtuelles en tant que métadonnées réelles",
|
"virtual-metadata.delete-relationship.modal-head": "Sélectionner les Items pour lesquels vous souhaitez sauvegarder les métadonnées virtuelles en tant que métadonnées réelles",
|
||||||
|
|
||||||
|
|
||||||
|
//"supervisedWorkspace.search.results.head": "Supervised Items",
|
||||||
|
"supervisedWorkspace.search.results.head": "Documents supervisés",
|
||||||
|
|
||||||
|
//"workspace.search.results.head": "Your submissions",
|
||||||
|
"workspace.search.results.head": "Vos dépôts",
|
||||||
|
|
||||||
// "workspace.search.results.head": "Your submissions",
|
// "workspace.search.results.head": "Your submissions",
|
||||||
"workspace.search.results.head": "Vos soumissions",
|
"workspace.search.results.head": "Vos dépôts",
|
||||||
|
|
||||||
// "workflowAdmin.search.results.head": "Administer Workflow",
|
// "workflowAdmin.search.results.head": "Administer Workflow",
|
||||||
"workflowAdmin.search.results.head": "Workflow Administrateur",
|
"workflowAdmin.search.results.head": "Workflow Administrateur",
|
||||||
@@ -6309,6 +6411,9 @@
|
|||||||
// "workflow.search.results.head": "Workflow tasks",
|
// "workflow.search.results.head": "Workflow tasks",
|
||||||
"workflow.search.results.head": "Tableau de suivi",
|
"workflow.search.results.head": "Tableau de suivi",
|
||||||
|
|
||||||
|
//"supervision.search.results.head": "Workflow and Workspace tasks",
|
||||||
|
"supervision.search.results.head": "Tableau de suivi et dépôts en cours",
|
||||||
|
|
||||||
// "workflow-item.edit.breadcrumbs": "Edit workflowitem",
|
// "workflow-item.edit.breadcrumbs": "Edit workflowitem",
|
||||||
"workflow-item.edit.breadcrumbs": "Éditer l'Item du Workflow",
|
"workflow-item.edit.breadcrumbs": "Éditer l'Item du Workflow",
|
||||||
|
|
||||||
@@ -6381,7 +6486,83 @@
|
|||||||
// "idle-modal.log-out": "Log out",
|
// "idle-modal.log-out": "Log out",
|
||||||
"idle-modal.log-out": "Déconnexion",
|
"idle-modal.log-out": "Déconnexion",
|
||||||
|
|
||||||
// "idle-modal.extend-session": "Extend session"
|
// "idle-modal.extend-session": "Extend session",
|
||||||
"idle-modal.extend-session": "Prolonger la session"
|
"idle-modal.extend-session": "Prolonger la session",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// "system-wide-alert-banner.retrieval.error": "Something went wrong retrieving the system-wide alert banner",
|
||||||
|
"system-wide-alert-banner.retrieval.error": "Une erreur s'est produite lors de la récupération de la bannière du message d'avertissement",
|
||||||
|
|
||||||
|
//"system-wide-alert-banner.countdown.prefix": "In",
|
||||||
|
"system-wide-alert-banner.countdown.prefix": "Dans",
|
||||||
|
|
||||||
|
// "system-wide-alert-banner.countdown.days": "{{days}} day(s),",
|
||||||
|
"system-wide-alert-banner.countdown.days": "{{days}} jour(s),",
|
||||||
|
|
||||||
|
// "system-wide-alert-banner.countdown.hours": "{{hours}} hour(s) and",
|
||||||
|
"system-wide-alert-banner.countdown.hours": "{{hours}} heure(s) et",
|
||||||
|
|
||||||
|
// "system-wide-alert-banner.countdown.minutes": "{{minutes}} minute(s):",
|
||||||
|
"system-wide-alert-banner.countdown.minutes": "{{minutes}} minute(s):",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// "menu.section.system-wide-alert": "System-wide Alert",
|
||||||
|
"menu.section.system-wide-alert": "Messages d'avertissement",
|
||||||
|
|
||||||
|
// "system-wide-alert.form.header": "System-wide Alert",
|
||||||
|
"system-wide-alert.form.header": "Messages d'avertissement",
|
||||||
|
|
||||||
|
// "system-wide-alert-form.retrieval.error": "Something went wrong retrieving the system-wide alert",
|
||||||
|
"system-wide-alert-form.retrieval.error": "Une erreur s'est produite lors de la récupération de la bannière du message d'avertissement",
|
||||||
|
|
||||||
|
//"system-wide-alert.form.cancel": "Cancel",
|
||||||
|
"system-wide-alert.form.cancel": "Annuler",
|
||||||
|
|
||||||
|
|
||||||
|
//"system-wide-alert.form.save": "Save",
|
||||||
|
"system-wide-alert.form.save": "Sauvegarder",
|
||||||
|
|
||||||
|
//"system-wide-alert.form.label.active": "ACTIVE",
|
||||||
|
"system-wide-alert.form.label.active": "ACTIF",
|
||||||
|
|
||||||
|
//"system-wide-alert.form.label.inactive": "INACTIVE",
|
||||||
|
"system-wide-alert.form.label.inactive": "INACTIF",
|
||||||
|
|
||||||
|
//"system-wide-alert.form.error.message": "The system wide alert must have a message",
|
||||||
|
"system-wide-alert.form.error.message": "Le message d'avertissement ne peut pas être vide",
|
||||||
|
|
||||||
|
//"system-wide-alert.form.label.message": "Alert message",
|
||||||
|
"system-wide-alert.form.label.message": "Message d'avertissement",
|
||||||
|
|
||||||
|
//"system-wide-alert.form.label.countdownTo.enable": "Enable a countdown timer",
|
||||||
|
"system-wide-alert.form.label.countdownTo.enable": "Activer un compte à rebours",
|
||||||
|
|
||||||
|
//"system-wide-alert.form.label.countdownTo.hint": "Hint: Set a countdown timer. When enabled, a date can be set in the future and the system-wide alert banner will perform a countdown to the set date. When this timer ends, it will disappear from the alert. The server will NOT be automatically stopped.",
|
||||||
|
"system-wide-alert.form.label.countdownTo.hint": "Lorsque cette option est activée, il est possible de fixer une date dans le futur et la bannière du message d'avertissement effectuera un compte à rebours jusqu'à la date fixée. À la fin du compte à rebours, le message d'avertissement disparaîtra mais le serveur ne sera pas arrêté automatiquement.",
|
||||||
|
|
||||||
|
//"system-wide-alert.form.label.preview": "System-wide alert preview",
|
||||||
|
"system-wide-alert.form.label.preview": "Aperçu du message d'avertissement",
|
||||||
|
|
||||||
|
//"system-wide-alert.form.update.success": "The system-wide alert was successfully updated",
|
||||||
|
"system-wide-alert.form.update.success": "Le message d'avertissement a été mis à jour",
|
||||||
|
|
||||||
|
//"system-wide-alert.form.update.error": "Something went wrong when updating the system-wide alert",
|
||||||
|
"system-wide-alert.form.update.error": "Un erreur s'est produite lors de la mise à jour du message d'avertissement",
|
||||||
|
|
||||||
|
//"system-wide-alert.form.create.success": "The system-wide alert was successfully created",
|
||||||
|
"system-wide-alert.form.create.success": "Le message d'avertissement a été crée",
|
||||||
|
|
||||||
|
//"system-wide-alert.form.create.error": "Something went wrong when creating the system-wide alert",
|
||||||
|
"system-wide-alert.form.create.error": "Un erreur s'est produite lors de la création du message d'avertissement",
|
||||||
|
|
||||||
|
//"admin.system-wide-alert.breadcrumbs": "System-wide Alerts",
|
||||||
|
"admin.system-wide-alert.breadcrumbs": "Messages d'avertissement",
|
||||||
|
|
||||||
|
//"admin.system-wide-alert.title": "System-wide Alerts",
|
||||||
|
"admin.system-wide-alert.title": "Messages d'avertissement",
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
98
yarn.lock
98
yarn.lock
@@ -1479,9 +1479,9 @@
|
|||||||
integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==
|
integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==
|
||||||
|
|
||||||
"@cypress/request@^2.88.10":
|
"@cypress/request@^2.88.10":
|
||||||
version "2.88.10"
|
version "2.88.11"
|
||||||
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce"
|
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.11.tgz#5a4c7399bc2d7e7ed56e92ce5acb620c8b187047"
|
||||||
integrity sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg==
|
integrity sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w==
|
||||||
dependencies:
|
dependencies:
|
||||||
aws-sign2 "~0.7.0"
|
aws-sign2 "~0.7.0"
|
||||||
aws4 "^1.8.0"
|
aws4 "^1.8.0"
|
||||||
@@ -1496,7 +1496,7 @@
|
|||||||
json-stringify-safe "~5.0.1"
|
json-stringify-safe "~5.0.1"
|
||||||
mime-types "~2.1.19"
|
mime-types "~2.1.19"
|
||||||
performance-now "^2.1.0"
|
performance-now "^2.1.0"
|
||||||
qs "~6.5.2"
|
qs "~6.10.3"
|
||||||
safe-buffer "^5.1.2"
|
safe-buffer "^5.1.2"
|
||||||
tough-cookie "~2.5.0"
|
tough-cookie "~2.5.0"
|
||||||
tunnel-agent "^0.6.0"
|
tunnel-agent "^0.6.0"
|
||||||
@@ -2287,12 +2287,22 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
|
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
|
||||||
integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==
|
integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==
|
||||||
|
|
||||||
"@types/node@*", "@types/node@>=10.0.0":
|
"@types/node@*":
|
||||||
|
version "18.14.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.2.tgz#c076ed1d7b6095078ad3cf21dfeea951842778b1"
|
||||||
|
integrity sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==
|
||||||
|
|
||||||
|
"@types/node@>=10.0.0":
|
||||||
version "18.11.11"
|
version "18.11.11"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.11.tgz#1d455ac0211549a8409d3cdb371cd55cc971e8dc"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.11.tgz#1d455ac0211549a8409d3cdb371cd55cc971e8dc"
|
||||||
integrity sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g==
|
integrity sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g==
|
||||||
|
|
||||||
"@types/node@^14.14.31", "@types/node@^14.14.9":
|
"@types/node@^14.14.31":
|
||||||
|
version "14.18.36"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.36.tgz#c414052cb9d43fab67d679d5f3c641be911f5835"
|
||||||
|
integrity sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==
|
||||||
|
|
||||||
|
"@types/node@^14.14.9":
|
||||||
version "14.18.34"
|
version "14.18.34"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.34.tgz#cd2e6fa0dbfb08a62582a7b967558e73c32061ec"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.34.tgz#cd2e6fa0dbfb08a62582a7b967558e73c32061ec"
|
||||||
integrity sha512-hcU9AIQVHmPnmjRK+XUUYlILlr9pQrsqSrwov/JK1pnf3GTQowVBhx54FbvM0AU/VXGH4i3+vgXS5EguR7fysA==
|
integrity sha512-hcU9AIQVHmPnmjRK+XUUYlILlr9pQrsqSrwov/JK1pnf3GTQowVBhx54FbvM0AU/VXGH4i3+vgXS5EguR7fysA==
|
||||||
@@ -3144,14 +3154,14 @@ aws-sign2@~0.7.0:
|
|||||||
integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==
|
integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==
|
||||||
|
|
||||||
aws4@^1.8.0:
|
aws4@^1.8.0:
|
||||||
version "1.11.0"
|
version "1.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3"
|
||||||
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
|
integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==
|
||||||
|
|
||||||
axe-core@^4.4.3:
|
axe-core@^4.4.3:
|
||||||
version "4.5.2"
|
version "4.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.5.2.tgz#823fdf491ff717ac3c58a52631d4206930c1d9f7"
|
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.6.3.tgz#fc0db6fdb65cc7a80ccf85286d91d64ababa3ece"
|
||||||
integrity sha512-u2MVsXfew5HBvjsczCv+xlwdNnB1oQR9HlAcsejZttNjKKSkeDNVwB1vMThIUIFI9GoT57Vtk8iQLwqOfAkboA==
|
integrity sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==
|
||||||
|
|
||||||
axios@0.21.4:
|
axios@0.21.4:
|
||||||
version "0.21.4"
|
version "0.21.4"
|
||||||
@@ -3692,9 +3702,9 @@ chrome-trace-event@^1.0.2:
|
|||||||
integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
|
integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
|
||||||
|
|
||||||
ci-info@^3.2.0:
|
ci-info@^3.2.0:
|
||||||
version "3.7.0"
|
version "3.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.7.0.tgz#6d01b3696c59915b6ce057e4aa4adfc2fa25f5ef"
|
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91"
|
||||||
integrity sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog==
|
integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==
|
||||||
|
|
||||||
circular-dependency-plugin@5.2.2:
|
circular-dependency-plugin@5.2.2:
|
||||||
version "5.2.2"
|
version "5.2.2"
|
||||||
@@ -4255,15 +4265,15 @@ custom-event@~1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
|
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
|
||||||
integrity sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==
|
integrity sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==
|
||||||
|
|
||||||
cypress-axe@^0.14.0:
|
cypress-axe@^1.1.0:
|
||||||
version "0.14.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-0.14.0.tgz#5f5e70fb36b8cb3ba73a8ba01e9262ff1268d5e2"
|
resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-1.4.0.tgz#e67482bfe9e740796bf77c7823f19781a8a2faff"
|
||||||
integrity sha512-7Rdjnko0MjggCmndc1wECAkvQBIhuy+DRtjF7bd5YPZRFvubfMNvrxfqD8PWQmxm7MZE0ffS4Xr43V6ZmvLopg==
|
integrity sha512-Ut7NKfzjyKm0BEbt2WxuKtLkIXmx6FD2j0RwdvO/Ykl7GmB/qRQkwbKLk3VP35+83hiIr8GKD04PDdrTK5BnyA==
|
||||||
|
|
||||||
cypress@9.7.0:
|
cypress@12.9.0:
|
||||||
version "9.7.0"
|
version "12.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.7.0.tgz#bf55b2afd481f7a113ef5604aa8b693564b5e744"
|
resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.9.0.tgz#e6ab43cf329fd7c821ef7645517649d72ccf0a12"
|
||||||
integrity sha512-+1EE1nuuuwIt/N1KXRR2iWHU+OiIt7H28jJDyyI4tiUftId/DrXYEwoDa5+kH2pki1zxnA0r6HrUGHV5eLbF5Q==
|
integrity sha512-Ofe09LbHKgSqX89Iy1xen2WvpgbvNxDzsWx3mgU1mfILouELeXYGwIib3ItCwoRrRifoQwcBFmY54Vs0zw7QCg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@cypress/request" "^2.88.10"
|
"@cypress/request" "^2.88.10"
|
||||||
"@cypress/xvfb" "^1.2.4"
|
"@cypress/xvfb" "^1.2.4"
|
||||||
@@ -4282,9 +4292,9 @@ cypress@9.7.0:
|
|||||||
commander "^5.1.0"
|
commander "^5.1.0"
|
||||||
common-tags "^1.8.0"
|
common-tags "^1.8.0"
|
||||||
dayjs "^1.10.4"
|
dayjs "^1.10.4"
|
||||||
debug "^4.3.2"
|
debug "^4.3.4"
|
||||||
enquirer "^2.3.6"
|
enquirer "^2.3.6"
|
||||||
eventemitter2 "^6.4.3"
|
eventemitter2 "6.4.7"
|
||||||
execa "4.1.0"
|
execa "4.1.0"
|
||||||
executable "^4.1.1"
|
executable "^4.1.1"
|
||||||
extract-zip "2.0.1"
|
extract-zip "2.0.1"
|
||||||
@@ -5292,10 +5302,10 @@ eventemitter-asyncresource@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz#734ff2e44bf448e627f7748f905d6bdd57bdb65b"
|
resolved "https://registry.yarnpkg.com/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz#734ff2e44bf448e627f7748f905d6bdd57bdb65b"
|
||||||
integrity sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==
|
integrity sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==
|
||||||
|
|
||||||
eventemitter2@^6.4.3:
|
eventemitter2@6.4.7:
|
||||||
version "6.4.9"
|
version "6.4.7"
|
||||||
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.9.tgz#41f2750781b4230ed58827bc119d293471ecb125"
|
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d"
|
||||||
integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==
|
integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==
|
||||||
|
|
||||||
eventemitter3@^4.0.0:
|
eventemitter3@^4.0.0:
|
||||||
version "4.0.7"
|
version "4.0.7"
|
||||||
@@ -9227,11 +9237,16 @@ pump@^3.0.0:
|
|||||||
end-of-stream "^1.1.0"
|
end-of-stream "^1.1.0"
|
||||||
once "^1.3.1"
|
once "^1.3.1"
|
||||||
|
|
||||||
punycode@^2.1.0, punycode@^2.1.1:
|
punycode@^2.1.0:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||||
|
|
||||||
|
punycode@^2.1.1:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
|
||||||
|
integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
|
||||||
|
|
||||||
q@^1.4.1:
|
q@^1.4.1:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||||
@@ -9254,6 +9269,13 @@ qs@6.2.3:
|
|||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.3.tgz#1cfcb25c10a9b2b483053ff39f5dfc9233908cfe"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.3.tgz#1cfcb25c10a9b2b483053ff39f5dfc9233908cfe"
|
||||||
integrity sha512-AY4g8t3LMboim0t6XWFdz6J5OuJ1ZNYu54SXihS/OMpgyCqYmcAJnWqkNSOjSjWmq3xxy+GF9uWQI2lI/7tKIA==
|
integrity sha512-AY4g8t3LMboim0t6XWFdz6J5OuJ1ZNYu54SXihS/OMpgyCqYmcAJnWqkNSOjSjWmq3xxy+GF9uWQI2lI/7tKIA==
|
||||||
|
|
||||||
|
qs@~6.10.3:
|
||||||
|
version "6.10.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.5.tgz#974715920a80ff6a262264acd2c7e6c2a53282b4"
|
||||||
|
integrity sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ==
|
||||||
|
dependencies:
|
||||||
|
side-channel "^1.0.4"
|
||||||
|
|
||||||
qs@~6.5.2:
|
qs@~6.5.2:
|
||||||
version "6.5.3"
|
version "6.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
|
||||||
@@ -9878,13 +9900,20 @@ rxjs@^5.5.6:
|
|||||||
dependencies:
|
dependencies:
|
||||||
symbol-observable "1.0.1"
|
symbol-observable "1.0.1"
|
||||||
|
|
||||||
rxjs@^7.2.0, rxjs@^7.5.1, rxjs@^7.5.5:
|
rxjs@^7.2.0, rxjs@^7.5.5:
|
||||||
version "7.6.0"
|
version "7.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.6.0.tgz#361da5362b6ddaa691a2de0b4f2d32028f1eb5a2"
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.6.0.tgz#361da5362b6ddaa691a2de0b4f2d32028f1eb5a2"
|
||||||
integrity sha512-DDa7d8TFNUalGC9VqXvQ1euWNN7sc63TrUCuM9J998+ViviahMIjKSOU7rfcgFOF+FCD71BhDRv4hrFz+ImDLQ==
|
integrity sha512-DDa7d8TFNUalGC9VqXvQ1euWNN7sc63TrUCuM9J998+ViviahMIjKSOU7rfcgFOF+FCD71BhDRv4hrFz+ImDLQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^2.1.0"
|
tslib "^2.1.0"
|
||||||
|
|
||||||
|
rxjs@^7.5.1:
|
||||||
|
version "7.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4"
|
||||||
|
integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.1.0"
|
||||||
|
|
||||||
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
@@ -10945,11 +10974,16 @@ tslib@^1.8.1, tslib@^1.9.0:
|
|||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||||
|
|
||||||
tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0:
|
tslib@^2.0.0, tslib@^2.0.3, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
|
||||||
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
|
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
|
||||||
|
|
||||||
|
tslib@^2.1.0:
|
||||||
|
version "2.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
|
||||||
|
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
|
||||||
|
|
||||||
tsutils@^3.21.0:
|
tsutils@^3.21.0:
|
||||||
version "3.21.0"
|
version "3.21.0"
|
||||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
|
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
|
||||||
|
Reference in New Issue
Block a user