Merge branch 'master' into resolvers-branch-angular6

Conflicts:
	package.json
	src/app/+search-page/search-filters/search-filters.component.ts
	src/app/core/auth/auth.effects.ts
	src/app/core/auth/auth.service.ts
	src/app/core/auth/server-auth.service.ts
	src/app/core/data/comcol-data.service.ts
	src/app/core/data/community-data.service.ts
	src/app/core/data/data.service.ts
	src/app/core/data/item-data.service.ts
	src/app/core/shared/dspace-object.model.ts
	src/app/header/header.component.spec.ts
	src/app/shared/auth-nav-menu/auth-nav-menu.component.ts
	src/app/shared/testing/auth-service-stub.ts
	yarn.lock
This commit is contained in:
lotte
2018-10-08 12:28:08 +02:00
78 changed files with 2011 additions and 265 deletions

View File

@@ -89,11 +89,11 @@
"angular2-text-mask": "9.0.0",
"angulartics2": "^6.2.0",
"body-parser": "1.18.2",
"bootstrap": "4.1.1",
"bootstrap": "4.1.3",
"cerialize": "0.1.18",
"compression": "1.7.1",
"cookie-parser": "1.4.3",
"core-js": "2.5.3",
"core-js": "^2.5.7",
"express": "4.16.2",
"express-session": "1.15.6",
"font-awesome": "4.7.0",

277
resources/i18n/cs.json Normal file
View File

@@ -0,0 +1,277 @@
{
"footer": {
"copyright": "copyright © 2002-{{ year }}",
"link.dspace": "software DSpace",
"link.duraspace": "DuraSpace"
},
"collection": {
"page": {
"news": "Novinky",
"license": "Licence",
"browse": {
"recent": {
"head": "Poslední příspěvky"
}
}
}
},
"community": {
"page": {
"news": "Novinky",
"license": "Licence"
},
"sub-collection-list": {
"head": "Kolekce v této komunitě"
}
},
"item": {
"page": {
"author": "Autor",
"abstract": "Abstract",
"date": "Datum",
"uri": "URI",
"files": "Soubory",
"collections": "Kolekce",
"filesection": {
"download": "Stáhnout",
"name": "Název:",
"format": "Formát:",
"size": "Velikost:",
"description": "Popis:"
},
"link": {
"simple": "Minimální záznam",
"full": "Úplný záznam"
}
}
},
"nav": {
"home": "Domů",
"login": "Přihlásit se",
"logout": "Odhlásit se"
},
"pagination": {
"results-per-page": "Výsledků na stránku",
"sort-direction": "Seřazení",
"showing": {
"label": "Zobrazují se záznamy ",
"detail": "{{ range }} z {{ total }}"
}
},
"sorting": {
"score": {
"DESC": "Relevance"
},
"dc.title": {
"ASC": "Název vzestupně",
"DESC": "Název sestupně"
}
},
"title": "DSpace",
"404": {
"help": "Nepodařilo se najít stránku, kterou hledáte. Je možné, že stránka byla přesunuta nebo smazána. Pomocí tlačítka níže můžete přejít na domovskou stránku. ",
"page-not-found": "stránka nenalezena",
"link": {
"home-page": "Přejít na domovskou stránku"
}
},
"home": {
"title": "DSpace Angular :: Domů",
"description": "",
"top-level-communities": {
"head": "Komunity v DSpace",
"help": "Vybráním komunity můžete prohlížet její kolekce."
}
},
"search": {
"title": "DSpace Angular :: Hledat",
"description": "",
"form": {
"search": "Hledat",
"search_dspace": "Hledat v DSpace"
},
"results": {
"head": "Výsledky hledání",
"no-results": "Nebyli nalezeny žádné výsledky"
},
"sidebar": {
"close": "Zpět na výsledky",
"open": "Vyhledávací nástroje",
"results": "výsledky",
"filters": {
"title": "Filtry"
},
"settings": {
"title": "Nastavení",
"sort-by": "Řadit dle",
"rpp": "Výsledků na stránku"
}
},
"view-switch": {
"show-list": "Zobrazit seznam",
"show-grid": "Zobrazit mřížku"
},
"filters": {
"head": "Filtry",
"reset": "Obnovit filtry",
"applied": {
"f.author": "Autor",
"f.dateIssued.min": "Od data",
"f.dateIssued.max": "Do data",
"f.subject": "Předmět",
"f.has_content_in_original_bundle": "Má soubory"
},
"filter": {
"show-more": "Zobrazit více",
"show-less": "Sbalit",
"author": {
"placeholder": "Jméno autora",
"head": "Autor"
},
"scope": {
"placeholder": "Filtr rozsahu",
"head": "Rozsah"
},
"subject": {
"placeholder": "Předmět",
"head": "Předmět"
},
"dateIssued": {
"max": {
"placeholder": "Datum od"
},
"min": {
"placeholder": "Datum do"
},
"head": "Datum"
},
"has_content_in_original_bundle": {
"head": "Má soubory"
}
}
}
},
"browse": {
"title": "Prohlížíte {{ collection }} dle {{ field }} {{ value }}"
},
"admin": {
"registries": {
"metadata": {
"title": "DSpace Angular :: Registr metadat",
"head": "Registr metadat",
"description": "Registr metadat je seznam všech metadatových polí dostupných v repozitáři. Tyto pole mohou být rozdělena do více schémat. DSpace však vyžaduje použití schématu kvalifikový Dublin Core.",
"schemas": {
"table": {
"id": "ID",
"namespace": "Jmenný prostor",
"name": "Název"
},
"no-items": "Žádná schémata metadat."
}
},
"schema": {
"title": "DSpace Angular :: Registr schémat metadat",
"head": "Metadata Schema",
"description": "Toto je schéma metadat pro „{{namespace}}“.",
"fields": {
"head": "Pole schématu metadat",
"table": {
"field": "Pole",
"scopenote": "Poznámka o rozsahu"
},
"no-items": "Žádná metadatová pole."
}
},
"bitstream-formats": {
"title": "DSpace Angular :: Registr formátů souborů",
"head": "Registr formátů souborů",
"description": "Tento seznam formátů souborů poskytuje informace o známých formátech a o úrovni jejich podpory.",
"formats": {
"table": {
"name": "Název",
"mimetype": "Typ MIME",
"supportLevel": {
"head": "Úroveň podpory",
"0": "Neznámá",
"1": "Známá",
"2": "Podpora"
},
"internal": "interní"
},
"no-items": "Žádné formáty souborů."
}
}
}
},
"loading": {
"default": "Načítá se...",
"top-level-communities": "Načítají se komunity nejvyšší úrovně...",
"community": "Načítá se komunita...",
"collection": "Načítá se kolekce...",
"sub-collections": "Načítají se subkolekce...",
"recent-submissions": "Načítají se poslední příspěvky...",
"item": "Načítá se záznam...",
"objects": "Načítá se...",
"search-results": "Načítají se výsledky hledání...",
"browse-by": "Načítají se záznamy..."
},
"error": {
"default": "Chyba",
"top-level-communities": "Chyba během stahování komunit nejvyšší úrovně",
"community": "Chyba během stahování komunity",
"collection": "Chyba během stahování kolekce",
"sub-collections": "Chyba během stahování subkolekcí",
"recent-submissions": "Chyba během stahování posledních příspěvků",
"item": "Chyba během stahování záznamu",
"objects": "Chyba během stahování objektů",
"search-results": "Chyba během stahování výsledků hledání",
"browse-by": "Chyba během stahování záznamů",
"validation": {
"pattern": "Tento vstup je omezen dle vzoru: {{ pattern }}.",
"license": {
"notgranted": "Pro dokončení zaslání Musíte udělit licenci. Pokud v tuto chvíli tuto licenci nemůžete udělit, můžete svou práci uložit a později se k svému příspěveku vrátit nebo jej smazat."
}
}
},
"form": {
"submit": "Odeslat",
"cancel": "Zrušit",
"search": "Hledat",
"remove": "Smazat",
"first-name": "Křestní jméno",
"last-name": "Příjmení",
"loading": "Načítá se...",
"no-results": "Nebyli nalezeny žádné výsledky",
"no-value": "Nebyla zadána hodnota",
"group-collapse": "Sbalit",
"group-expand": "Rozbalit",
"group-collapse-help": "Kliknutím sem sbalíte",
"group-expand-help": "Kliknutím sem rozbalíte a přidáte další prvky"
},
"login": {
"title": "Přihlásit se",
"form": {
"header": "Prosím, přihlaste se do DSpace",
"email": "E-mailová adresa",
"forgot-password": "Zapomněli jste své heslo?",
"new-user": "Nový uživatel? Zaregistrujte se kliknutím sem.",
"password": "Heslo",
"submit": "Přihlásit se"
}
},
"logout": {
"title": "Odhlásit se",
"form": {
"header": "Odhlásit se z DSpace",
"submit": "Odhlásit se"
}
},
"auth": {
"messages": {
"expired": "Vaše relace vypršela. Prosím, znova se přihlaste."
},
"errors": {
"invalid-user": "Neplatná e-mailová adresa nebo heslo."
}
}
}

277
resources/i18n/de.json Normal file
View File

@@ -0,0 +1,277 @@
{
"footer": {
"copyright": "Copyright © 2002-{{ year }}",
"link.dspace": "DSpace Software",
"link.duraspace": "DuraSpace"
},
"collection": {
"page": {
"news": "Neuigkeiten",
"license": "Lizenz",
"browse": {
"recent": {
"head": "Aktuellste Veröffentlichungen"
}
}
}
},
"community": {
"page": {
"news": "Neuigkeiten",
"license": "Lizenz"
},
"sub-collection-list": {
"head": "Sammlungen in diesem Bereich"
}
},
"item": {
"page": {
"author": "Autor",
"abstract": "Kurzfassung",
"date": "Datum",
"uri": "URI",
"files": "Dateien",
"collections": "Sammlungen",
"filesection": {
"download": "Herunterladen",
"name": "Name:",
"format": "Format:",
"size": "Größe:",
"description": "Beschreibung:"
},
"link": {
"simple": "Kurzanzeige",
"full": "Vollanzeige"
}
}
},
"nav": {
"home": "Zur Startseite",
"login": "Anmelden",
"logout": "Abmelden"
},
"pagination": {
"results-per-page": "Ergebnisse pro Seite",
"sort-direction": "Sortiermöglichkeiten",
"showing": {
"label": "Anzeige der Treffer ",
"detail": "{{ range }} bis {{ total }}"
}
},
"sorting": {
"score": {
"DESC": "Relevanz"
},
"dc.title": {
"ASC": "Titel aufsteigend",
"DESC": "Titel absteigend"
}
},
"title": "DSpace",
"404": {
"help": "Die Seite, die Sie aufrufen wollten, konnte nicht gefunden werden. Sie könnte verschoben oder gelöscht worden sein. Mit dem Link unten kommen Sie zurück zur Startseite. ",
"page-not-found": "Seite nicht gefunden",
"link": {
"home-page": "Zurück zur Startseite"
}
},
"home": {
"title": "DSpace Angular :: Startseite",
"description": "",
"top-level-communities": {
"head": "Bereiche in DSpace",
"help": "Wählen Sie einen Bereich, um seine Sammlungen einzusehen."
}
},
"search": {
"title": "DSpace Angular :: Suche",
"description": "",
"form": {
"search": "Suche",
"search_dspace": "DSpace durchsuchen"
},
"results": {
"head": "Suchergebnisse",
"no-results": "Zu dieser Suche gibt es keine Treffer."
},
"sidebar": {
"close": "Zurück zu den Ergebnissen",
"open": "Suchwerkzeuge",
"results": "Ergebnisse",
"filters": {
"title": "Filter"
},
"settings": {
"title": "Einstellungen",
"sort-by": "Sortiere nach",
"rpp": "Treffer pro Seite"
}
},
"view-switch": {
"show-list": "Zeige als Liste",
"show-grid": "Zeige als Raster"
},
"filters": {
"head": "Filter",
"reset": "Filter zurücksetzen",
"applied": {
"f.author": "Autor",
"f.dateIssued.min": "Anfangsdatum",
"f.dateIssued.max": "Enddatum",
"f.subject": "Thema",
"f.has_content_in_original_bundle": "Besitzt Dateien"
},
"filter": {
"show-more": "Zeige mehr",
"show-less": "Zeige weniger",
"author": {
"placeholder": "Autor",
"head": "Autor"
},
"scope": {
"placeholder": "Bereichsfilter",
"head": "Bereich"
},
"subject": {
"placeholder": "Schlagwort",
"head": "Schlagwort"
},
"dateIssued": {
"max": {
"placeholder": "Frühestes Datum"
},
"min": {
"placeholder": "Ältestes Datum"
},
"head": "Datum"
},
"has_content_in_original_bundle": {
"head": "Besitzt Dateien"
}
}
}
},
"browse": {
"title": "Anzeige {{ collection }} nach {{ field }} {{ value }}"
},
"admin": {
"registries": {
"metadata": {
"title": "DSpace Angular :: Metadatenreferenzliste",
"head": "Metadatenreferenzliste",
"description": "Die Metadatenreferenzliste beinhaltet alle Metadatenfelder, die zur Verfügung stehen. Die Felder können in unterschiedlichen Schemata enthalten sein. Nichtsdestotrotz benötigt DSpace mindestens qualifiziertes Dublin Core.",
"schemas": {
"table": {
"id": "ID",
"namespace": "Namensraum",
"name": "Name"
},
"no-items": "Es gbit keine Metadatenschemata."
}
},
"schema": {
"title": "DSpace Angular :: Referenzliste der Metadatenschemata",
"head": "Metadatenschemata",
"description": "Dies ist das Metadatenschema für \"{{namespace}}\".",
"fields": {
"head": "Felder in diesem Schema",
"table": {
"field": "Feld",
"scopenote": "Gültigkeitsbereich"
},
"no-items": "Es gibt keine Felder in diesem Schema."
}
},
"bitstream-formats": {
"title": "DSpace Angular :: Referenzliste der Dateiformate",
"head": "Referenzliste der Dateiformate",
"description": "Diese Liste enhtält die in diesem Repositorium zulässigen Dateiformate und den jeweiligen Unterstützungsgrad.",
"formats": {
"table": {
"name": "Name",
"mimetype": "MIME Type",
"supportLevel": {
"head": "Unterstützungsgrad",
"0": "Unbekannt",
"1": "Bekannt",
"2": "Unterstützt"
},
"internal": "intern"
},
"no-items": "Es gibt keine Formate in dieser Referenzliste."
}
}
}
},
"loading": {
"default": "Am Laden ...",
"top-level-communities": "Die Hauptbereiche werden geladen ...",
"community": "Der Bereich wird geladen ...",
"collection": "Die Sammlung wird geladen ...",
"sub-collections": "Die untergeordneten Sammlungen werden geladen ...",
"recent-submissions": "Die aktuellsten Veröffentlichungen werden geladen ...",
"item": "Die Ressource wird geladen ...",
"objects": "Am Laden ...",
"search-results": "Die Suchergebnisse werden geladen ...",
"browse-by": "Die Ressourcen werden geladen ..."
},
"error": {
"default": "Fehler",
"top-level-communities": "Fehler beim Laden der Hauptbereiche.",
"community": "Fehler beim Laden des Bereiches.",
"collection": "Fehler beim Laden der Sammlung.",
"sub-collections": "Fehler beim Laden der untergeordneten Sammlungen.",
"recent-submissions": "Fehler beim Laden der aktuellsten Veröffentlichungen.",
"item": "Fehler beim Laden der Ressource.",
"objects": "Fehler beim Laden der Objekte.",
"search-results": "Fehler beim Laden der Suchergebnisse.",
"browse-by": "Fehler beim Laden der Ressourcen",
"validation": {
"pattern": "Die Eingabe kann nur folgendes Muster haben: {{ pattern }}.",
"license": {
"notgranted": "Sie müssen der Lizenz zustimmen, um die Ressource einzureichen. Wenn dies zur Zeit nicht geht, können Sie die Einreichung speichern und später wiederaufnehmen oder löschen."
}
}
},
"form": {
"submit": "Los",
"cancel": "Abbrechen",
"search": "Suchen",
"remove": "Löschen",
"first-name": "Vorname",
"last-name": "Nachname",
"loading": "Am Laden ...",
"no-results": "Keine Ergebnisse gefunden",
"no-value": "Kein Wert eingegeben",
"group-collapse": "Weniger",
"group-expand": "Mehr",
"group-collapse-help": "Hier klicken, um die Anzeige zu reduzieren",
"group-expand-help": "Hier klicken, um mehr Elemente anzuzeigen"
},
"login": {
"title": "Einloggen",
"form": {
"header": "Bitte Loggen Sie sich ein.",
"email": "E-Mail-Adresse",
"forgot-password": "Haben Sie Ihr Passwort vergessen?",
"new-user": "Sind Sie neu hier? Klicken Sie hier, um sich zu registrieren.",
"password": "Passwort",
"submit": "Einloggen"
}
},
"logout": {
"title": "Ausloggen",
"form": {
"header": "Ausloggen aus DSpace",
"submit": "Ausloggen"
}
},
"auth": {
"messages": {
"expired": "Ihre Sitzung ist abgelaufen, bitte melden Sie sich erneut an."
},
"errors": {
"invalid-user": "Ungültige E-Mail-Adresse oder Passwort."
}
}
}

View File

@@ -151,6 +151,9 @@
}
}
},
"browse": {
"title": "Browsing {{ collection }} by {{ field }} {{ value }}"
},
"admin": {
"registries": {
"metadata": {
@@ -202,18 +205,19 @@
},
"loading": {
"default": "Loading...",
"top-level-communities": "Loading top level communities...",
"top-level-communities": "Loading top-level communities...",
"community": "Loading community...",
"collection": "Loading collection...",
"sub-collections": "Loading sub-collections...",
"recent-submissions": "Loading recent submissions...",
"item": "Loading item...",
"objects": "Loading...",
"search-results": "Loading search results..."
"search-results": "Loading search results...",
"browse-by": "Loading items..."
},
"error": {
"default": "Error",
"top-level-communities": "Error fetching top level communities",
"top-level-communities": "Error fetching top-level communities",
"community": "Error fetching community",
"collection": "Error fetching collection",
"sub-collections": "Error fetching sub-collections",
@@ -221,6 +225,7 @@
"item": "Error fetching item",
"objects": "Error fetching objects",
"search-results": "Error fetching search results",
"browse-by": "Error fetching items",
"validation": {
"pattern": "This input is restricted by the current pattern: {{ pattern }}.",
"license": {
@@ -241,7 +246,7 @@
"group-collapse": "Collapse",
"group-expand": "Expand",
"group-collapse-help": "Click here to collapse",
"group-expand-help": "Click here to expand and add more element"
"group-expand-help": "Click here to expand and add more elements"
},
"login": {
"title": "Login",
@@ -266,7 +271,7 @@
"expired": "Your session has expired. Please log in again."
},
"errors": {
"invalid-user": "Invalid email or password."
"invalid-user": "Invalid email address or password."
}
}
}

277
resources/i18n/nl.json Normal file
View File

@@ -0,0 +1,277 @@
{
"footer": {
"copyright": "copyright © 2002-{{ year }}",
"link.dspace": "DSpace software",
"link.duraspace": "DuraSpace"
},
"collection": {
"page": {
"news": "Nieuws",
"license": "Licentie",
"browse": {
"recent": {
"head": "Recent toegevoegd"
}
}
}
},
"community": {
"page": {
"news": "Nieuws",
"license": "Licentie"
},
"sub-collection-list": {
"head": "Collecties in deze Community"
}
},
"item": {
"page": {
"author": "Auteur",
"abstract": "Abstract",
"date": "Datum",
"uri": "URI",
"files": "Bestanden",
"collections": "Collecties",
"filesection": {
"download": "Download",
"name": "Naam:",
"format": "Formaat:",
"size": "Grootte:",
"description": "Beschrijving:"
},
"link": {
"simple": "Eenvoudige item weergave",
"full": "Volledige item weergave"
}
}
},
"nav": {
"home": "Home",
"login": "Log In",
"logout": "Log Uit"
},
"pagination": {
"results-per-page": "Resultaten per pagina",
"sort-direction": "Sorteer mogelijkheden",
"showing": {
"label": "Getoonde items ",
"detail": "{{ range }} tot {{ total }}"
}
},
"sorting": {
"score": {
"DESC": "Relevantie"
},
"dc.title": {
"ASC": "Oplopend op titel",
"DESC": "Aflopend op titel"
}
},
"title": "DSpace",
"404": {
"help": "De pagina die u zoekt kan niet gevonden worden. De pagina werd mogelijk verplaatst of verwijderd. U kan onderstaande knop gebruiken om terug naar de homepagina te gaan. ",
"page-not-found": "Pagina niet gevonden",
"link": {
"home-page": "Terug naar de homepagina"
}
},
"home": {
"title": "DSpace Angular :: Home",
"description": "",
"top-level-communities": {
"head": "Communities in DSpace",
"help": "Selecteer een community om diens collecties te verkennen."
}
},
"search": {
"title": "DSpace Angular :: Zoek",
"description": "",
"form": {
"search": "Zoek",
"search_dspace": "Zoek in DSpace"
},
"results": {
"head": "Zoekresultaten",
"no-results": "Er waren geen resultaten voor deze zoekopdracht"
},
"sidebar": {
"close": "Terug naar de resultaten",
"open": "Zoek Tools",
"results": "resultaten",
"filters": {
"title": "Filters"
},
"settings": {
"title": "Instellingen",
"sort-by": "Sorteer volgens",
"rpp": "Resultaten per pagina"
}
},
"view-switch": {
"show-list": "Toon als lijst",
"show-grid": "Toon in raster"
},
"filters": {
"head": "Filters",
"reset": "Filters verwijderen",
"applied": {
"f.author": "Auteur",
"f.dateIssued.min": "Start datum",
"f.dateIssued.max": "Eind datum",
"f.subject": "Sleutelwoord",
"f.has_content_in_original_bundle": "Heeft bestanden"
},
"filter": {
"show-more": "Toon meer",
"show-less": "Inklappen",
"author": {
"placeholder": "Auteursnaam",
"head": "Auteur"
},
"scope": {
"placeholder": "Bereik filter",
"head": "Bereik"
},
"subject": {
"placeholder": "Onderwerp",
"head": "Onderwerp"
},
"dateIssued": {
"max": {
"placeholder": "Vroegste Datum"
},
"min": {
"placeholder": "Laatste Datum"
},
"head": "Datum"
},
"has_content_in_original_bundle": {
"head": "Heeft bestanden"
}
}
}
},
"browse": {
"title": "Verken {{ collection }} volgens {{ field }} {{ value }}"
},
"admin": {
"registries": {
"metadata": {
"title": "DSpace Angular :: Metadata Register",
"head": "Metadata Register",
"description": "Het metadata register omvat de lijst van alle metadata velden die beschikbaar zijn in het systeem. Deze velden kunnen verspreid zijn over verschillende metadata schema's. Het qualified Dublin Core schema (dc) is een verplicht schema en kan niet worden verwijderd.",
"schemas": {
"table": {
"id": "ID",
"namespace": "Naamruimte",
"name": "Naam"
},
"no-items": "Er kunnen geen metadata schema's getoond worden."
}
},
"schema": {
"title": "DSpace Angular :: Metadata Schema Register",
"head": "Metadata Schema",
"description": "Dit is het metadata schema voor \"{{namespace}}\".",
"fields": {
"head": "Schema metadata velden",
"table": {
"field": "Veld",
"scopenote": "Opmerking over bereik"
},
"no-items": "Er kunnen geen metadata velden getoond worden."
}
},
"bitstream-formats": {
"title": "DSpace Angular :: Bitstream Formaat Register",
"head": "Bitstream Formaat Register",
"description": "Deze lijst van Bitstream formaten biedt informatie over de formaten die in deze repository zijn toegelaten en op welke manier ze ondersteund worden. De term Bitstream wordt in DSpace gebruikt om een bestand aan te duiden dat samen met metadata onderdeel uitmaakt van een item. De naam bitstream duidt op het feit dat het bestand achterliggend wordt opgeslaan zonder bestandsextensie.",
"formats": {
"table": {
"name": "Naam",
"mimetype": "MIME Type",
"supportLevel": {
"head": "Ondersteuning",
"0": "Onbekend",
"1": "Gekend",
"2": "Ondersteund"
},
"internal": "intern"
},
"no-items": "Er kunnen geen bitstream formaten getoond worden."
}
}
}
},
"loading": {
"default": "Laden...",
"top-level-communities": "Inladen van de Communities op het hoogste niveau...",
"community": "Community wordt ingeladen...",
"collection": "Collectie wordt ingeladen...",
"sub-collections": "De sub-collecties worden ingeladen...",
"recent-submissions": "Recent toegevoegde items worden ingeladen...",
"item": "Item wordt ingeladen...",
"objects": "Laden...",
"search-results": "Zoekresultaten worden ingeladen...",
"browse-by": "Items worden ingeladen..."
},
"error": {
"default": "Fout",
"top-level-communities": "Fout bij het inladen van communities op het hoogste niveau",
"community": "Fout bij het ophalen van een community",
"collection": "Fout bij het ophalen van een collectie",
"sub-collections": "Fout bij het ophalen van sub-collecties",
"recent-submissions": "Fout bij het ophalen van recent toegevoegde items",
"item": "Fout bij het ophalen van items",
"objects": "Fout bij het ophalen van objecten",
"search-results": "Fout bij het ophalen van zoekresultaten",
"browse-by": "Fout bij het ophalen van items",
"validation": {
"pattern": "Deze invoer is niet toegelaten volgens dit patroon: {{ pattern }}.",
"license": {
"notgranted": "U moet de invoerlicentie goedkeuren om de invoer af te werken. Indien u deze licentie momenteel niet kan of mag goedkeuren, kan u uw werk opslaan en de invoer later afwerken. U kan dit nieuwe item ook verwijderen indien u niet voldoet aan de vereisten van de invoer licentie."
}
}
},
"form": {
"submit": "Verstuur",
"cancel": "Annuleer",
"search": "Zoek",
"remove": "Verwijder",
"first-name": "Voornaam",
"last-name": "Achternaam",
"loading": "Inladen...",
"no-results": "Geen resultaten gevonden",
"no-value": "Geen waarde ingevoerd",
"group-collapse": "Inklappen",
"group-expand": "Uitklappen",
"group-collapse-help": "Klik hier op in te klappen",
"group-expand-help": "Klik hier om uit te klappen en om meer onderdelen toe te voegen"
},
"login": {
"title": "Aanmelden",
"form": {
"header": "Gelieve in te loggen in DSpace",
"email": "Email adres",
"forgot-password": "Bent u uw wachtwoord vergeten?",
"new-user": "Nieuwe gebruiker? Gelieve u hier te registreren",
"password": "Wachtwoord",
"submit": "Aanmelden"
}
},
"logout": {
"title": "Afmelden",
"form": {
"header": "Afmelden in DSpace",
"submit": "Afmelden"
}
},
"auth": {
"messages": {
"expired": "Uw sessie is vervallen. Gelieve opnieuw aan te melden."
},
"errors": {
"invalid-user": "Ongeldig email adres of wachtwoord."
}
}
}

View File

@@ -0,0 +1,11 @@
<div class="container">
<div class="browse-by-author w-100 row">
<ds-browse-by class="col-xs-12 w-100"
title="{{'browse.title' | translate:{collection: '', field: 'Author', value: (value)? '&quot;' + value + '&quot;': ''} }}"
[objects$]="(items$ !== undefined)? items$ : authors$"
[currentUrl]="currentUrl"
[paginationConfig]="paginationConfig"
[sortConfig]="sortConfig">
</ds-browse-by>
</div>
</div>

View File

@@ -0,0 +1,108 @@
import { Component, OnInit } from '@angular/core';
import { RemoteData } from '../../core/data/remote-data';
import { PaginatedList } from '../../core/data/paginated-list';
import { ItemDataService } from '../../core/data/item-data.service';
import { Observable } from 'rxjs/Observable';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { Subscription } from 'rxjs/Subscription';
import { ActivatedRoute } from '@angular/router';
import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { Metadatum } from '../../core/shared/metadatum.model';
import { BrowseService } from '../../core/browse/browse.service';
import { BrowseEntry } from '../../core/shared/browse-entry.model';
import { Item } from '../../core/shared/item.model';
@Component({
selector: 'ds-browse-by-author-page',
styleUrls: ['./browse-by-author-page.component.scss'],
templateUrl: './browse-by-author-page.component.html'
})
/**
* Component for browsing (items) by author (dc.contributor.author)
*/
export class BrowseByAuthorPageComponent implements OnInit {
authors$: Observable<RemoteData<PaginatedList<BrowseEntry>>>;
items$: Observable<RemoteData<PaginatedList<Item>>>;
paginationConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
id: 'browse-by-author-pagination',
currentPage: 1,
pageSize: 20
});
sortConfig: SortOptions = new SortOptions('dc.contributor.author', SortDirection.ASC);
subs: Subscription[] = [];
currentUrl: string;
value = '';
public constructor(private itemDataService: ItemDataService, private route: ActivatedRoute, private browseService: BrowseService) {
}
ngOnInit(): void {
this.currentUrl = this.route.snapshot.pathFromRoot
.map((snapshot) => (snapshot.routeConfig) ? snapshot.routeConfig.path : '')
.join('/');
this.updatePage({
pagination: this.paginationConfig,
sort: this.sortConfig
});
this.subs.push(
Observable.combineLatest(
this.route.params,
this.route.queryParams,
(params, queryParams, ) => {
return Object.assign({}, params, queryParams);
})
.subscribe((params) => {
const page = +params.page || this.paginationConfig.currentPage;
const pageSize = +params.pageSize || this.paginationConfig.pageSize;
const sortDirection = params.sortDirection || this.sortConfig.direction;
const sortField = params.sortField || this.sortConfig.field;
this.value = +params.value || params.value || '';
const pagination = Object.assign({},
this.paginationConfig,
{ currentPage: page, pageSize: pageSize }
);
const sort = Object.assign({},
this.sortConfig,
{ direction: sortDirection, field: sortField }
);
const searchOptions = {
pagination: pagination,
sort: sort
};
if (isNotEmpty(this.value)) {
this.updatePageWithItems(searchOptions, this.value);
} else {
this.updatePage(searchOptions);
}
}));
}
/**
* Updates the current page with searchOptions
* @param searchOptions Options to narrow down your search:
* { pagination: PaginationComponentOptions,
* sort: SortOptions }
*/
updatePage(searchOptions) {
this.authors$ = this.browseService.getBrowseEntriesFor('author', searchOptions);
this.items$ = undefined;
}
/**
* Updates the current page with searchOptions and display items linked to author
* @param searchOptions Options to narrow down your search:
* { pagination: PaginationComponentOptions,
* sort: SortOptions }
* @param author The author's name for displaying items
*/
updatePageWithItems(searchOptions, author: string) {
this.items$ = this.browseService.getBrowseItemsFor('author', author, searchOptions);
}
ngOnDestroy(): void {
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
}
}

View File

@@ -0,0 +1,11 @@
<div class="container">
<div class="browse-by-title w-100 row">
<ds-browse-by class="col-xs-12 w-100"
title="{{'browse.title' | translate:{collection: '', field: 'Title', value: ''} }}"
[objects$]="items$"
[currentUrl]="currentUrl"
[paginationConfig]="paginationConfig"
[sortConfig]="sortConfig">
</ds-browse-by>
</div>
</div>

View File

@@ -0,0 +1,92 @@
import { Component, OnInit } from '@angular/core';
import { RemoteData } from '../../core/data/remote-data';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { PaginatedList } from '../../core/data/paginated-list';
import { ItemDataService } from '../../core/data/item-data.service';
import { Observable } from 'rxjs/Observable';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { Item } from '../../core/shared/item.model';
import { Subscription } from 'rxjs/Subscription';
import { ActivatedRoute, PRIMARY_OUTLET, UrlSegmentGroup } from '@angular/router';
import { hasValue } from '../../shared/empty.util';
import { Collection } from '../../core/shared/collection.model';
@Component({
selector: 'ds-browse-by-title-page',
styleUrls: ['./browse-by-title-page.component.scss'],
templateUrl: './browse-by-title-page.component.html'
})
/**
* Component for browsing items by title (dc.title)
*/
export class BrowseByTitlePageComponent implements OnInit {
items$: Observable<RemoteData<PaginatedList<Item>>>;
paginationConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
id: 'browse-by-title-pagination',
currentPage: 1,
pageSize: 20
});
sortConfig: SortOptions = new SortOptions('dc.title', SortDirection.ASC);
subs: Subscription[] = [];
currentUrl: string;
public constructor(private itemDataService: ItemDataService, private route: ActivatedRoute) {
}
ngOnInit(): void {
this.currentUrl = this.route.snapshot.pathFromRoot
.map((snapshot) => (snapshot.routeConfig) ? snapshot.routeConfig.path : '')
.join('/');
this.updatePage({
pagination: this.paginationConfig,
sort: this.sortConfig
});
this.subs.push(
Observable.combineLatest(
this.route.params,
this.route.queryParams,
(params, queryParams, ) => {
return Object.assign({}, params, queryParams);
})
.subscribe((params) => {
const page = +params.page || this.paginationConfig.currentPage;
const pageSize = +params.pageSize || this.paginationConfig.pageSize;
const sortDirection = params.sortDirection || this.sortConfig.direction;
const sortField = params.sortField || this.sortConfig.field;
const pagination = Object.assign({},
this.paginationConfig,
{ currentPage: page, pageSize: pageSize }
);
const sort = Object.assign({},
this.sortConfig,
{ direction: sortDirection, field: sortField }
);
this.updatePage({
pagination: pagination,
sort: sort
});
}));
}
/**
* Updates the current page with searchOptions
* @param searchOptions Options to narrow down your search:
* { pagination: PaginationComponentOptions,
* sort: SortOptions }
*/
updatePage(searchOptions) {
this.items$ = this.itemDataService.findAll({
currentPage: searchOptions.pagination.currentPage,
elementsPerPage: searchOptions.pagination.pageSize,
sort: searchOptions.sort
});
}
ngOnDestroy(): void {
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
}
}

View File

@@ -0,0 +1,16 @@
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { BrowseByTitlePageComponent } from './+browse-by-title-page/browse-by-title-page.component';
import { BrowseByAuthorPageComponent } from './+browse-by-author-page/browse-by-author-page.component';
@NgModule({
imports: [
RouterModule.forChild([
{ path: 'title', component: BrowseByTitlePageComponent },
{ path: 'author', component: BrowseByAuthorPageComponent }
])
]
})
export class BrowseByRoutingModule {
}

View File

@@ -0,0 +1,27 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BrowseByTitlePageComponent } from './+browse-by-title-page/browse-by-title-page.component';
import { ItemDataService } from '../core/data/item-data.service';
import { SharedModule } from '../shared/shared.module';
import { BrowseByRoutingModule } from './browse-by-routing.module';
import { BrowseByAuthorPageComponent } from './+browse-by-author-page/browse-by-author-page.component';
import { BrowseService } from '../core/browse/browse.service';
@NgModule({
imports: [
BrowseByRoutingModule,
CommonModule,
SharedModule
],
declarations: [
BrowseByTitlePageComponent,
BrowseByAuthorPageComponent
],
providers: [
ItemDataService,
BrowseService
]
})
export class BrowseByModule {
}

View File

@@ -35,7 +35,7 @@
</ds-comcol-page-content>
</div>
</div>
<ds-error *ngIf="collectionRD?.hasFailed"0 message="{{'error.collection' | translate}}"></ds-error>
<ds-error *ngIf="collectionRD?.hasFailed" message="{{'error.collection' | translate}}"></ds-error>
<ds-loading *ngIf="collectionRD?.isLoading" message="{{'loading.collection' | translate}}"></ds-loading>
<br>
<ng-container *ngVar="(itemRD$ | async) as itemRD">

View File

@@ -6,13 +6,21 @@ import { CollectionDataService } from '../core/data/collection-data.service';
import { RemoteData } from '../core/data/remote-data';
import { getSucceededRemoteData } from '../core/shared/operators';
/**
* This class represents a resolver that requests a specific collection before the route is activated
*/
@Injectable()
export class CollectionPageResolver implements Resolve<RemoteData<Collection>> {
constructor(private collectionService: CollectionDataService) {
}
/**
* Method for resolving a collection based on the parameters in the current route
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @returns Observable<<RemoteData<Collection>> Emits the found collection based on the parameters in the current route
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Collection>> {
return this.collectionService.findById(route.params.id).pipe(
getSucceededRemoteData()
);

View File

@@ -6,13 +6,21 @@ import { getSucceededRemoteData } from '../core/shared/operators';
import { Community } from '../core/shared/community.model';
import { CommunityDataService } from '../core/data/community-data.service';
/**
* This class represents a resolver that requests a specific community before the route is activated
*/
@Injectable()
export class CommunityPageResolver implements Resolve<RemoteData<Community>> {
constructor(private communityService: CommunityDataService) {
}
/**
* Method for resolving a community based on the parameters in the current route
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @returns Observable<<RemoteData<Community>> Emits the found community based on the parameters in the current route
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Community>> {
return this.communityService.findById(route.params.id).pipe(
getSucceededRemoteData()
);

View File

@@ -6,11 +6,20 @@ import { getSucceededRemoteData } from '../core/shared/operators';
import { ItemDataService } from '../core/data/item-data.service';
import { Item } from '../core/shared/item.model';
/**
* This class represents a resolver that requests a specific item before the route is activated
*/
@Injectable()
export class ItemPageResolver implements Resolve<RemoteData<Item>> {
constructor(private itemService: ItemDataService) {
}
/**
* Method for resolving an item based on the parameters in the current route
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
return this.itemService.findById(route.params.id).pipe(
getSucceededRemoteData()

View File

@@ -12,6 +12,7 @@ import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
{ path: 'collections', loadChildren: './+collection-page/collection-page.module#CollectionPageModule' },
{ path: 'items', loadChildren: './+item-page/item-page.module#ItemPageModule' },
{ path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' },
{ path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule' },
{ path: 'admin', loadChildren: './+admin/admin.module#AdminModule' },
{ path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
{ path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' },

View File

@@ -1,14 +1,15 @@
import { AuthType } from './auth-type';
import { GenericConstructor } from '../shared/generic-constructor';
import { NormalizedAuthStatus } from './models/normalized-auth-status.model';
import { NormalizedDSpaceObject } from '../cache/models/normalized-dspace-object.model';
import { NormalizedEpersonModel } from '../eperson/models/NormalizedEperson.model';
import { NormalizedEPerson } from '../eperson/models/normalized-eperson.model';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { EPerson } from '../eperson/models/eperson.model';
export class AuthObjectFactory {
public static getConstructor(type): GenericConstructor<NormalizedDSpaceObject> {
public static getConstructor(type): GenericConstructor<NormalizedObject> {
switch (type) {
case AuthType.Eperson: {
return NormalizedEpersonModel
case AuthType.EPerson: {
return NormalizedEPerson
}
case AuthType.Status: {

View File

@@ -8,12 +8,13 @@ import { CoreState } from '../core.reducers';
import { AuthStatus } from './models/auth-status.model';
import { AuthResponseParsingService } from './auth-response-parsing.service';
import { AuthGetRequest, AuthPostRequest } from '../data/request.models';
import { getMockStore } from '../../shared/mocks/mock-store';
describe('ConfigResponseParsingService', () => {
describe('AuthResponseParsingService', () => {
let service: AuthResponseParsingService;
const EnvConfig = {} as GlobalConfig;
const store = {} as Store<CoreState>;
const EnvConfig = {cache: {msToLive: 1000}} as GlobalConfig;
const store = getMockStore() as Store<CoreState>;
const objectCacheService = new ObjectCacheService(store);
beforeEach(() => {
@@ -86,13 +87,19 @@ describe('ConfigResponseParsingService', () => {
type: 'eperson',
uuid: '4dc70ab5-cd73-492f-b007-3179d2d9296b',
_links: {
self: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/eperson/epersons/4dc70ab5-cd73-492f-b007-3179d2d9296b'
self: {
href: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/eperson/epersons/4dc70ab5-cd73-492f-b007-3179d2d9296b'
}
}
}
},
_links: {
eperson: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/eperson/epersons/4dc70ab5-cd73-492f-b007-3179d2d9296b',
self: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/authn/status'
eperson: {
href: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/eperson/epersons/4dc70ab5-cd73-492f-b007-3179d2d9296b'
},
self: {
href: 'https://hasselt-dspace.dev01.4science.it/dspace-spring-rest/api/authn/status'
}
}
},
statusCode: '200'

View File

@@ -12,22 +12,23 @@ import { ResponseParsingService } from '../data/parsing.service';
import { RestRequest } from '../data/request.models';
import { AuthType } from './auth-type';
import { AuthStatus } from './models/auth-status.model';
import { NormalizedAuthStatus } from './models/normalized-auth-status.model';
@Injectable()
export class AuthResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
protected objectFactory = AuthObjectFactory;
protected toCache = false;
protected toCache = true;
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
protected objectCache: ObjectCacheService,) {
protected objectCache: ObjectCacheService) {
super();
}
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links) && (data.statusCode === '200' || data.statusCode === 'OK')) {
const response = this.process<AuthStatus, AuthType>(data.payload, request.href);
return new AuthStatusResponse(response[Object.keys(response)[0]][0], data.statusCode);
const response = this.process<NormalizedAuthStatus, AuthType>(data.payload, request.href);
return new AuthStatusResponse(response, data.statusCode);
} else {
return new AuthStatusResponse(data.payload as AuthStatus, data.statusCode);
}

View File

@@ -1,4 +1,4 @@
export enum AuthType {
Eperson = 'eperson',
EPerson = 'eperson',
Status = 'status'
}

View File

@@ -5,7 +5,7 @@ import { Action } from '@ngrx/store';
import { type } from '../../shared/ngrx/type';
// import models
import { Eperson } from '../eperson/models/eperson.model';
import { EPerson } from '../eperson/models/eperson.model';
import { AuthTokenInfo } from './models/auth-token-info.model';
export const AuthActionTypes = {
@@ -76,10 +76,10 @@ export class AuthenticatedSuccessAction implements Action {
payload: {
authenticated: boolean;
authToken: AuthTokenInfo;
user: Eperson
user: EPerson
};
constructor(authenticated: boolean, authToken: AuthTokenInfo, user: Eperson) {
constructor(authenticated: boolean, authToken: AuthTokenInfo, user: EPerson) {
this.payload = { authenticated, authToken, user };
}
}
@@ -250,9 +250,9 @@ export class RefreshTokenErrorAction implements Action {
*/
export class RegistrationAction implements Action {
public type: string = AuthActionTypes.REGISTRATION;
payload: Eperson;
payload: EPerson;
constructor(user: Eperson) {
constructor(user: EPerson) {
this.payload = user;
}
}
@@ -278,9 +278,9 @@ export class RegistrationErrorAction implements Action {
*/
export class RegistrationSuccessAction implements Action {
public type: string = AuthActionTypes.REGISTRATION_SUCCESS;
payload: Eperson;
payload: EPerson;
constructor(user: Eperson) {
constructor(user: EPerson) {
this.payload = user;
}
}

View File

@@ -25,12 +25,11 @@ import { AuthServiceStub } from '../../shared/testing/auth-service-stub';
import { AuthService } from './auth.service';
import { TruncatablesState } from '../../shared/truncatable/truncatable.reducer';
import { EpersonMock } from '../../shared/testing/eperson-mock';
import { EPersonMock } from '../../shared/testing/eperson-mock';
describe('AuthEffects', () => {
let authEffects: AuthEffects;
let actions: Observable<any>;
const authServiceStub = new AuthServiceStub();
const store: Store<TruncatablesState> = jasmine.createSpyObj('store', {
/* tslint:disable:no-empty */
@@ -105,7 +104,7 @@ describe('AuthEffects', () => {
it('should return a AUTHENTICATED_SUCCESS action in response to a AUTHENTICATED action', () => {
actions = hot('--a-', {a: {type: AuthActionTypes.AUTHENTICATED, payload: token}});
const expected = cold('--b-', {b: new AuthenticatedSuccessAction(true, token, EpersonMock)});
const expected = cold('--b-', {b: new AuthenticatedSuccessAction(true, token, EPersonMock)});
expect(authEffects.authenticated$).toBeObservable(expected);
});

View File

@@ -28,7 +28,7 @@ import {
RegistrationErrorAction,
RegistrationSuccessAction
} from './auth.actions';
import { Eperson } from '../eperson/models/eperson.model';
import { EPerson } from '../eperson/models/eperson.model';
import { AuthStatus } from './models/auth-status.model';
import { AuthTokenInfo } from './models/auth-token-info.model';
import { AppState } from '../../app.reducer';
@@ -66,7 +66,7 @@ export class AuthEffects {
ofType(AuthActionTypes.AUTHENTICATED),
switchMap((action: AuthenticatedAction) => {
return this.authService.authenticatedUser(action.payload).pipe(
map((user: Eperson) => new AuthenticatedSuccessAction((user !== null), action.payload, user)),
map((user: EPerson) => new AuthenticatedSuccessAction((user !== null), action.payload, user)),
catchError((error) => observableOf(new AuthenticatedErrorAction(error))),);
})
);
@@ -94,7 +94,7 @@ export class AuthEffects {
debounceTime(500), // to remove when functionality is implemented
switchMap((action: RegistrationAction) => {
return this.authService.create(action.payload).pipe(
map((user: Eperson) => new RegistrationSuccessAction(user)),
map((user: EPerson) => new RegistrationSuccessAction(user)),
catchError((error) => observableOf(new RegistrationErrorAction(error)))
);
})

View File

@@ -42,7 +42,7 @@ export class AuthInterceptor implements HttpInterceptor {
}
private isSuccess(response: HttpResponseBase): boolean {
return response.status === 200;
return (response.status === 200 || response.status === 204);
}
private isAuthRequest(http: HttpRequest<any> | HttpResponseBase): boolean {

View File

@@ -21,7 +21,7 @@ import {
SetRedirectUrlAction
} from './auth.actions';
import { AuthTokenInfo } from './models/auth-token-info.model';
import { EpersonMock } from '../../shared/testing/eperson-mock';
import { EPersonMock } from '../../shared/testing/eperson-mock';
describe('authReducer', () => {
@@ -107,7 +107,7 @@ describe('authReducer', () => {
loading: true,
info: undefined
};
const action = new AuthenticatedSuccessAction(true, mockTokenInfo, EpersonMock);
const action = new AuthenticatedSuccessAction(true, mockTokenInfo, EPersonMock);
const newState = authReducer(initialState, action);
state = {
authenticated: true,
@@ -116,7 +116,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
user: EpersonMock
user: EPersonMock
};
expect(newState).toEqual(state);
});
@@ -182,7 +182,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
user: EpersonMock
user: EPersonMock
};
const action = new LogOutAction();
@@ -199,7 +199,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
user: EpersonMock
user: EPersonMock
};
const action = new LogOutSuccessAction();
@@ -225,7 +225,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
user: EpersonMock
user: EPersonMock
};
const action = new LogOutErrorAction(mockError);
@@ -237,7 +237,7 @@ describe('authReducer', () => {
error: 'Test error message',
loading: false,
info: undefined,
user: EpersonMock
user: EPersonMock
};
expect(newState).toEqual(state);
});
@@ -250,7 +250,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
user: EpersonMock
user: EPersonMock
};
const newTokenInfo = new AuthTokenInfo('Refreshed token');
const action = new RefreshTokenAction(newTokenInfo);
@@ -262,7 +262,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
user: EpersonMock,
user: EPersonMock,
refreshing: true
};
expect(newState).toEqual(state);
@@ -276,7 +276,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
user: EpersonMock,
user: EPersonMock,
refreshing: true
};
const newTokenInfo = new AuthTokenInfo('Refreshed token');
@@ -289,7 +289,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
user: EpersonMock,
user: EPersonMock,
refreshing: false
};
expect(newState).toEqual(state);
@@ -303,7 +303,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
user: EpersonMock,
user: EPersonMock,
refreshing: true
};
const action = new RefreshTokenErrorAction();
@@ -329,7 +329,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
user: EpersonMock
user: EPersonMock
};
state = {

View File

@@ -12,7 +12,7 @@ import {
SetRedirectUrlAction
} from './auth.actions';
// import models
import { Eperson } from '../eperson/models/eperson.model';
import { EPerson } from '../eperson/models/eperson.model';
import { AuthTokenInfo } from './models/auth-token-info.model';
/**
@@ -46,7 +46,7 @@ export interface AuthState {
refreshing?: boolean;
// the authenticated user
user?: Eperson;
user?: EPerson;
}
/**

View File

@@ -17,10 +17,12 @@ import { AuthRequestServiceStub } from '../../shared/testing/auth-request-servic
import { AuthRequestService } from './auth-request.service';
import { AuthStatus } from './models/auth-status.model';
import { AuthTokenInfo } from './models/auth-token-info.model';
import { Eperson } from '../eperson/models/eperson.model';
import { EpersonMock } from '../../shared/testing/eperson-mock';
import { EPerson } from '../eperson/models/eperson.model';
import { EPersonMock } from '../../shared/testing/eperson-mock';
import { AppState } from '../../app.reducer';
import { ClientCookieService } from '../../shared/services/client-cookie.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service';
describe('AuthService test', () => {
@@ -41,9 +43,9 @@ describe('AuthService test', () => {
loaded: true,
loading: false,
authToken: token,
user: EpersonMock
user: EPersonMock
};
const rdbService = getMockRemoteDataBuildService();
describe('', () => {
beforeEach(() => {
@@ -60,6 +62,7 @@ describe('AuthService test', () => {
{provide: Router, useValue: routerStub},
{provide: ActivatedRoute, useValue: routeStub},
{provide: Store, useValue: mockStore},
{provide: RemoteDataBuildService, useValue: rdbService},
CookieService,
AuthService
],
@@ -78,7 +81,7 @@ describe('AuthService test', () => {
});
it('should return the authenticated user object when user token is valid', () => {
authService.authenticatedUser(new AuthTokenInfo('test_token')).subscribe((user: Eperson) => {
authService.authenticatedUser(new AuthTokenInfo('test_token')).subscribe((user: EPerson) => {
expect(user).toBeDefined();
});
});
@@ -120,6 +123,7 @@ describe('AuthService test', () => {
{provide: AuthRequestService, useValue: authRequest},
{provide: REQUEST, useValue: {}},
{provide: Router, useValue: routerStub},
{provide: RemoteDataBuildService, useValue: rdbService},
CookieService
]
}).compileComponents();
@@ -131,7 +135,7 @@ describe('AuthService test', () => {
(state as any).core = Object.create({});
(state as any).core.auth = authenticatedState;
});
authService = new AuthService({}, window, authReqService, router, cookieService, store);
authService = new AuthService({}, window, authReqService, router, cookieService, store, rdbService);
}));
it('should return true when user is logged in', () => {
@@ -183,14 +187,14 @@ describe('AuthService test', () => {
loaded: true,
loading: false,
authToken: expiredToken,
user: EpersonMock
user: EPersonMock
};
store
.subscribe((state) => {
(state as any).core = Object.create({});
(state as any).core.auth = authenticatedState;
});
authService = new AuthService({}, window, authReqService, router, cookieService, store);
authService = new AuthService({}, window, authReqService, router, cookieService, store, rdbService);
storage = (authService as any).storage;
spyOn(storage, 'get');
spyOn(storage, 'remove');

View File

@@ -16,8 +16,10 @@ import { REQUEST } from '@nguniversal/express-engine/tokens';
import { RouterReducerState } from '@ngrx/router-store';
import { select, Store } from '@ngrx/store';
import { CookieAttributes } from 'js-cookie';
import { Observable } from 'rxjs/Observable';
import { map, switchMap, withLatestFrom } from 'rxjs/operators';
import { Eperson } from '../eperson/models/eperson.model';
import { EPerson } from '../eperson/models/eperson.model';
import { AuthRequestService } from './auth-request.service';
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
@@ -35,6 +37,8 @@ import { AppState, routerStateSelector } from '../../app.reducer';
import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions';
import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service';
import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedEPerson } from '../eperson/models/normalized-eperson.model';
export const LOGIN_ROUTE = '/login';
export const LOGOUT_ROUTE = '/logout';
@@ -58,7 +62,9 @@ export class AuthService {
protected authRequestService: AuthRequestService,
protected router: Router,
protected storage: CookieService,
protected store: Store<AppState>) {
protected store: Store<AppState>,
protected rdbService: RemoteDataBuildService
) {
this.store.pipe(
select(isAuthenticated),
startWith(false)
@@ -132,7 +138,7 @@ export class AuthService {
* Returns the authenticated user
* @returns {User}
*/
public authenticatedUser(token: AuthTokenInfo): Observable<Eperson> {
public authenticatedUser(token: AuthTokenInfo): Observable<EPerson> {
// Determine if the user has an existing auth session on the server
const options: HttpOptions = Object.create({});
let headers = new HttpHeaders();
@@ -140,13 +146,18 @@ export class AuthService {
headers = headers.append('Authorization', `Bearer ${token.accessToken}`);
options.headers = headers;
return this.authRequestService.getRequest('status', options).pipe(
map((status: AuthStatus) => {
switchMap((status: AuthStatus) => {
if (status.authenticated) {
return status.eperson[0];
// TODO this should be cleaned up, AuthStatus could be parsed by the RemoteDataService as a whole...
// Review when https://jira.duraspace.org/browse/DS-4006 is fixed
// See https://github.com/DSpace/dspace-angular/issues/292
const person$ = this.rdbService.buildSingle<NormalizedEPerson, EPerson>(status.eperson.toString());
return person$.pipe(map((eperson) => eperson.payload));
} else {
throw(new Error('Not authenticated'));
}
}));
}))
}
/**
@@ -206,7 +217,7 @@ export class AuthService {
* Create a new user
* @returns {User}
*/
public create(user: Eperson): Observable<Eperson> {
public create(user: EPerson): Observable<EPerson> {
// Normally you would do an HTTP request to POST the user
// details and then return the new user object
// but, let's just return the new user for this example.
@@ -357,8 +368,12 @@ export class AuthService {
this.router.navigated = false;
const url = decodeURIComponent(redirectUrl);
this.router.navigateByUrl(url);
/* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */
// this._window.nativeWindow.location.href = url;
} else {
this.router.navigate(['/']);
/* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */
// this._window.nativeWindow.location.href = '/';
}
})

View File

@@ -1,7 +1,8 @@
import { AuthError } from './auth-error.model';
import { AuthTokenInfo } from './auth-token-info.model';
import { DSpaceObject } from '../../shared/dspace-object.model';
import { Eperson } from '../../eperson/models/eperson.model';
import { EPerson } from '../../eperson/models/eperson.model';
import { RemoteData } from '../../data/remote-data';
import { Observable } from 'rxjs/Observable';
export class AuthStatus {
@@ -13,7 +14,7 @@ export class AuthStatus {
error?: AuthError;
eperson: Eperson[];
eperson: Observable<RemoteData<EPerson>>;
token?: AuthTokenInfo;

View File

@@ -1,12 +1,18 @@
import { AuthStatus } from './auth-status.model';
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
import { mapsTo } from '../../cache/builders/build-decorators';
import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
import { Eperson } from '../../eperson/models/eperson.model';
import { mapsTo, relationship } from '../../cache/builders/build-decorators';
import { ResourceType } from '../../shared/resource-type';
import { NormalizedObject } from '../../cache/models/normalized-object.model';
import { IDToUUIDSerializer } from '../../cache/id-to-uuid-serializer';
@mapsTo(AuthStatus)
@inheritSerialization(NormalizedDSpaceObject)
export class NormalizedAuthStatus extends NormalizedDSpaceObject {
@inheritSerialization(NormalizedObject)
export class NormalizedAuthStatus extends NormalizedObject {
@autoserialize
id: string;
@autoserializeAs(new IDToUUIDSerializer('auth-status'), 'id')
uuid: string;
/**
* True if REST API is up and running, should never return false
@@ -20,7 +26,7 @@ export class NormalizedAuthStatus extends NormalizedDSpaceObject {
@autoserialize
authenticated: boolean;
@autoserializeAs(Eperson)
eperson: Eperson[];
@relationship(ResourceType.EPerson, false)
@autoserialize
eperson: string;
}

View File

@@ -1,5 +1,4 @@
import {first, map} from 'rxjs/operators';
import { first, map, switchMap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@@ -10,7 +9,8 @@ import { isNotEmpty } from '../../shared/empty.util';
import { AuthService } from './auth.service';
import { AuthTokenInfo } from './models/auth-token-info.model';
import { CheckAuthenticationTokenAction } from './auth.actions';
import { Eperson } from '../eperson/models/eperson.model';
import { EPerson } from '../eperson/models/eperson.model';
import { NormalizedEPerson } from '../eperson/models/normalized-eperson.model';
/**
* The auth service.
@@ -22,7 +22,7 @@ export class ServerAuthService extends AuthService {
* Returns the authenticated user
* @returns {User}
*/
public authenticatedUser(token: AuthTokenInfo): Observable<Eperson> {
public authenticatedUser(token: AuthTokenInfo): Observable<EPerson> {
// Determine if the user has an existing auth session on the server
const options: HttpOptions = Object.create({});
let headers = new HttpHeaders();
@@ -35,13 +35,18 @@ export class ServerAuthService extends AuthService {
options.headers = headers;
return this.authRequestService.getRequest('status', options).pipe(
map((status: AuthStatus) => {
switchMap((status: AuthStatus) => {
if (status.authenticated) {
return status.eperson[0];
// TODO this should be cleaned up, AuthStatus could be parsed by the RemoteDataService as a whole...
const person$ = this.rdbService.buildSingle<NormalizedEPerson, EPerson>(status.eperson.toString());
// person$.subscribe(() => console.log('test'));
return person$.pipe(map((eperson) => eperson.payload));
} else {
throw(new Error('Not authenticated'));
}
}));
}))
}
/**

View File

@@ -6,7 +6,7 @@ import { getMockResponseCacheService } from '../../shared/mocks/mock-response-ca
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ResponseCacheService } from '../cache/response-cache.service';
import { BrowseEndpointRequest, BrowseEntriesRequest } from '../data/request.models';
import { BrowseEndpointRequest, BrowseEntriesRequest, BrowseItemsRequest } from '../data/request.models';
import { RequestService } from '../data/request.service';
import { BrowseDefinition } from '../shared/browse-definition.model';
import { BrowseService } from './browse.service';
@@ -143,7 +143,9 @@ describe('BrowseService', () => {
});
describe('getBrowseEntriesFor', () => {
describe('getBrowseEntriesFor and getBrowseItemsFor', () => {
const mockAuthorName = 'Donald Smith';
beforeEach(() => {
responseCache = initMockResponseCacheService(true);
requestService = getMockRequestService();
@@ -156,7 +158,7 @@ describe('BrowseService', () => {
spyOn(rdbService, 'toRemoteDataObservable').and.callThrough();
});
describe('when called with a valid browse definition id', () => {
describe('when getBrowseEntriesFor is called with a valid browse definition id', () => {
it('should configure a new BrowseEntriesRequest', () => {
const expected = new BrowseEntriesRequest(requestService.generateRequestId(), browseDefinitions[1]._links.entries);
@@ -175,7 +177,26 @@ describe('BrowseService', () => {
});
describe('when called with an invalid browse definition id', () => {
describe('when getBrowseItemsFor is called with a valid browse definition id', () => {
it('should configure a new BrowseItemsRequest', () => {
const expected = new BrowseItemsRequest(requestService.generateRequestId(), browseDefinitions[1]._links.items + '?filterValue=' + mockAuthorName);
scheduler.schedule(() => service.getBrowseItemsFor(browseDefinitions[1].id, mockAuthorName).subscribe());
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(expected);
});
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
service.getBrowseItemsFor(browseDefinitions[1].id, mockAuthorName);
expect(rdbService.toRemoteDataObservable).toHaveBeenCalled();
});
});
describe('when getBrowseEntriesFor is called with an invalid browse definition id', () => {
it('should throw an Error', () => {
const definitionID = 'invalidID';
@@ -184,6 +205,16 @@ describe('BrowseService', () => {
expect(service.getBrowseEntriesFor(definitionID)).toBeObservable(expected);
});
});
describe('when getBrowseItemsFor is called with an invalid browse definition id', () => {
it('should throw an Error', () => {
const definitionID = 'invalidID';
const expected = cold('--#-', undefined, new Error(`No metadata browse definition could be found for id '${definitionID}'`))
expect(service.getBrowseItemsFor(definitionID, mockAuthorName)).toBeObservable(expected);
});
});
});
describe('getBrowseURLFor', () => {

View File

@@ -16,19 +16,27 @@ import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { ResponseCacheService } from '../cache/response-cache.service';
import { PaginatedList } from '../data/paginated-list';
import { RemoteData } from '../data/remote-data';
import { BrowseEndpointRequest, BrowseEntriesRequest, RestRequest } from '../data/request.models';
import {
BrowseEndpointRequest,
BrowseEntriesRequest,
BrowseItemsRequest,
GetRequest,
RestRequest
} from '../data/request.models';
import { RequestService } from '../data/request.service';
import { BrowseDefinition } from '../shared/browse-definition.model';
import { BrowseEntry } from '../shared/browse-entry.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import {
configureRequest,
filterSuccessfulResponses,
filterSuccessfulResponses, getBrowseDefinitionLinks,
getRemoteDataPayload,
getRequestFromSelflink,
getResponseFromSelflink
} from '../shared/operators';
import { URLCombiner } from '../url-combiner/url-combiner';
import { Item } from '../shared/item.model';
import { DSpaceObject } from '../shared/dspace-object.model';
@Injectable()
export class BrowseService {
@@ -71,6 +79,8 @@ export class BrowseService {
map((entry: ResponseCacheEntry) => entry.response),
map((response: GenericSuccessResponse<BrowseDefinition[]>) => response.payload),
ensureArrayHasValue(),
map((definitions: BrowseDefinition[]) => definitions
.map((definition: BrowseDefinition) => Object.assign(new BrowseDefinition(), definition))),
distinctUntilChanged()
);
@@ -82,17 +92,7 @@ export class BrowseService {
sort?: SortOptions;
} = {}): Observable<RemoteData<PaginatedList<BrowseEntry>>> {
const request$ = this.getBrowseDefinitions().pipe(
getRemoteDataPayload(),
map((browseDefinitions: BrowseDefinition[]) => browseDefinitions
.find((def: BrowseDefinition) => def.id === definitionID && def.metadataBrowse === true)
),
map((def: BrowseDefinition) => {
if (isNotEmpty(def)) {
return def._links;
} else {
throw new Error(`No metadata browse definition could be found for id '${definitionID}'`);
}
}),
getBrowseDefinitionLinks(definitionID),
hasValueOperator(),
map((_links: any) => _links.entries),
hasValueOperator(),
@@ -124,6 +124,66 @@ export class BrowseService {
filterSuccessfulResponses(),
map((entry: ResponseCacheEntry) => entry.response),
map((response: GenericSuccessResponse<BrowseEntry[]>) => new PaginatedList(response.pageInfo, response.payload)),
map((list: PaginatedList<BrowseEntry>) => Object.assign(list, {
page: list.page ? list.page.map((entry: BrowseEntry) => Object.assign(new BrowseEntry(), entry)) : list.page
})),
distinctUntilChanged()
);
return this.rdb.toRemoteDataObservable(requestEntry$, responseCache$, payload$);
}
/**
* Get all items linked to a certain metadata value
* @param {string} definitionID definition ID to define the metadata-field (e.g. author)
* @param {string} filterValue metadata value to filter by (e.g. author's name)
* @param options Options to narrow down your search:
* { pagination: PaginationComponentOptions,
* sort: SortOptions }
* @returns {Observable<RemoteData<PaginatedList<Item>>>}
*/
getBrowseItemsFor(definitionID: string, filterValue: string, options: {
pagination?: PaginationComponentOptions;
sort?: SortOptions;
} = {}): Observable<RemoteData<PaginatedList<Item>>> {
const request$ = this.getBrowseDefinitions().pipe(
getBrowseDefinitionLinks(definitionID),
hasValueOperator(),
map((_links: any) => _links.items),
hasValueOperator(),
map((href: string) => {
const args = [];
if (isNotEmpty(options.sort)) {
args.push(`sort=${options.sort.field},${options.sort.direction}`);
}
if (isNotEmpty(options.pagination)) {
args.push(`page=${options.pagination.currentPage - 1}`);
args.push(`size=${options.pagination.pageSize}`);
}
if (isNotEmpty(filterValue)) {
args.push(`filterValue=${filterValue}`);
}
if (isNotEmpty(args)) {
href = new URLCombiner(href, `?${args.join('&')}`).toString();
}
return href;
}),
map((endpointURL: string) => new BrowseItemsRequest(this.requestService.generateRequestId(), endpointURL)),
configureRequest(this.requestService)
);
const href$ = request$.pipe(map((request: RestRequest) => request.href));
const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService));
const responseCache$ = href$.pipe(getResponseFromSelflink(this.responseCache));
const payload$ = responseCache$.pipe(
filterSuccessfulResponses(),
map((entry: ResponseCacheEntry) => entry.response),
map((response: GenericSuccessResponse<Item[]>) => new PaginatedList(response.pageInfo, response.payload)),
map((list: PaginatedList<Item>) => Object.assign(list, {
page: list.page ? list.page.map((item: DSpaceObject) => Object.assign(new Item(), item)) : list.page
})),
distinctUntilChanged()
);

View File

@@ -8,6 +8,8 @@ import { ResourceType } from '../../shared/resource-type';
import { NormalizedObject } from './normalized-object.model';
import { NormalizedBitstreamFormat } from './normalized-bitstream-format.model';
import { NormalizedResourcePolicy } from './normalized-resource-policy.model';
import { NormalizedEPerson } from '../../eperson/models/normalized-eperson.model';
import { NormalizedGroup } from '../../eperson/models/normalized-group.model';
export class NormalizedObjectFactory {
public static getConstructor(type: ResourceType): GenericConstructor<NormalizedObject> {
@@ -33,6 +35,12 @@ export class NormalizedObjectFactory {
case ResourceType.ResourcePolicy: {
return NormalizedResourcePolicy
}
case ResourceType.EPerson: {
return NormalizedEPerson
}
case ResourceType.Group: {
return NormalizedGroup
}
default: {
return undefined;
}

View File

@@ -62,6 +62,7 @@ import { RegistryMetadatafieldsResponseParsingService } from './data/registry-me
import { RegistryBitstreamformatsResponseParsingService } from './data/registry-bitstreamformats-response-parsing.service';
import { NotificationsService } from '../shared/notifications/notifications.service';
import { UploaderService } from '../shared/uploader/uploader.service';
import { BrowseItemsResponseParsingService } from './data/browse-items-response-parsing-service';
import { DSpaceObjectDataService } from './data/dspace-object-data.service';
const IMPORTS = [
@@ -115,6 +116,7 @@ const PROVIDERS = [
ServerResponseService,
BrowseResponseParsingService,
BrowseEntriesResponseParsingService,
BrowseItemsResponseParsingService,
BrowseService,
ConfigResponseParsingService,
RouteService,

View File

@@ -7,6 +7,8 @@ import { GlobalConfig } from '../../../config/global-config.interface';
import { GenericConstructor } from '../shared/generic-constructor';
import { PaginatedList } from './paginated-list';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { ResourceType } from '../shared/resource-type';
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
function isObjectLevel(halObj: any) {
return isNotEmpty(halObj._links) && hasValue(halObj._links.self);
@@ -34,6 +36,7 @@ export abstract class BaseResponseParsingService {
} else if (Array.isArray(data)) {
return this.processArray(data, requestHref);
} else if (isObjectLevel(data)) {
data = this.fixBadEPersonRestResponse(data);
const object = this.deserialize(data);
if (isNotEmpty(data._embedded)) {
Object
@@ -53,6 +56,7 @@ export abstract class BaseResponseParsingService {
}
});
}
this.cache(object, requestHref);
return object;
}
@@ -145,4 +149,23 @@ export abstract class BaseResponseParsingService {
}
return obj[keys[0]];
}
// TODO Remove when https://jira.duraspace.org/browse/DS-4006 is fixed
// See https://github.com/DSpace/dspace-angular/issues/292
private fixBadEPersonRestResponse(obj: any): any {
if (obj.type === ResourceType.EPerson) {
const groups = obj.groups;
const normGroups = [];
if (isNotEmpty(groups)) {
groups.forEach((group) => {
const parts = ['eperson', 'groups', group.uuid];
const href = new RESTURLCombiner(this.EnvConfig, ...parts).toString();
normGroups.push(href);
}
)
}
return Object.assign({}, obj, { groups: normGroups });
}
return obj;
}
}

View File

@@ -0,0 +1,168 @@
import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service';
import { ErrorResponse, GenericSuccessResponse } from '../cache/response-cache.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { BrowseEntriesResponseParsingService } from './browse-entries-response-parsing.service';
import { BrowseEntriesRequest, BrowseItemsRequest } from './request.models';
import { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service';
describe('BrowseItemsResponseParsingService', () => {
let service: BrowseItemsResponseParsingService;
beforeEach(() => {
service = new BrowseItemsResponseParsingService(undefined, getMockObjectCacheService());
});
describe('parse', () => {
const request = new BrowseItemsRequest('client/f5b4ccb8-fbb0-4548-b558-f234d9fdfad6', 'https://rest.api/discover/browses/author/items');
const validResponse = {
payload: {
_embedded: {
items: [
{
id: 'd7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7',
uuid: 'd7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7',
name: 'Development of Local Supply Chain : A Critical Link for Concentrated Solar Power in India',
handle: '10986/17472',
metadata: [
{
key: 'dc.creator',
value: 'World Bank',
language: null
}
],
inArchive: true,
discoverable: true,
withdrawn: false,
lastModified: '2018-05-25T09:32:58.005+0000',
type: 'item',
_links: {
bitstreams: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/bitstreams'
},
owningCollection: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/owningCollection'
},
templateItemOf: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/templateItemOf'
},
self: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7'
}
}
},
{
id: '27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b',
uuid: '27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b',
name: 'Development of Local Supply Chain : The Missing Link for Concentrated Solar Power Projects in India',
handle: '10986/17475',
metadata: [
{
key: 'dc.creator',
value: 'World Bank',
language: null
}
],
inArchive: true,
discoverable: true,
withdrawn: false,
lastModified: '2018-05-25T09:33:42.526+0000',
type: 'item',
_links: {
bitstreams: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b/bitstreams'
},
owningCollection: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b/owningCollection'
},
templateItemOf: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b/templateItemOf'
},
self: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b'
}
}
}
]
},
_links: {
first: {
href: 'https://dspace7-internal.atmire.com/rest/api/discover/browses/author/items?page=0&size=2'
},
self: {
href: 'https://dspace7-internal.atmire.com/rest/api/discover/browses/author/items'
},
next: {
href: 'https://dspace7-internal.atmire.com/rest/api/discover/browses/author/items?page=1&size=2'
},
last: {
href: 'https://dspace7-internal.atmire.com/rest/api/discover/browses/author/items?page=7&size=2'
}
},
page: {
size: 2,
totalElements: 16,
totalPages: 8,
number: 0
}
},
statusCode: '200'
} as DSpaceRESTV2Response;
const invalidResponseNotAList = {
payload: {
id: 'd7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7',
uuid: 'd7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7',
name: 'Development of Local Supply Chain : A Critical Link for Concentrated Solar Power in India',
handle: '10986/17472',
metadata: [
{
key: 'dc.creator',
value: 'World Bank',
language: null
}
],
inArchive: true,
discoverable: true,
withdrawn: false,
lastModified: '2018-05-25T09:32:58.005+0000',
type: 'item',
_links: {
bitstreams: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/bitstreams'
},
owningCollection: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/owningCollection'
},
templateItemOf: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/templateItemOf'
},
self: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7'
}
}
},
statusCode: '200'
} as DSpaceRESTV2Response;
const invalidResponseStatusCode = {
payload: {}, statusCode: '500'
} as DSpaceRESTV2Response;
it('should return a GenericSuccessResponse if data contains a valid browse items response', () => {
const response = service.parse(request, validResponse);
expect(response.constructor).toBe(GenericSuccessResponse);
});
it('should return an ErrorResponse if data contains an invalid browse entries response', () => {
const response = service.parse(request, invalidResponseNotAList);
expect(response.constructor).toBe(ErrorResponse);
});
it('should return an ErrorResponse if data contains a statuscode other than 200', () => {
const response = service.parse(request, invalidResponseStatusCode);
expect(response.constructor).toBe(ErrorResponse);
});
});
});

View File

@@ -0,0 +1,58 @@
import { Inject, Injectable } from '@angular/core';
import { GLOBAL_CONFIG } from '../../../config';
import { GlobalConfig } from '../../../config/global-config.interface';
import { isNotEmpty } from '../../shared/empty.util';
import { ObjectCacheService } from '../cache/object-cache.service';
import {
ErrorResponse,
GenericSuccessResponse,
RestResponse
} from '../cache/response-cache.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { BaseResponseParsingService } from './base-response-parsing.service';
import { ResponseParsingService } from './parsing.service';
import { RestRequest } from './request.models';
import { Item } from '../shared/item.model';
import { DSpaceObject } from '../shared/dspace-object.model';
/**
* A ResponseParsingService used to parse DSpaceRESTV2Response coming from the REST API to Browse Items (DSpaceObject[])
*/
@Injectable()
export class BrowseItemsResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
protected objectFactory = {
getConstructor: () => DSpaceObject
};
protected toCache = false;
constructor(
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
protected objectCache: ObjectCacheService,
) { super();
}
/**
* Parses data from the browse endpoint to a list of DSpaceObjects
* @param {RestRequest} request
* @param {DSpaceRESTV2Response} data
* @returns {RestResponse}
*/
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._embedded)
&& Array.isArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]])) {
const serializer = new DSpaceRESTv2Serializer(DSpaceObject);
const items = serializer.deserializeArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]]);
return new GenericSuccessResponse(items, data.statusCode, this.processPageInfo(data.payload));
} else {
return new ErrorResponse(
Object.assign(
new Error('Unexpected response from browse endpoint'),
{ statusText: data.statusCode }
)
);
}
}
}

View File

@@ -9,7 +9,7 @@ import { ResponseCacheService } from '../cache/response-cache.service';
import { CoreState } from '../core.reducers';
import { ComColDataService } from './comcol-data.service';
import { CommunityDataService } from './community-data.service';
import { FindByIDRequest } from './request.models';
import { FindAllOptions, FindByIDRequest } from './request.models';
import { RequestService } from './request.service';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
@@ -52,6 +52,10 @@ describe('ComColDataService', () => {
const EnvConfig = {} as GlobalConfig;
const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d';
const options = Object.assign(new FindAllOptions(), {
scopeID: scopeID
});
const communitiesEndpoint = 'https://rest.api/core/communities';
const communityEndpoint = `${communitiesEndpoint}/${scopeID}`;
const scopedEndpoint = `${communityEndpoint}/${LINK_NAME}`;
@@ -98,7 +102,7 @@ describe('ComColDataService', () => {
);
}
describe('getScopedEndpoint', () => {
describe('getBrowseEndpoint', () => {
beforeEach(() => {
scheduler = getTestScheduler();
});
@@ -112,7 +116,7 @@ describe('ComColDataService', () => {
const expected = new FindByIDRequest(requestService.generateRequestId(), communityEndpoint, scopeID);
scheduler.schedule(() => service.getScopedEndpoint(scopeID).subscribe());
scheduler.schedule(() => service.getBrowseEndpoint(options).subscribe());
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(expected);
@@ -128,13 +132,13 @@ describe('ComColDataService', () => {
});
it('should fetch the scope Community from the cache', () => {
scheduler.schedule(() => service.getScopedEndpoint(scopeID).subscribe());
scheduler.schedule(() => service.getBrowseEndpoint(options).subscribe());
scheduler.flush();
expect(objectCache.getByUUID).toHaveBeenCalledWith(scopeID);
});
it('should return the endpoint to fetch resources within the given scope', () => {
const result = service.getScopedEndpoint(scopeID);
const result = service.getBrowseEndpoint(options);
const expected = cold('--e-', { e: scopedEndpoint });
expect(result).toBeObservable(expected);
@@ -151,7 +155,7 @@ describe('ComColDataService', () => {
});
it('should throw an error', () => {
const result = service.getScopedEndpoint(scopeID);
const result = service.getBrowseEndpoint(options);
const expected = cold('--#-', undefined, new Error(`The Community with scope ${scopeID} couldn't be retrieved`));
expect(result).toBeObservable(expected);

View File

@@ -7,7 +7,7 @@ import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { CommunityDataService } from './community-data.service';
import { DataService } from './data.service';
import { FindByIDRequest } from './request.models';
import { FindAllOptions, FindByIDRequest } from './request.models';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { DSOSuccessResponse } from '../cache/response-cache.models';
@@ -27,17 +27,18 @@ export abstract class ComColDataService<TNormalized extends NormalizedObject, TD
* @return { Observable<string> }
* an Observable<string> containing the scoped URL
*/
public getScopedEndpoint(scopeID: string): Observable<string> {
if (isEmpty(scopeID)) {
public getBrowseEndpoint(options: FindAllOptions = {}): Observable<string> {
if (isEmpty(options.scopeID)) {
return this.halService.getEndpoint(this.linkPath);
} else {
const scopeCommunityHrefObs = this.cds.getEndpoint().pipe(
mergeMap((endpoint: string) => this.cds.getFindByIDHref(endpoint, scopeID)),
first((href: string) => isNotEmpty(href)),
tap((href: string) => {
const request = new FindByIDRequest(this.requestService.generateRequestId(), href, scopeID);
const scopeCommunityHrefObs = this.cds.getEndpoint()
.flatMap((endpoint: string) => this.cds.getFindByIDHref(endpoint, options.scopeID))
.filter((href: string) => isNotEmpty(href))
.take(1)
.do((href: string) => {
const request = new FindByIDRequest(this.requestService.generateRequestId(), href, options.scopeID);
this.requestService.configure(request);
}),);
});
// return scopeCommunityHrefObs.pipe(
// mergeMap((href: string) => this.responseCache.get(href)),
@@ -61,16 +62,15 @@ export abstract class ComColDataService<TNormalized extends NormalizedObject, TD
map((entry: ResponseCacheEntry) => entry.response));
const errorResponses = responses.pipe(
filter((response) => !response.isSuccessful),
mergeMap(() => observableThrowError(new Error(`The Community with scope ${scopeID} couldn't be retrieved`)))
mergeMap(() => observableThrowError(new Error(`The Community with scope ${options.scopeID} couldn't be retrieved`)))
);
const successResponses = responses.pipe(
filter((response) => response.isSuccessful),
mergeMap(() => this.objectCache.getByUUID(scopeID)),
mergeMap(() => this.objectCache.getByUUID(options.scopeID)),
map((nc: NormalizedCommunity) => nc._links[this.linkPath]),
filter((href) => isNotEmpty(href))
);
return observableMerge(errorResponses, successResponses).pipe(distinctUntilChanged());
}
}

View File

@@ -10,7 +10,7 @@ import { Observable } from 'rxjs';
import { FindAllOptions } from './request.models';
import { SortOptions, SortDirection } from '../cache/models/sort-options.model';
const LINK_NAME = 'test'
const endpoint = 'https://rest.api/core';
// tslint:disable:max-classes-per-file
class NormalizedTestObject extends NormalizedObject {
@@ -28,10 +28,9 @@ class TestService extends DataService<NormalizedTestObject, any> {
super();
}
public getScopedEndpoint(scope: string): Observable<string> {
throw new Error('getScopedEndpoint is abstract in DataService');
public getBrowseEndpoint(options: FindAllOptions): Observable<string> {
return Observable.of(endpoint);
}
}
describe('DataService', () => {
@@ -42,7 +41,6 @@ describe('DataService', () => {
const halService = {} as HALEndpointService;
const rdbService = {} as RemoteDataBuildService;
const store = {} as Store<CoreState>;
const endpoint = 'https://rest.api/core';
function initTestService(): TestService {
return new TestService(
@@ -50,7 +48,7 @@ describe('DataService', () => {
requestService,
rdbService,
store,
LINK_NAME,
endpoint,
halService
);
}
@@ -62,25 +60,17 @@ describe('DataService', () => {
it('should return an observable with the endpoint', () => {
options = {};
(service as any).getFindAllHref(endpoint).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(endpoint);
}
);
});
// getScopedEndpoint is not implemented in abstract DataService
it('should throw error if scopeID provided in options', () => {
options = { scopeID: 'somevalue' };
expect(() => { (service as any).getFindAllHref(endpoint, options) })
.toThrowError('getScopedEndpoint is abstract in DataService');
});
it('should include page in href if currentPage provided in options', () => {
options = { currentPage: 2 };
const expected = `${endpoint}?page=${options.currentPage - 1}`;
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -89,7 +79,7 @@ describe('DataService', () => {
options = { elementsPerPage: 5 };
const expected = `${endpoint}?size=${options.elementsPerPage}`;
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -99,7 +89,7 @@ describe('DataService', () => {
options = { sort: sortOptions};
const expected = `${endpoint}?sort=${sortOptions.field},${sortOptions.direction}`;
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -108,7 +98,7 @@ describe('DataService', () => {
options = { startsWith: 'ab' };
const expected = `${endpoint}?startsWith=${options.startsWith}`;
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -124,7 +114,7 @@ describe('DataService', () => {
const expected = `${endpoint}?page=${options.currentPage - 1}&size=${options.elementsPerPage}` +
`&sort=${sortOptions.field},${sortOptions.direction}&startsWith=${options.startsWith}`;
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(expected);
});
})

View File

@@ -1,7 +1,5 @@
import { filter, take, first } from 'rxjs/operators';
import {of as observableOf, Observable } from 'rxjs';
import {mergeMap, first, take, distinctUntilChanged, map, filter} from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
@@ -14,6 +12,8 @@ import { RemoteData } from './remote-data';
import { FindAllOptions, FindAllRequest, FindByIDRequest, GetRequest } from './request.models';
import { RequestService } from './request.service';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { promise } from 'selenium-webdriver';
import map = promise.map;
export abstract class DataService<TNormalized extends NormalizedObject, TDomain> {
protected abstract responseCache: ResponseCacheService;
@@ -23,17 +23,13 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
protected abstract linkPath: string;
protected abstract halService: HALEndpointService;
public abstract getScopedEndpoint(scope: string): Observable<string>
public abstract getBrowseEndpoint(options: FindAllOptions): Observable<string>
protected getFindAllHref(endpoint, options: FindAllOptions = {}): Observable<string> {
protected getFindAllHref(options: FindAllOptions = {}): Observable<string> {
let result: Observable<string>;
const args = [];
if (hasValue(options.scopeID)) {
result = this.getScopedEndpoint(options.scopeID).pipe(distinctUntilChanged());
} else {
result = observableOf(endpoint);
}
result = this.getBrowseEndpoint(options).distinctUntilChanged();
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
/* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */
@@ -60,12 +56,11 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
}
findAll(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<TDomain>>> {
const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(filter((href: string) => isNotEmpty(href)),
mergeMap((endpoint: string) => this.getFindAllHref(endpoint, options)),);
const hrefObs = this.getFindAllHref(options);
hrefObs.pipe(
filter((href: string) => hasValue(href)),
take(1),)
take(1))
.subscribe((href: string) => {
const request = new FindAllRequest(this.requestService.generateRequestId(), href, options);
this.requestService.configure(request);

View File

@@ -10,6 +10,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
import { DataService } from './data.service';
import { RemoteData } from './remote-data';
import { RequestService } from './request.service';
import { FindAllOptions } from './request.models';
/* tslint:disable:max-classes-per-file */
class DataServiceImpl extends DataService<NormalizedDSpaceObject, DSpaceObject> {
@@ -24,8 +25,8 @@ class DataServiceImpl extends DataService<NormalizedDSpaceObject, DSpaceObject>
super();
}
getScopedEndpoint(scope: string): Observable<string> {
return undefined;
getBrowseEndpoint(options: FindAllOptions): Observable<string> {
return this.halService.getEndpoint(this.linkPath);
}
getFindByIDHref(endpoint, resourceID): string {

View File

@@ -8,6 +8,7 @@ import { CoreState } from '../core.reducers';
import { ItemDataService } from './item-data.service';
import { RequestService } from './request.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { FindAllOptions } from './request.models';
describe('ItemDataService', () => {
let scheduler: TestScheduler;
@@ -20,6 +21,14 @@ describe('ItemDataService', () => {
const halEndpointService = {} as HALEndpointService;
const scopeID = '4af28e99-6a9c-4036-a199-e1b587046d39';
const options = Object.assign(new FindAllOptions(), {
scopeID: scopeID,
sort: {
field: '',
direction: undefined
}
});
const browsesEndpoint = 'https://rest.api/discover/browses';
const itemBrowseEndpoint = `${browsesEndpoint}/author/items`;
const scopedEndpoint = `${itemBrowseEndpoint}?scope=${scopeID}`;
@@ -46,16 +55,16 @@ describe('ItemDataService', () => {
);
}
describe('getScopedEndpoint', () => {
describe('getBrowseEndpoint', () => {
beforeEach(() => {
scheduler = getTestScheduler();
});
it('should return the endpoint to fetch Items within the given scope', () => {
it('should return the endpoint to fetch Items within the given scope and starting with the given string', () => {
bs = initMockBrowseService(true);
service = initTestService();
const result = service.getScopedEndpoint(scopeID);
const result = service.getBrowseEndpoint(options);
const expected = cold('--b-', { b: scopedEndpoint });
expect(result).toBeObservable(expected);
@@ -67,7 +76,7 @@ describe('ItemDataService', () => {
service = initTestService();
});
it('should throw an error', () => {
const result = service.getScopedEndpoint(scopeID);
const result = service.getBrowseEndpoint(options);
const expected = cold('--#-', undefined, browseError);
expect(result).toBeObservable(expected);

View File

@@ -1,11 +1,7 @@
import {distinctUntilChanged, map, filter} from 'rxjs/operators';
import { Inject, Injectable } from '@angular/core';
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { isNotEmpty } from '../../shared/empty.util';
import { BrowseService } from '../browse/browse.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedItem } from '../cache/models/normalized-item.model';
@@ -17,6 +13,7 @@ import { URLCombiner } from '../url-combiner/url-combiner';
import { DataService } from './data.service';
import { RequestService } from './request.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { FindAllOptions } from './request.models';
@Injectable()
export class ItemDataService extends DataService<NormalizedItem, Item> {
@@ -32,15 +29,21 @@ export class ItemDataService extends DataService<NormalizedItem, Item> {
super();
}
public getScopedEndpoint(scopeID: string): Observable<string> {
if (isEmpty(scopeID)) {
return this.halService.getEndpoint(this.linkPath);
} else {
return this.bs.getBrowseURLFor('dc.date.issued', this.linkPath).pipe(
filter((href: string) => isNotEmpty(href)),
map((href: string) => new URLCombiner(href, `?scope=${scopeID}`).toString()),
distinctUntilChanged(),);
/**
* Get the endpoint for browsing items
* (When options.sort.field is empty, the default field to browse by will be 'dc.date.issued')
* @param {FindAllOptions} options
* @returns {Observable<string>}
*/
public getBrowseEndpoint(options: FindAllOptions = {}): Observable<string> {
let field = 'dc.date.issued';
if (options.sort && options.sort.field) {
field = options.sort.field;
}
return this.bs.getBrowseURLFor(field, this.linkPath)
.filter((href: string) => isNotEmpty(href))
.map((href: string) => new URLCombiner(href, `?scope=${options.scopeID}`).toString())
.distinctUntilChanged();
}
}

View File

@@ -12,6 +12,7 @@ import { AuthResponseParsingService } from '../auth/auth-response-parsing.servic
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { HttpHeaders } from '@angular/common/http';
import { IntegrationResponseParsingService } from '../integration/integration-response-parsing.service';
import { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service';
/* tslint:disable:max-classes-per-file */
@@ -184,6 +185,12 @@ export class BrowseEntriesRequest extends GetRequest {
}
}
export class BrowseItemsRequest extends GetRequest {
getResponseParser(): GenericConstructor<ResponseParsingService> {
return BrowseItemsResponseParsingService;
}
}
export class ConfigRequest extends GetRequest {
constructor(uuid: string, href: string) {
super(uuid, href);

View File

@@ -1,7 +1,7 @@
import { DSpaceObject } from '../../shared/dspace-object.model';
import { Group } from './group.model';
export class Eperson extends DSpaceObject {
export class EPerson extends DSpaceObject {
public handle: string;

View File

@@ -2,13 +2,13 @@ import { autoserialize, inheritSerialization } from 'cerialize';
import { CacheableObject } from '../../cache/object-cache.reducer';
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
import { Eperson } from './eperson.model';
import { EPerson } from './eperson.model';
import { mapsTo, relationship } from '../../cache/builders/build-decorators';
import { ResourceType } from '../../shared/resource-type';
@mapsTo(Eperson)
@mapsTo(EPerson)
@inheritSerialization(NormalizedDSpaceObject)
export class NormalizedEpersonModel extends NormalizedDSpaceObject implements CacheableObject, ListableObject {
export class NormalizedEPerson extends NormalizedDSpaceObject implements CacheableObject, ListableObject {
@autoserialize
public handle: string;

View File

@@ -2,13 +2,12 @@ import { autoserialize, inheritSerialization } from 'cerialize';
import { CacheableObject } from '../../cache/object-cache.reducer';
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
import { Eperson } from './eperson.model';
import { mapsTo } from '../../cache/builders/build-decorators';
import { Group } from './group.model';
@mapsTo(Group)
@inheritSerialization(NormalizedDSpaceObject)
export class NormalizedGroupModel extends NormalizedDSpaceObject implements CacheableObject, ListableObject {
export class NormalizedGroup extends NormalizedDSpaceObject implements CacheableObject, ListableObject {
@autoserialize
public handle: string;

View File

@@ -1,6 +1,7 @@
import { autoserialize, autoserializeAs } from 'cerialize';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
export class BrowseEntry {
export class BrowseEntry implements ListableObject {
@autoserialize
type: string;

View File

@@ -5,6 +5,7 @@ import { RemoteData } from '../data/remote-data';
import { ResourceType } from './resource-type';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { Observable } from 'rxjs';
import { autoserialize } from 'cerialize';
/**
* An abstract model class for a DSpaceObject.
@@ -16,11 +17,13 @@ export class DSpaceObject implements CacheableObject, ListableObject {
/**
* The human-readable identifier of this DSpaceObject
*/
@autoserialize
id: string;
/**
* The universally unique identifier of this DSpaceObject
*/
@autoserialize
uuid: string;
/**
@@ -31,11 +34,13 @@ export class DSpaceObject implements CacheableObject, ListableObject {
/**
* The name for this DSpaceObject
*/
@autoserialize
name: string;
/**
* An array containing all metadata of this DSpaceObject
*/
@autoserialize
metadata: Metadatum[];
/**

View File

@@ -1,6 +1,6 @@
import { Observable } from 'rxjs';
import { filter, first, flatMap, map, tap } from 'rxjs/operators';
import { hasValueOperator } from '../../shared/empty.util';
import { hasValueOperator, isNotEmpty } from '../../shared/empty.util';
import { DSOSuccessResponse } from '../cache/response-cache.models';
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { ResponseCacheService } from '../cache/response-cache.service';
@@ -8,6 +8,7 @@ import { RemoteData } from '../data/remote-data';
import { RestRequest } from '../data/request.models';
import { RequestEntry } from '../data/request.reducer';
import { RequestService } from '../data/request.service';
import { BrowseDefinition } from './browse-definition.model';
import { DSpaceObject } from './dspace-object.model';
import { PaginatedList } from '../data/paginated-list';
import { SearchResult } from '../../+search-page/search-result.model';
@@ -62,3 +63,24 @@ export const toDSpaceObjectListRD = () =>
return Object.assign(rd, {payload: payload});
})
);
/**
* Get the browse links from a definition by ID given an array of all definitions
* @param {string} definitionID
* @returns {(source: Observable<RemoteData<BrowseDefinition[]>>) => Observable<any>}
*/
export const getBrowseDefinitionLinks = (definitionID: string) =>
(source: Observable<RemoteData<BrowseDefinition[]>>): Observable<any> =>
source.pipe(
getRemoteDataPayload(),
map((browseDefinitions: BrowseDefinition[]) => browseDefinitions
.find((def: BrowseDefinition) => def.id === definitionID && def.metadataBrowse === true)
),
map((def: BrowseDefinition) => {
if (isNotEmpty(def)) {
return def._links;
} else {
throw new Error(`No metadata browse definition could be found for id '${definitionID}'`);
}
})
);

View File

@@ -6,7 +6,7 @@ export enum ResourceType {
Item = 'item',
Collection = 'collection',
Community = 'community',
Eperson = 'eperson',
EPerson = 'eperson',
Group = 'group',
ResourcePolicy = 'resourcePolicy'
}

View File

@@ -20,6 +20,8 @@ import { RouterStub } from '../shared/testing/router-stub';
import { Router } from '@angular/router';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import * as ngrx from '@ngrx/store';
import { NO_ERRORS_SCHEMA } from '@angular/core';
let comp: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>;
let store: Store<HeaderState>;
@@ -35,11 +37,12 @@ describe('HeaderComponent', () => {
NgbCollapseModule.forRoot(),
NoopAnimationsModule,
ReactiveFormsModule],
declarations: [HeaderComponent, AuthNavMenuComponent, LoadingComponent, LogInComponent, LogOutComponent],
declarations: [HeaderComponent],
providers: [
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
{ provide: Router, useClass: RouterStub },
]
],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents(); // compile template and css
}));

View File

@@ -5,7 +5,7 @@ import { By } from '@angular/platform-browser';
import { Store, StoreModule } from '@ngrx/store';
import { authReducer, AuthState } from '../../core/auth/auth.reducer';
import { EpersonMock } from '../testing/eperson-mock';
import { EPersonMock } from '../testing/eperson-mock';
import { TranslateModule } from '@ngx-translate/core';
import { AppState } from '../../app.reducer';
import { AuthNavMenuComponent } from './auth-nav-menu.component';
@@ -13,6 +13,7 @@ import { HostWindowServiceStub } from '../testing/host-window-service-stub';
import { HostWindowService } from '../host-window.service';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model';
import { AuthService } from '../../core/auth/auth.service';
describe('AuthNavMenuComponent', () => {
@@ -31,7 +32,7 @@ describe('AuthNavMenuComponent', () => {
loaded: true,
loading: false,
authToken: new AuthTokenInfo('test_token'),
user: EpersonMock
user: EPersonMock
};
let routerState = {
url: '/home'
@@ -53,6 +54,7 @@ describe('AuthNavMenuComponent', () => {
],
providers: [
{provide: HostWindowService, useValue: window},
{provide: AuthService, useValue: {setRedirectUrl: () => { /*empty*/ }}}
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
@@ -222,6 +224,7 @@ describe('AuthNavMenuComponent', () => {
],
providers: [
{provide: HostWindowService, useValue: window},
{provide: AuthService, useValue: {setRedirectUrl: () => { /*empty*/ }}}
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA

View File

@@ -14,8 +14,9 @@ import {
isAuthenticated,
isAuthenticationLoading
} from '../../core/auth/selectors';
import { Eperson } from '../../core/eperson/models/eperson.model';
import { LOGIN_ROUTE, LOGOUT_ROUTE } from '../../core/auth/auth.service';
import { EPerson } from '../../core/eperson/models/eperson.model';
import { AuthService, LOGIN_ROUTE, LOGOUT_ROUTE } from '../../core/auth/auth.service';
import { Subscription } from 'rxjs/Subscription';
@Component({
selector: 'ds-auth-nav-menu',
@@ -40,10 +41,14 @@ export class AuthNavMenuComponent implements OnInit {
public showAuth = observableOf(false);
public user: Observable<Eperson>;
public user: Observable<EPerson>;
public sub: Subscription;
constructor(private store: Store<AppState>,
private windowService: HostWindowService) {
private windowService: HostWindowService,
private authService: AuthService
) {
this.isXsOrSm$ = this.windowService.isXsOrSm();
}
@@ -56,12 +61,15 @@ export class AuthNavMenuComponent implements OnInit {
this.user = this.store.pipe(select(getAuthenticatedUser));
this.showAuth = this.store.pipe(
select(routerStateSelector),
filter((router: RouterReducerState) => isNotUndefined(router) && isNotUndefined(router.state)),
map((router: RouterReducerState) => {
return !router.state.url.startsWith(LOGIN_ROUTE) && !router.state.url.startsWith(LOGOUT_ROUTE);
})
);
this.showAuth = this.store.select(routerStateSelector)
.filter((router: RouterReducerState) => isNotUndefined(router) && isNotUndefined(router.state))
.map((router: RouterReducerState) => {
const url = router.state.url;
const show = !router.state.url.startsWith(LOGIN_ROUTE) && !router.state.url.startsWith(LOGOUT_ROUTE);
if (show) {
this.authService.setRedirectUrl(url);
}
return show;
});
}
}

View File

@@ -0,0 +1,12 @@
<ng-container *ngVar="(objects$ | async) as objects">
<h2 class="w-100">{{title}}</h2>
<div *ngIf="objects?.hasSucceeded && !objects?.isLoading && objects?.payload?.page.length > 0" @fadeIn>
<ds-viewable-collection
[config]="paginationConfig"
[sortConfig]="sortConfig"
[objects]="objects">
</ds-viewable-collection>
</div>
<ds-loading *ngIf="!objects || objects?.payload?.page.length <= 0" message="{{'loading.browse-by' | translate}}"></ds-loading>
<ds-error *ngIf="objects?.hasFailed" message="{{'error.browse-by' | translate}}"></ds-error>
</ng-container>

View File

@@ -0,0 +1,44 @@
import { BrowseByComponent } from './browse-by.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { By } from '@angular/platform-browser';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { SharedModule } from '../shared.module';
describe('BrowseByComponent', () => {
let comp: BrowseByComponent;
let fixture: ComponentFixture<BrowseByComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), SharedModule],
declarations: [],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BrowseByComponent);
comp = fixture.componentInstance;
});
it('should display a loading message when objects is empty',() => {
(comp as any).objects = undefined;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('ds-loading'))).toBeDefined();
});
it('should display results when objects is not empty', () => {
(comp as any).objects = Observable.of({
payload: {
page: {
length: 1
}
}
});
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('ds-viewable-collection'))).toBeDefined();
});
});

View File

@@ -0,0 +1,30 @@
import { Component, Input } from '@angular/core';
import { RemoteData } from '../../core/data/remote-data';
import { PaginatedList } from '../../core/data/paginated-list';
import { PaginationComponentOptions } from '../pagination/pagination-component-options.model';
import { SortOptions } from '../../core/cache/models/sort-options.model';
import { fadeIn, fadeInOut } from '../animations/fade';
import { Observable } from 'rxjs/Observable';
import { Item } from '../../core/shared/item.model';
import { ListableObject } from '../object-collection/shared/listable-object.model';
@Component({
selector: 'ds-browse-by',
styleUrls: ['./browse-by.component.scss'],
templateUrl: './browse-by.component.html',
animations: [
fadeIn,
fadeInOut
]
})
/**
* Component to display a browse-by page for any ListableObject
*/
export class BrowseByComponent {
@Input() title: string;
@Input() objects$: Observable<RemoteData<PaginatedList<ListableObject>>>;
@Input() paginationConfig: PaginationComponentOptions;
@Input() sortConfig: SortOptions;
@Input() currentUrl: string;
query: string;
}

View File

@@ -7,8 +7,8 @@ import { Store, StoreModule } from '@ngrx/store';
import { LogInComponent } from './log-in.component';
import { authReducer } from '../../core/auth/auth.reducer';
import { EpersonMock } from '../testing/eperson-mock';
import { Eperson } from '../../core/eperson/models/eperson.model';
import { EPersonMock } from '../testing/eperson-mock';
import { EPerson } from '../../core/eperson/models/eperson.model';
import { TranslateModule } from '@ngx-translate/core';
import { AuthService } from '../../core/auth/auth.service';
import { AuthServiceStub } from '../testing/auth-service-stub';
@@ -19,7 +19,7 @@ describe('LogInComponent', () => {
let component: LogInComponent;
let fixture: ComponentFixture<LogInComponent>;
let page: Page;
let user: Eperson;
let user: EPerson;
const authState = {
authenticated: false,
@@ -28,7 +28,7 @@ describe('LogInComponent', () => {
};
beforeEach(() => {
user = EpersonMock;
user = EPersonMock;
});
beforeEach(async(() => {

View File

@@ -5,8 +5,8 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Store, StoreModule } from '@ngrx/store';
import { authReducer } from '../../core/auth/auth.reducer';
import { EpersonMock } from '../testing/eperson-mock';
import { Eperson } from '../../core/eperson/models/eperson.model';
import { EPersonMock } from '../testing/eperson-mock';
import { EPerson } from '../../core/eperson/models/eperson.model';
import { TranslateModule } from '@ngx-translate/core';
import { Router } from '@angular/router';
import { AppState } from '../../app.reducer';
@@ -18,7 +18,7 @@ describe('LogOutComponent', () => {
let component: LogOutComponent;
let fixture: ComponentFixture<LogOutComponent>;
let page: Page;
let user: Eperson;
let user: EPerson;
const authState = {
authenticated: false,
@@ -28,7 +28,7 @@ describe('LogOutComponent', () => {
const routerStub = new RouterStub();
beforeEach(() => {
user = EpersonMock;
user = EPersonMock;
});
beforeEach(async(() => {

View File

@@ -5,6 +5,7 @@ import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer';
import { RemoteData } from '../../core/data/remote-data';
import { RequestEntry } from '../../core/data/request.reducer';
import { hasValue } from '../empty.util';
import { NormalizedObject } from '../../core/cache/models/normalized-object.model';
export function getMockRemoteDataBuildService(toRemoteDataObservable$?: Observable<RemoteData<any>>): RemoteDataBuildService {
return {
@@ -17,7 +18,8 @@ export function getMockRemoteDataBuildService(toRemoteDataObservable$?: Observab
payload
} as RemoteData<any>)))
}
}
},
buildSingle: (href$: string | Observable<string>) => Observable.of(new RemoteData(false, false, true, undefined, {}))
} as RemoteDataBuildService;
}

View File

@@ -8,7 +8,7 @@
<ds-object-grid [config]="config"
[sortConfig]="sortConfig"
[objects]="objects"
[hideGear]="true"
[hideGear]="hideGear"
*ngIf="getViewMode()===viewModeEnum.Grid">
</ds-object-grid>

View File

@@ -1,22 +1,28 @@
<div class="card">
<ds-truncatable [id]="object.id">
<div class="card">
<a [routerLink]="['/items/', object.id]" class="card-img-top">
<ds-grid-thumbnail [thumbnail]="object.getThumbnail()">
</ds-grid-thumbnail>
</a>
<div class="card-body">
<h4 class="card-title">{{object.findMetadata('dc.title')}}</h4>
<ds-truncatable-part [id]="object.id" [minLines]="2">
<p *ngIf="object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0" class="item-authors card-text text-muted">
<span *ngFor="let authorMd of object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">{{authorMd.value}}
<span *ngIf="!last">; </span>
</span>
<span *ngIf="hasValue(object.findMetadata('dc.date.issued'))" class="item-date">{{object.findMetadata("dc.date.issued")}}</span>
</p>
</ds-truncatable-part>
<p *ngIf="object.findMetadata('dc.description.abstract')" class="item-abstract card-text">{{object.findMetadata("dc.description.abstract") | dsTruncate:[200] }}</p>
<ds-truncatable-part [id]="object.id" [minLines]="5">
<p *ngIf="object.findMetadata('dc.description.abstract')" class="item-abstract card-text">{{object.findMetadata("dc.description.abstract") }}</p>
</ds-truncatable-part>
<div class="text-center">
<div class="text-center pt-2">
<a [routerLink]="['/items/', object.id]" class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</div>
</ds-truncatable>

View File

@@ -0,0 +1,7 @@
<div class="d-flex flex-row">
<a [routerLink]="" [queryParams]="{value: object.value}" class="lead">
{{object.value}}
</a>
<span class="pr-2">&nbsp;</span>
<span class="badge badge-pill badge-secondary align-self-center">{{object.count}}</span>
</div>

View File

@@ -0,0 +1 @@
@import '../../../../styles/variables';

View File

@@ -0,0 +1,47 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { TruncatePipe } from '../../utils/truncate.pipe';
import { Metadatum } from '../../../core/shared/metadatum.model';
import { BrowseEntryListElementComponent } from './browse-entry-list-element.component';
import { BrowseEntry } from '../../../core/shared/browse-entry.model';
let browseEntryListElementComponent: BrowseEntryListElementComponent;
let fixture: ComponentFixture<BrowseEntryListElementComponent>;
const mockValue: BrowseEntry = Object.assign(new BrowseEntry(), {
type: 'browseEntry',
value: 'De Langhe Kristof'
});
describe('MetadataListElementComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ BrowseEntryListElementComponent , TruncatePipe],
providers: [
{ provide: 'objectElementProvider', useValue: {mockValue}}
],
schemas: [ NO_ERRORS_SCHEMA ]
}).overrideComponent(BrowseEntryListElementComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(BrowseEntryListElementComponent);
browseEntryListElementComponent = fixture.componentInstance;
}));
describe('When the metadatum is loaded', () => {
beforeEach(() => {
browseEntryListElementComponent.object = mockValue;
fixture.detectChanges();
});
it('should show the value as a link', () => {
const browseEntryLink = fixture.debugElement.query(By.css('a.lead'));
expect(browseEntryLink.nativeElement.textContent.trim()).toBe(mockValue.value);
});
});
});

View File

@@ -0,0 +1,18 @@
import { Component, Input, Inject } from '@angular/core';
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
import { renderElementsFor } from '../../object-collection/shared/dso-element-decorator';
import { BrowseEntry } from '../../../core/shared/browse-entry.model';
import { ViewMode } from '../../../core/shared/view-mode.model';
@Component({
selector: 'ds-browse-entry-list-element',
styleUrls: ['./browse-entry-list-element.component.scss'],
templateUrl: './browse-entry-list-element.component.html'
})
/**
* This component is automatically used to create a list view for BrowseEntry objects when used in ObjectCollectionComponent
*/
@renderElementsFor(BrowseEntry, ViewMode.List)
export class BrowseEntryListElementComponent extends AbstractListableElementComponent<BrowseEntry> {}

View File

@@ -1,7 +1,9 @@
<a [routerLink]="['/items/' + object.id]" class="lead">
<ds-truncatable [id]="object.id">
<a [routerLink]="['/items/' + object.id]" class="lead">
{{object.findMetadata("dc.title")}}
</a>
<div>
</a>
<div>
<ds-truncatable-part [id]="object.id" [minLines]="1">
<span class="text-muted">
<span *ngIf="object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0"
class="item-list-authors">
@@ -12,7 +14,11 @@
(<span *ngIf="hasValue(object.findMetadata('dc.publisher'))" class="item-list-publisher">{{object.findMetadata("dc.publisher")}}, </span><span
*ngIf="hasValue(object.findMetadata('dc.date.issued'))" class="item-list-date">{{object.findMetadata("dc.date.issued")}}</span>)
</span>
</ds-truncatable-part>
<ds-truncatable-part [id]="object.id" [minLines]="3">
<div *ngIf="object.findMetadata('dc.description.abstract')" class="item-list-abstract">
{{object.findMetadata("dc.description.abstract") | dsTruncate:[200] }}
{{object.findMetadata("dc.description.abstract")}}
</div>
</div>
</ds-truncatable-part>
</div>
</ds-truncatable>

View File

@@ -76,6 +76,9 @@ import { NumberPickerComponent } from './number-picker/number-picker.component';
import { DsDatePickerComponent } from './form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component';
import { DsDynamicLookupComponent } from './form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component';
import { MockAdminGuard } from './mocks/mock-admin-guard.service';
import { BrowseByModule } from '../+browse-by/browse-by.module';
import { BrowseByComponent } from './browse-by/browse-by.component';
import { BrowseEntryListElementComponent } from './object-list/browse-entry-list-element/browse-entry-list-element.component';
import { DebounceDirective } from './utils/debounce.directive';
import { ClickOutsideDirective } from './utils/click-outside.directive';
import { EmphasizePipe } from './utils/emphasize.pipe';
@@ -155,6 +158,7 @@ const COMPONENTS = [
ViewModeSwitchComponent,
TruncatableComponent,
TruncatablePartComponent,
BrowseByComponent,
InputSuggestionsComponent
];
@@ -168,6 +172,7 @@ const ENTRY_COMPONENTS = [
CollectionGridElementComponent,
CommunityGridElementComponent,
SearchResultGridElementComponent,
BrowseEntryListElementComponent
];
const PROVIDERS = [

View File

@@ -2,12 +2,13 @@ import {of as observableOf, Observable } from 'rxjs';
import { HttpOptions } from '../../core/dspace-rest-v2/dspace-rest-v2.service';
import { AuthStatus } from '../../core/auth/models/auth-status.model';
import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model';
import { Eperson } from '../../core/eperson/models/eperson.model';
import { EPerson } from '../../core/eperson/models/eperson.model';
import { isNotEmpty } from '../empty.util';
import { EpersonMock } from './eperson-mock';
import { EPersonMock } from './eperson-mock';
import { RemoteData } from '../../core/data/remote-data';
export class AuthRequestServiceStub {
protected mockUser: Eperson = EpersonMock;
protected mockUser: EPerson = EPersonMock;
protected mockTokenInfo = new AuthTokenInfo('test_token');
public postToEndpoint(method: string, body: any, options?: HttpOptions): Observable<any> {
@@ -26,7 +27,7 @@ export class AuthRequestServiceStub {
if (this.validateToken(token)) {
authStatusStub.authenticated = true;
authStatusStub.token = this.mockTokenInfo;
authStatusStub.eperson = [this.mockUser];
authStatusStub.eperson = Observable.of(new RemoteData<EPerson>(false, false, true, undefined, this.mockUser));
} else {
authStatusStub.authenticated = false;
}
@@ -45,7 +46,7 @@ export class AuthRequestServiceStub {
if (this.validateToken(token)) {
authStatusStub.authenticated = true;
authStatusStub.token = this.mockTokenInfo;
authStatusStub.eperson = [this.mockUser];
authStatusStub.eperson = Observable.of(new RemoteData<EPerson>(false, false, true, undefined, this.mockUser));
} else {
authStatusStub.authenticated = false;
}

View File

@@ -2,8 +2,9 @@
import {of as observableOf, Observable } from 'rxjs';
import { AuthStatus } from '../../core/auth/models/auth-status.model';
import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model';
import { EpersonMock } from './eperson-mock';
import { Eperson } from '../../core/eperson/models/eperson.model';
import { EPersonMock } from './eperson-mock';
import { EPerson } from '../../core/eperson/models/eperson.model';
import { RemoteData } from '../../core/data/remote-data';
export class AuthServiceStub {
@@ -20,7 +21,7 @@ export class AuthServiceStub {
authStatus.okay = true;
authStatus.authenticated = true;
authStatus.token = this.token;
authStatus.eperson = [EpersonMock];
authStatus.eperson = Observable.of(new RemoteData<EPerson>(false, false, true, undefined, EPersonMock));
return observableOf(authStatus);
} else {
console.log('error');
@@ -28,9 +29,9 @@ export class AuthServiceStub {
}
}
public authenticatedUser(token: AuthTokenInfo): Observable<Eperson> {
public authenticatedUser(token: AuthTokenInfo): Observable<EPerson> {
if (token.accessToken === 'token_test') {
return observableOf(EpersonMock);
return Observable.of(EPersonMock);
} else {
throw(new Error('Message Error test'));
}

View File

@@ -1,6 +1,6 @@
import { Eperson } from '../../core/eperson/models/eperson.model';
import { EPerson } from '../../core/eperson/models/eperson.model';
export const EpersonMock: Eperson = Object.assign(new Eperson(),{
export const EPersonMock: EPerson = Object.assign(new EPerson(),{
handle: null,
groups: [],
netid: 'test@test.com',

View File

@@ -1216,9 +1216,9 @@ boom@5.x.x:
dependencies:
hoek "4.x.x"
bootstrap@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.1.tgz#3aec85000fa619085da8d2e4983dfd67cf2114cb"
bootstrap@4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.3.tgz#0eb371af2c8448e8c210411d0cb824a6409a12be"
boxen@^1.2.1:
version "1.3.0"
@@ -1965,14 +1965,14 @@ copy-webpack-plugin@^4.4.1:
p-limit "^1.0.0"
serialize-javascript "^1.4.0"
core-js@2.5.3:
version "2.5.3"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
core-js@^2.2.0, core-js@^2.4.0:
version "2.5.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
core-js@^2.5.7:
version "2.5.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
core-js@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.3.0.tgz#fab83fbb0b2d8dc85fa636c4b9d34c75420c6d65"