From 06a2c382b5993cc3df35c453df4f4b33e61ba554 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Mon, 18 Sep 2023 11:48:30 +0300 Subject: [PATCH 01/47] Change ElasticSearch query to be "query_string" based --- .../publications2.component.html | 67 +++++++++++ .../publications2.component.scss | 39 ++++++ .../publications2.component.spec.ts | 23 ++++ .../publications2/publications2.component.ts | 112 ++++++++++++++++++ src/app/portal/portal-routing.module.ts | 10 ++ .../resolvers/publication.resolver.spec.ts | 16 +++ .../publications-resolver.service.ts | 100 ++++++++++++++++ src/styles.scss | 6 + 8 files changed, 373 insertions(+) create mode 100644 src/app/portal/components/results/publications2/publications2.component.html create mode 100644 src/app/portal/components/results/publications2/publications2.component.scss create mode 100644 src/app/portal/components/results/publications2/publications2.component.spec.ts create mode 100644 src/app/portal/components/results/publications2/publications2.component.ts create mode 100644 src/app/portal/resolvers/publication.resolver.spec.ts create mode 100644 src/app/portal/resolvers/publications-resolver.service.ts diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html new file mode 100644 index 000000000..0b2d8c204 --- /dev/null +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -0,0 +1,67 @@ +
+ + + +
+ +
+ + +
+ +
+
Keywords: {{keywords}}
+
Page: {{page}}
+
Size: {{pageSize}}
+
+ +
+ + +
+ + + + + + + publicationName + + +
+
+
+ + + authorsText + + +
+ +
+
+
+ + + publisherName + + +
+ +
+
+
+ + + publicationYear + + +
+ +
+
+
+ + + +
diff --git a/src/app/portal/components/results/publications2/publications2.component.scss b/src/app/portal/components/results/publications2/publications2.component.scss new file mode 100644 index 000000000..c421a6099 --- /dev/null +++ b/src/app/portal/components/results/publications2/publications2.component.scss @@ -0,0 +1,39 @@ +/* Ensure the table takes the full width */ +cdk-table { + width: 100%; +} + +/* Add some spacing and border to the cells */ +cdk-header-cell, cdk-cell { + padding: 16px; + border-bottom: 1px solid #e0e0e0; + display: table-cell; /* This ensures they behave like traditional table cells */ +} + +/* Define the header row and data row to behave like traditional table rows */ +cdk-header-row, cdk-row { + display: table-row; +} + +/* Define columns to ensure they take equal widths - adjust as required */ +[cdkColumnDef="title"] { + width: 25%; +} + +[cdkColumnDef="authors"] { + width: 25%; +} + +[cdkColumnDef="journalName"] { + width: 25%; +} + +[cdkColumnDef="publicationYear"] { + width: 25%; +} + +em { + background-color: #FFFF00; + font-style: normal; + padding: 0.1em 0; +} diff --git a/src/app/portal/components/results/publications2/publications2.component.spec.ts b/src/app/portal/components/results/publications2/publications2.component.spec.ts new file mode 100644 index 000000000..325346f77 --- /dev/null +++ b/src/app/portal/components/results/publications2/publications2.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Publications2Component } from './publications2.component'; + +describe('Publications2Component', () => { + let component: Publications2Component; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ Publications2Component ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(Publications2Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts new file mode 100644 index 000000000..494b16dbc --- /dev/null +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -0,0 +1,112 @@ +import { Component, inject, OnInit, SecurityContext } from '@angular/core'; +import { CdkTableModule, DataSource } from '@angular/cdk/table'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormsModule } from '@angular/forms'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { JsonPipe } from '@angular/common'; + +// highlighted (HTML based) data that contains the columns: ['publicationName', 'authorsText', 'publisherName', 'publicationYear']; +interface HighlightedPublication { + publicationName: SafeHtml; + authorsText: SafeHtml; + authorsTextSplitted: SafeHtml; + publisherName: SafeHtml; + publicationYear: SafeHtml; +} + +// function that creates a new HighlightedPublication from a search data that has +// the highlighted data in the Publications2Component exists in: this.route.snapshot.data.publications.hits.hits.highlight.{... names of the columns ...} +// fallback values are in: this.route.snapshot.data.publications.hits.hits._source.{... names of the columns ...} +// final fallback would be empty strings +// data is passed from the this.route.snapshot.data.publications.hits.hits + +// sanitize values with: this.sanitizedHtml = this.sanitizer.sanitize(SecurityContext.HTML, this.someHtmlContent); + +function createHighlightedPublication(searchData: any): HighlightedPublication { + const sanitizer = inject(DomSanitizer); + + const values = { + publicationName: searchData.highlight?.publicationName?.[0] ?? searchData._source.publicationName ?? "", + authorsText: searchData.highlight?.authorsText?.[0] ?? searchData._source.authorsText ?? "", + authorsTextSplitted: searchData.highlight?.authorsText?.[0] ?? searchData._source.authorsText ?? "", + publisherName: searchData.highlight?.publisherName?.[0] ?? searchData._source.publisherName ?? "", + publicationYear: searchData.highlight?.publicationYear?.[0] ?? searchData._source.publicationYear ?? "" + } + + return { + publicationName: sanitizer.sanitize(SecurityContext.HTML, values.publicationName), + authorsText: sanitizer.sanitize(SecurityContext.HTML, values.authorsText), + authorsTextSplitted: sanitizer.sanitize(SecurityContext.HTML, values.authorsTextSplitted), + publisherName: sanitizer.sanitize(SecurityContext.HTML, values.publisherName), + publicationYear: sanitizer.sanitize(SecurityContext.HTML, values.publicationYear) + } +} + +@Component({ + selector: 'app-publications2', + templateUrl: './publications2.component.html', + styleUrls: ['./publications2.component.scss'], + imports: [CdkTableModule, JsonPipe, FormsModule], + standalone: true +}) +export class Publications2Component { + route = inject(ActivatedRoute); + router = inject(Router); + sanitizer = inject(DomSanitizer); + + displayedColumns: string[] = ['publicationName', 'authorsText', 'publisherName', 'publicationYear']; + + search = this.route.snapshot.data.publications; + + // publications: Publication[] = this.route.snapshot.data.publications.hits.hits.map(e => e._source); + highlights: HighlightedPublication[] = this.route.snapshot.data.publications.hits.hits.map(e => createHighlightedPublication(e)); + + dataSource = new PublicationDataSource(this.highlights); + + // queryParam 'page' parsed to number + page: number = parseInt(this.route.snapshot.queryParams.page ?? 1); + + // queryParam 'pageSize' parsed to number + pageSize: number = parseInt(this.route.snapshot.queryParams.pageSize?? 10); + + // queryParam 'q' assigned to keywords + public keywords = this.route.snapshot.queryParams.q ?? ""; + + constructor() { + this.router.routeReuseStrategy.shouldReuseRoute = () => false; + } + + searchKeywords(keywords: string) { + this.router.navigate([], { queryParams: { q: keywords }, queryParamsHandling: 'merge' }); + } + + nextPage() { + this.router.navigate([], { queryParams: { page: this.page + 1 }, queryParamsHandling: 'merge' }); + } + + previousPage() { + this.router.navigate([], { queryParams: { page: Math.max(this.page - 1, 0) }, queryParamsHandling: 'merge' }); + } + + setPageSize(size: number) { + this.router.navigate([], { queryParams: { pageSize: size }, queryParamsHandling: 'merge' }); + } +} + +export class PublicationDataSource extends DataSource { + data: BehaviorSubject; + + constructor(initialData: HighlightedPublication[]) { + super(); + this.data = new BehaviorSubject(initialData); + } + + connect(): Observable { + return this.data; + } + + disconnect() { + this.data.complete(); + } +} diff --git a/src/app/portal/portal-routing.module.ts b/src/app/portal/portal-routing.module.ts index 6e93cfbaf..576b10c2e 100644 --- a/src/app/portal/portal-routing.module.ts +++ b/src/app/portal/portal-routing.module.ts @@ -34,6 +34,8 @@ import { SingleDatasetComponent } from './components/single/single-dataset/singl import { SingleFundingCallComponent } from './components/single/single-funding-call/single-funding-call.component'; import { SingleIndicatorComponent } from './components/science-politics/open-science-and-research-indicators/single-indicator/single-indicator.component'; import { SinglePersonComponent } from './components/single/single-person/single-person.component'; +import { Publications2Component } from '@portal/components/results/publications2/publications2.component'; +import { PublicationsResolver } from '@portal/resolvers/publications-resolver.service'; // import { TkiReportsComponent } from "@portal/components/science-politics/tki-reports/tki-reports.component"; const routes: Routes = [ @@ -98,6 +100,14 @@ const routes: Routes = [ redirectTo: 'results/publications', pathMatch: 'full', }, + { + path: 'results/publications2', + component: Publications2Component, + resolve: { + publications: PublicationsResolver + }, + runGuardsAndResolvers: 'always' + }, { path: 'results/:tab', component: ResultsComponent, diff --git a/src/app/portal/resolvers/publication.resolver.spec.ts b/src/app/portal/resolvers/publication.resolver.spec.ts new file mode 100644 index 000000000..bd622eb0c --- /dev/null +++ b/src/app/portal/resolvers/publication.resolver.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { PublicationsResolver } from './publications-resolver.service'; + +describe('PublicationResolver', () => { + let resolver: PublicationsResolver; + + beforeEach(() => { + TestBed.configureTestingModule({}); + resolver = TestBed.inject(PublicationsResolver); + }); + + it('should be created', () => { + expect(resolver).toBeTruthy(); + }); +}); diff --git a/src/app/portal/resolvers/publications-resolver.service.ts b/src/app/portal/resolvers/publications-resolver.service.ts new file mode 100644 index 000000000..5f455c001 --- /dev/null +++ b/src/app/portal/resolvers/publications-resolver.service.ts @@ -0,0 +1,100 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; + +@Injectable({ + providedIn: 'root' +}) +export class PublicationsResolver implements Resolve { + constructor(private http: HttpClient) {} + + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + const page: number = route.queryParams.page || 1; + const pageSize: number = route.queryParams.pageSize || 10; + const q = route.queryParams.q || ''; + + const from = (page - 1) * pageSize; + + console.log('Results from:', from, "with size:", pageSize, "using query", q || ""); + + if (q === "") { + console.log("EMPTY QUERY"); + + return this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', { + "from": from, + "size": pageSize, + "query": { + "match_all": {} + }, + }); + } + + /* + return this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', { + "from": from, + "size": pageSize, + "query": { + "multi_match": { + "query": q, + "fields": ["publicationName", "authorsText", "publisherName"] + } + }, + "highlight": { + "fields": { + "publicationName": {}, + "authorsText": {}, + "publisherName": {} + }, + "pre_tags": [""], + "post_tags": [""] + } // */ + + /* + return this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', { + "from": from, + "size": pageSize, + "query": { + "simple_query_string": { + "query": q, + "fields": ["publicationName", "authorsTextSplitted", "publisherName"], // TODO "authorsText", + "default_operator": "OR", + "flags": "PHRASE" + } + }, + "highlight": { + "fields": { + "publicationName": {}, + "authorsText": {}, + "authorsTextSplitted": {}, + "publisherName": {} + }, + "pre_tags": [""], + "post_tags": [""] + } + }); // */ + + // * + return this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', { + "from": from, + "size": pageSize, + "query": { + "query_string": { + "query": q, + "fields": ["publicationName", "authorsText", "publisherName"], + "default_operator": "OR" + } + }, + "highlight": { + "fields": { + "publicationName": {}, + "authorsText": {}, + "authorsTextSplitted": {}, + "publisherName": {} + }, + "pre_tags": [""], + "post_tags": [""] + } + }); // */ + } +} diff --git a/src/styles.scss b/src/styles.scss index fe744d31e..a64b48260 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -151,3 +151,9 @@ body { margin: 0 auto; min-height: 100%; } + +em.highlight { + background-color: #FFFF00; + font-style: normal; + padding: 0.1em 0; +} From 3f4f3386f052c44b77cfb54e98b7b6df7a7fe43b Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Thu, 28 Sep 2023 17:44:20 +0300 Subject: [PATCH 02/47] Add URL search query parameters. Use search query as source of truth. --- package-lock.json | 11 + package.json | 1 + .../publications2.component.html | 124 ++++++---- .../publications2/publications2.component.ts | 218 ++++++++++++------ .../components/results/results.component.html | 4 + .../search-bar/search-bar.component.html | 21 +- .../search-bar/search-bar.component.ts | 14 +- src/app/portal/portal-routing.module.ts | 6 +- src/app/portal/portal.module.ts | 4 +- .../resolvers/publication.resolver.spec.ts | 16 -- .../publications-resolver.service.ts | 100 -------- .../search-bar2/search-bar2.component.html | 22 ++ .../search-bar2/search-bar2.component.scss | 112 +++++++++ .../search-bar2/search-bar2.component.spec.ts | 23 ++ .../search-bar2/search-bar2.component.ts | 26 +++ .../services/publication2.service.spec.ts | 16 ++ .../portal/services/publication2.service.ts | 142 ++++++++++++ .../pagination/pagination.component.ts | 7 +- 18 files changed, 606 insertions(+), 261 deletions(-) delete mode 100644 src/app/portal/resolvers/publication.resolver.spec.ts delete mode 100644 src/app/portal/resolvers/publications-resolver.service.ts create mode 100644 src/app/portal/search-bar2/search-bar2.component.html create mode 100644 src/app/portal/search-bar2/search-bar2.component.scss create mode 100644 src/app/portal/search-bar2/search-bar2.component.spec.ts create mode 100644 src/app/portal/search-bar2/search-bar2.component.ts create mode 100644 src/app/portal/services/publication2.service.spec.ts create mode 100644 src/app/portal/services/publication2.service.ts diff --git a/package-lock.json b/package-lock.json index 5d841ebe9..ffb567247 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,6 +66,7 @@ "through2": "^4.0.2", "timers": "^0.1.1", "tslib": "^2.1.0", + "valibot": "^0.17.0", "zone.js": "~0.11.8" }, "devDependencies": { @@ -16830,6 +16831,11 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/valibot": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.17.0.tgz", + "integrity": "sha512-ykoYfBc4HOH+osdWaFBmeL7yX35bFfKg1WdwHYZhNVuvGIRH5f2gtYT6cnU85XOsOE7R8la5RnVY6yAScWU9qg==" + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -30071,6 +30077,11 @@ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true }, + "valibot": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.17.0.tgz", + "integrity": "sha512-ykoYfBc4HOH+osdWaFBmeL7yX35bFfKg1WdwHYZhNVuvGIRH5f2gtYT6cnU85XOsOE7R8la5RnVY6yAScWU9qg==" + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/package.json b/package.json index a4c982900..06253ae87 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "through2": "^4.0.2", "timers": "^0.1.1", "tslib": "^2.1.0", + "valibot": "^0.17.0", "zone.js": "~0.11.8" }, "devDependencies": { diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index 0b2d8c204..2080bdb07 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -11,8 +11,7 @@
Keywords: {{keywords}}
-
Page: {{page}}
-
Size: {{pageSize}}
+
Search Parameters: {{searchParams$ | async | json}}
@@ -20,48 +19,79 @@
- - - - - - publicationName - - -
-
-
- - - authorsText - - -
- -
-
-
- - - publisherName - - -
- -
-
-
- - - publicationYear - - -
- -
-
-
- - - -
+
+
+
+ + + + + + + + + +
+ + {{yearFilter.year}} + ({{yearFilter.count}}) +
+
+
+
+ +
+
+ + + + + + +
+
{{yearFilter.year}}
+ +
+
+
+ +
+ + + + publicationName + + +
+
+
+ + + authorsText + + +
+
+
+ + + publisherName + + +
+
+
+ + + publicationYear + + +
+
+
+ + + +
+
+
diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 494b16dbc..5e0451fe8 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -1,112 +1,186 @@ -import { Component, inject, OnInit, SecurityContext } from '@angular/core'; +import { Component, inject, OnDestroy } from '@angular/core'; import { CdkTableModule, DataSource } from '@angular/cdk/table'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { combineLatest, Observable } from 'rxjs'; import { ActivatedRoute, Router } from '@angular/router'; import { FormsModule } from '@angular/forms'; -import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; -import { JsonPipe } from '@angular/common'; - -// highlighted (HTML based) data that contains the columns: ['publicationName', 'authorsText', 'publisherName', 'publicationYear']; -interface HighlightedPublication { - publicationName: SafeHtml; - authorsText: SafeHtml; - authorsTextSplitted: SafeHtml; - publisherName: SafeHtml; - publicationYear: SafeHtml; -} - -// function that creates a new HighlightedPublication from a search data that has -// the highlighted data in the Publications2Component exists in: this.route.snapshot.data.publications.hits.hits.highlight.{... names of the columns ...} -// fallback values are in: this.route.snapshot.data.publications.hits.hits._source.{... names of the columns ...} -// final fallback would be empty strings -// data is passed from the this.route.snapshot.data.publications.hits.hits - -// sanitize values with: this.sanitizedHtml = this.sanitizer.sanitize(SecurityContext.HTML, this.someHtmlContent); - -function createHighlightedPublication(searchData: any): HighlightedPublication { - const sanitizer = inject(DomSanitizer); - - const values = { - publicationName: searchData.highlight?.publicationName?.[0] ?? searchData._source.publicationName ?? "", - authorsText: searchData.highlight?.authorsText?.[0] ?? searchData._source.authorsText ?? "", - authorsTextSplitted: searchData.highlight?.authorsText?.[0] ?? searchData._source.authorsText ?? "", - publisherName: searchData.highlight?.publisherName?.[0] ?? searchData._source.publisherName ?? "", - publicationYear: searchData.highlight?.publicationYear?.[0] ?? searchData._source.publicationYear ?? "" - } - - return { - publicationName: sanitizer.sanitize(SecurityContext.HTML, values.publicationName), - authorsText: sanitizer.sanitize(SecurityContext.HTML, values.authorsText), - authorsTextSplitted: sanitizer.sanitize(SecurityContext.HTML, values.authorsTextSplitted), - publisherName: sanitizer.sanitize(SecurityContext.HTML, values.publisherName), - publicationYear: sanitizer.sanitize(SecurityContext.HTML, values.publicationYear) - } -} +import { AsyncPipe, JsonPipe, NgForOf, NgIf } from '@angular/common'; +import { HighlightedPublication, Publication2Service } from '@portal/services/publication2.service'; +import { map, take } from 'rxjs/operators'; @Component({ selector: 'app-publications2', templateUrl: './publications2.component.html', styleUrls: ['./publications2.component.scss'], - imports: [CdkTableModule, JsonPipe, FormsModule], + imports: [CdkTableModule, FormsModule, AsyncPipe, JsonPipe, NgForOf, NgIf], standalone: true }) -export class Publications2Component { +export class Publications2Component implements OnDestroy { route = inject(ActivatedRoute); router = inject(Router); - sanitizer = inject(DomSanitizer); + publications2Service = inject(Publication2Service); displayedColumns: string[] = ['publicationName', 'authorsText', 'publisherName', 'publicationYear']; - search = this.route.snapshot.data.publications; + highlights$ = this.publications2Service.getSearch(); // TODO: /*: Observable*/ + dataSource = new PublicationDataSource(this.highlights$); + + aggregations$ = this.publications2Service.getAggregations(); + + // TODO DELETE LEGACY + /*enabledFilters$ = this.route.queryParams.pipe( + map(params => params.filters ?? []), + );*/ + + yearCounts$ = this.aggregations$.pipe( + map(aggs => aggs.by_publicationYear.buckets.map((bucket: any) => ({ year: bucket.key.toString(), count: bucket.doc_count }))), /*as { year: string, count: number }*/ + map(aggs => aggs.sort((a, b) => b.year - a.year)), + ); + + // Take query parameters and deserialize each "variable" using the splitFields function + searchParams$ = this.route.queryParams.pipe( + map(splitFields), + ); + + // TODO DELETE LEGACY + // Combine yearCounts$ and enabledFilters$ to get the yearFilters$ observable + yearFilters$ = combineLatest([this.yearCounts$, this.searchParams$.pipe(map(params => params.year ?? []))]).pipe( + map(([yearCounts, enabledFilters]) => yearCounts.map(yearCount => ({ + year: yearCount.year, + count: yearCount.count, + enabled: enabledFilters.includes(yearCount.year.toString()) + }))) + ); + + searchParamsSubscription = this.searchParams$.subscribe(searchParams => { + this.publications2Service.updateSearchTerms(searchParams); + }); + + + toggleParam(key: string, value: string) { + this.searchParams$.pipe(take(1)).subscribe(filterParams => { + const queryParams = { ...filterParams }; + + if (queryParams[key] == null) { + queryParams[key] = []; + } + + const index = queryParams[key].indexOf(value); + if (index === -1) { + queryParams[key].push(value); + } else { + queryParams[key].splice(index, 1); + } + + if (queryParams[key].length === 0) { + delete queryParams[key]; + } + + this.router.navigate([], { + relativeTo: this.route, + queryParams: concatFields(queryParams) + }); + }); + } - // publications: Publication[] = this.route.snapshot.data.publications.hits.hits.map(e => e._source); - highlights: HighlightedPublication[] = this.route.snapshot.data.publications.hits.hits.map(e => createHighlightedPublication(e)); + ngOnDestroy() { + this.searchParamsSubscription.unsubscribe(); + } - dataSource = new PublicationDataSource(this.highlights); + // Model for the search box + keywords = ""; - // queryParam 'page' parsed to number - page: number = parseInt(this.route.snapshot.queryParams.page ?? 1); + searchKeywords(keywords: string) { + this.router.navigate([], { + relativeTo: this.route, + queryParams: { q: keywords }, queryParamsHandling: 'merge' + }); + } - // queryParam 'pageSize' parsed to number - pageSize: number = parseInt(this.route.snapshot.queryParams.pageSize?? 10); + nextPage() { // TODO CLEAN UP - // queryParam 'q' assigned to keywords - public keywords = this.route.snapshot.queryParams.q ?? ""; + // TODO DELETE OLD + /*this.router.navigate([], { + relativeTo: this.route, + queryParams: { page: this.page + 1 }, queryParamsHandling: 'merge' + });*/ - constructor() { - this.router.routeReuseStrategy.shouldReuseRoute = () => false; - } - searchKeywords(keywords: string) { - this.router.navigate([], { queryParams: { q: keywords }, queryParamsHandling: 'merge' }); - } - nextPage() { - this.router.navigate([], { queryParams: { page: this.page + 1 }, queryParamsHandling: 'merge' }); + + // Access the existing page query parameter or use 1 if it doesn't exist + // Update the page query parameter by adding 1 and router.navigate + // take(1) from searchParams$ + + this.searchParams$.pipe(take(1)).subscribe(searchParams => { + const queryParams = { ...searchParams }; + const page = parseInt(queryParams.page?.[0] ?? "1"); + queryParams.page = [`${page + 1}`]; + this.router.navigate([], { + relativeTo: this.route, + queryParams: queryParams + }); + }); } - previousPage() { - this.router.navigate([], { queryParams: { page: Math.max(this.page - 1, 0) }, queryParamsHandling: 'merge' }); + previousPage() { // TODO + /*this.router.navigate([], { + relativeTo: this.route, + queryParams: { page: Math.max(this.page - 1, 0) }, queryParamsHandling: 'merge' + });*/ } setPageSize(size: number) { - this.router.navigate([], { queryParams: { pageSize: size }, queryParamsHandling: 'merge' }); + this.router.navigate([], { + relativeTo: this.route, + queryParams: { pageSize: size }, queryParamsHandling: 'merge' + }); } } export class PublicationDataSource extends DataSource { - data: BehaviorSubject; - - constructor(initialData: HighlightedPublication[]) { + constructor(private data$: Observable) { super(); - this.data = new BehaviorSubject(initialData); } connect(): Observable { - return this.data; + return this.data$; } - disconnect() { - this.data.complete(); + disconnect() { /**/ } +} + +// TODO Utility module + +function concatParams(strings: string[]): string { + return strings.sort().join(","); +} + +function splitParams(input: string | string[]): string[] { + if (Array.isArray(input)) { + return input.flatMap(item => item.split(",")); } + + return input.split(","); +} + +function splitFields(obj: Record): Record { + const result: Record = {}; + + for (const key in obj) { + const value = obj[key]; + result[key] = splitParams(value); + } + + return result; +} + +function concatFields(obj: Record): Record { + const result: Record = {}; + + for (const key in obj) { + const value = obj[key]; + result[key] = concatParams(value); + } + + return result; } diff --git a/src/app/portal/components/results/results.component.html b/src/app/portal/components/results/results.component.html index 2882283a1..d7031a138 100644 --- a/src/app/portal/components/results/results.component.html +++ b/src/app/portal/components/results/results.component.html @@ -8,6 +8,10 @@

+
+ +
+
Hae
-
- +

Kirjoita hakukenttään julkaisun nimi, asiasana, hankkeen nimi tai esimerkiksi organisaation nimi.

+

Hakutulosten rajaaminen onnistuu haun jälkeen hakunäkymässä.

+

Haku kohdistuu julkaisuihin, tutkijoihin, hankkeisiin, aineistoihin, rahoitushakuihin, infrastruktuureihin ja organisaatioihin. diff --git a/src/app/portal/components/search-bar/search-bar.component.ts b/src/app/portal/components/search-bar/search-bar.component.ts index 6e9a7619e..7fd5ac400 100644 --- a/src/app/portal/components/search-bar/search-bar.component.ts +++ b/src/app/portal/components/search-bar/search-bar.component.ts @@ -201,17 +201,18 @@ export class SearchBarComponent implements OnInit, AfterViewInit, OnDestroy { this.setCompletionWidth(); } - fireAutoSuggest() { - this.autoSuggestSub = this.queryField.valueChanges - .pipe(debounceTime(1000), distinctUntilChanged()) + fireAutoSuggest() { // TODO: Dense code warning; IS THIS DIFFERENT FROM NORMAL SEARCHES? + this.autoSuggestSub = this.queryField.valueChanges.pipe(debounceTime(1000), distinctUntilChanged()) .subscribe((result) => { this.keyManager = new ActiveDescendantKeyManager(this.items) .withWrap() .withTypeAhead(); this.currentInput = result; + if (result.length > 2) { this.topData = []; this.otherData = []; + this.autoSuggestResponseSub = this.autosuggestService .search(result) .pipe(map((response) => [response])) @@ -219,17 +220,18 @@ export class SearchBarComponent implements OnInit, AfterViewInit, OnDestroy { // Sort indices with highest doc count const arr = []; this.autoSuggestResponse = response; - const source = - this.autoSuggestResponse[0].aggregations._index.buckets; + const source = this.autoSuggestResponse[0].aggregations._index.buckets; + Object.keys(source) .sort((a, b) => source[b].doc_count - source[a].doc_count) .forEach((key) => { - arr.push({ + arr.push({ // TODO: arr side-effect index: key, source: source[key], translation: this.translations[key], }); }); + // Show hits for top 2 indices with most results this.topData = arr.slice(0, 2); // List other indices, filter out indices with no results diff --git a/src/app/portal/portal-routing.module.ts b/src/app/portal/portal-routing.module.ts index 576b10c2e..6e05f0a52 100644 --- a/src/app/portal/portal-routing.module.ts +++ b/src/app/portal/portal-routing.module.ts @@ -35,7 +35,6 @@ import { SingleFundingCallComponent } from './components/single/single-funding-c import { SingleIndicatorComponent } from './components/science-politics/open-science-and-research-indicators/single-indicator/single-indicator.component'; import { SinglePersonComponent } from './components/single/single-person/single-person.component'; import { Publications2Component } from '@portal/components/results/publications2/publications2.component'; -import { PublicationsResolver } from '@portal/resolvers/publications-resolver.service'; // import { TkiReportsComponent } from "@portal/components/science-politics/tki-reports/tki-reports.component"; const routes: Routes = [ @@ -103,10 +102,9 @@ const routes: Routes = [ { path: 'results/publications2', component: Publications2Component, - resolve: { + /*resolve: { // TODO Delete publications: PublicationsResolver - }, - runGuardsAndResolvers: 'always' + }*/ }, { path: 'results/:tab', diff --git a/src/app/portal/portal.module.ts b/src/app/portal/portal.module.ts index ef2e55115..9bd85acc8 100644 --- a/src/app/portal/portal.module.ts +++ b/src/app/portal/portal.module.ts @@ -166,6 +166,7 @@ import { CheckEmptyFieldsPipe } from './pipes/check-empty-fields.pipe'; import { PersonGroupComponent } from './components/single/single-person/person-group/person-group.component'; import { PersonGroupAdditionalComponent } from './components/single/single-person/person-group-additional/person-group-additional.component'; import { FooterComponent } from '../layout/footer/footer.component'; +import { SearchBar2Component } from '@portal/search-bar2/search-bar2.component'; @NgModule({ declarations: [ @@ -287,7 +288,8 @@ import { FooterComponent } from '../layout/footer/footer.component'; TooltipModule.forRoot(), MatTableModule, MatSortModule, - FooterComponent + FooterComponent, + SearchBar2Component ], exports: [DatasetAuthorComponent, FiltersComponent], providers: [ diff --git a/src/app/portal/resolvers/publication.resolver.spec.ts b/src/app/portal/resolvers/publication.resolver.spec.ts deleted file mode 100644 index bd622eb0c..000000000 --- a/src/app/portal/resolvers/publication.resolver.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { PublicationsResolver } from './publications-resolver.service'; - -describe('PublicationResolver', () => { - let resolver: PublicationsResolver; - - beforeEach(() => { - TestBed.configureTestingModule({}); - resolver = TestBed.inject(PublicationsResolver); - }); - - it('should be created', () => { - expect(resolver).toBeTruthy(); - }); -}); diff --git a/src/app/portal/resolvers/publications-resolver.service.ts b/src/app/portal/resolvers/publications-resolver.service.ts deleted file mode 100644 index 5f455c001..000000000 --- a/src/app/portal/resolvers/publications-resolver.service.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; -import { Observable } from 'rxjs'; -import { HttpClient } from '@angular/common/http'; - -@Injectable({ - providedIn: 'root' -}) -export class PublicationsResolver implements Resolve { - constructor(private http: HttpClient) {} - - resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - const page: number = route.queryParams.page || 1; - const pageSize: number = route.queryParams.pageSize || 10; - const q = route.queryParams.q || ''; - - const from = (page - 1) * pageSize; - - console.log('Results from:', from, "with size:", pageSize, "using query", q || ""); - - if (q === "") { - console.log("EMPTY QUERY"); - - return this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', { - "from": from, - "size": pageSize, - "query": { - "match_all": {} - }, - }); - } - - /* - return this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', { - "from": from, - "size": pageSize, - "query": { - "multi_match": { - "query": q, - "fields": ["publicationName", "authorsText", "publisherName"] - } - }, - "highlight": { - "fields": { - "publicationName": {}, - "authorsText": {}, - "publisherName": {} - }, - "pre_tags": [""], - "post_tags": [""] - } // */ - - /* - return this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', { - "from": from, - "size": pageSize, - "query": { - "simple_query_string": { - "query": q, - "fields": ["publicationName", "authorsTextSplitted", "publisherName"], // TODO "authorsText", - "default_operator": "OR", - "flags": "PHRASE" - } - }, - "highlight": { - "fields": { - "publicationName": {}, - "authorsText": {}, - "authorsTextSplitted": {}, - "publisherName": {} - }, - "pre_tags": [""], - "post_tags": [""] - } - }); // */ - - // * - return this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', { - "from": from, - "size": pageSize, - "query": { - "query_string": { - "query": q, - "fields": ["publicationName", "authorsText", "publisherName"], - "default_operator": "OR" - } - }, - "highlight": { - "fields": { - "publicationName": {}, - "authorsText": {}, - "authorsTextSplitted": {}, - "publisherName": {} - }, - "pre_tags": [""], - "post_tags": [""] - } - }); // */ - } -} diff --git a/src/app/portal/search-bar2/search-bar2.component.html b/src/app/portal/search-bar2/search-bar2.component.html new file mode 100644 index 000000000..3b12334a3 --- /dev/null +++ b/src/app/portal/search-bar2/search-bar2.component.html @@ -0,0 +1,22 @@ +

+
+ Kohdista haku +
+ + + + +
diff --git a/src/app/portal/search-bar2/search-bar2.component.scss b/src/app/portal/search-bar2/search-bar2.component.scss new file mode 100644 index 000000000..8d0a524de --- /dev/null +++ b/src/app/portal/search-bar2/search-bar2.component.scss @@ -0,0 +1,112 @@ +.search-input { + width: 510px; + height: 55px; + + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + border-width: 0; + + font-size: 25px; + + padding: 1px 44px 1px 16px; +} + +input[placeholder] { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +::placeholder { + font-size: 17px; + font-style: normal; +} + +/*! CSS Used from: Embedded */ +*, *:before, *:after { + box-sizing: border-box; +} + +body:not(.user-tabbing) *:focus { + outline: none; +} + +*, *:before, *:after { + box-sizing: inherit; +} + +/*! CSS Used from: https://tiedejatutkimus.fi/fi/styles.cdbf57794ce87ed1.css */ +*, *:before, *:after { + box-sizing: border-box; +} + +input { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +[type=search] { + outline-offset: -2px; + -webkit-appearance: textfield; +} + +.row > * { + flex-shrink: 0; + width: 100%; + max-width: 100%; + padding-right: calc(var(--bs-gutter-x) * .5); + padding-left: calc(var(--bs-gutter-x) * .5); + margin-top: var(--bs-gutter-y); +} + +.col-10 { + flex: 0 0 auto; + width: 83.33333333%; +} + +@media (min-width: 576px) { + .col-sm-12 { + flex: 0 0 auto; + width: 100%; + } +} + +body:not(.user-tabbing) input:focus, body:not(.user-tabbing) *:focus { + outline: none; +} + +*, *:before, *:after { + box-sizing: inherit; +} + +body app-search-bar .search-input input { + color: #495057 !important; +} + +/*! CSS Used from: Embedded */ +app-search-bar input#searchInput { + border-top-right-radius: .25rem !important; + border-bottom-right-radius: .25rem !important; +} + +app-search-bar .search-input input { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + border: none; + padding-right: 2.75rem; +} + +app-search-bar input { + z-index: 102; +} + + + +@media (max-width: 768px) { + app-search-bar input#searchInput { + border-radius: .25rem !important; + } +} diff --git a/src/app/portal/search-bar2/search-bar2.component.spec.ts b/src/app/portal/search-bar2/search-bar2.component.spec.ts new file mode 100644 index 000000000..227c2108d --- /dev/null +++ b/src/app/portal/search-bar2/search-bar2.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SearchBar2Component } from './search-bar2.component'; + +describe('SearchBar2Component', () => { + let component: SearchBar2Component; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ SearchBar2Component ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SearchBar2Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/portal/search-bar2/search-bar2.component.ts b/src/app/portal/search-bar2/search-bar2.component.ts new file mode 100644 index 000000000..a59b944be --- /dev/null +++ b/src/app/portal/search-bar2/search-bar2.component.ts @@ -0,0 +1,26 @@ +import { Component, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatRippleModule } from '@angular/material/core'; +import { FormsModule } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; + +@Component({ + selector: 'app-search-bar2', + standalone: true, + imports: [CommonModule, MatRippleModule, FormsModule], + templateUrl: './search-bar2.component.html', + styleUrls: ['./search-bar2.component.scss'] +}) +export class SearchBar2Component { + route = inject(ActivatedRoute); + router = inject(Router) + + /* Input/Output for keywords; Keep the keyword management in the parent? */ + /* Can be changed later easily */ + + public keywords = this.route.snapshot.queryParams.q ?? ""; + + searchKeywords(keywords: string) { + this.router.navigate([], { queryParams: { q: keywords }, queryParamsHandling: 'merge' }); + } +} diff --git a/src/app/portal/services/publication2.service.spec.ts b/src/app/portal/services/publication2.service.spec.ts new file mode 100644 index 000000000..2495ee00f --- /dev/null +++ b/src/app/portal/services/publication2.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { Publication2Service } from './publication2.service'; + +describe('Publication2Service', () => { + let service: Publication2Service; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(Publication2Service); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts new file mode 100644 index 000000000..ac1b5180b --- /dev/null +++ b/src/app/portal/services/publication2.service.ts @@ -0,0 +1,142 @@ +import { inject, Injectable, OnInit, SecurityContext } from '@angular/core'; +// import { object, Output, parse, string } from 'valibot'; +import { BehaviorSubject, Observable, shareReplay } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { map, switchMap, tap } from 'rxjs/operators'; +import { ActivatedRoute } from '@angular/router'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; + +/*const PublicationSearchSchema = object({ + publicationId: string(), + title: string(), +});*/ + +type PublicationSearch = any; // Output; + +// function that validates API response into a PublicationSearch +function parsePublicationSearch(data: any): PublicationSearch { + return data; + // return parse(PublicationSearchSchema, data); +} + +export interface HighlightedPublication { + publicationName: SafeHtml; + authorsText: SafeHtml; + authorsTextSplitted: SafeHtml; + publisherName: SafeHtml; + publicationYear: SafeHtml; +} + +type CachedPublicationSearch = { + keywords: string, + publicationSearch: PublicationSearch, +} + +@Injectable({ + providedIn: 'root' +}) +export class Publication2Service { + http = inject(HttpClient); + sanitizer = inject(DomSanitizer); + + searchParams = new BehaviorSubject>({}); + + aggregations ={ + "by_publicationYear": { + "terms": { + "field": "publicationYear" + } + } + } + + searchResults$: Observable = this.searchParams.pipe( + switchMap(searchParams => this.searchPublications(searchParams)), + shareReplay({ bufferSize: 1, refCount: true }) + ); + + publicationSearch$: Observable = this.searchResults$.pipe( + map((data) => parsePublicationSearch(data)), + map((publicationSearch: PublicationSearch) => this.createHighlightedPublications(publicationSearch)) + ); + + publicationAggregations$ = this.searchResults$.pipe( + map((data) => data.aggregations) + ); + + getSearch() { + return this.publicationSearch$; + } + + getAggregations() { + return this.publicationAggregations$; + } + + updateSearchTerms(searchParams: Record): void { + this.searchParams.next(searchParams); + } + + private searchPublications(searchParams: Record): Observable { // TODO Observable is more correct? + const q = (searchParams.q ?? [""])[0]; + const page = parseInt(searchParams.page?.[0] ?? "1"); + const size = parseInt(searchParams.size?.[0] ?? "10"); + const from = (page - 1) * size; + + console.log("TIME TO BUILD AGGREGATIONS", searchParams); + + if (q === "") { + return this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', { + "from": from, + "size": size, + "query": { + "match_all": {} + }, + "aggregations": this.aggregations + }); + } + + return this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', { + "from": from, + "size": size, + "query": { + "query_string": { + "query": q, + "fields": ["publicationName", "authorsText", "publisherName"], + "default_operator": "OR" + } + }, + "highlight": { + "fields": { + "publicationName": {}, + "authorsText": {}, + "authorsTextSplitted": {}, + "publisherName": {} + }, + "pre_tags": [""], + "post_tags": [""] + }, + "aggregations": this.aggregations + }); + } + + createHighlightedPublications(searchData: PublicationSearch): HighlightedPublication[] { + return searchData.hits.hits.map((hit: any/*ES doc for Publication*/) => this.createHighlightedPublication(hit)); + } + + createHighlightedPublication(searchData: any/*ES doc for Publication*/): HighlightedPublication { + const values = { + publicationName: searchData.highlight?.publicationName?.[0] ?? searchData._source.publicationName ?? '', + authorsText: searchData.highlight?.authorsText?.[0] ?? searchData._source.authorsText ?? '', + authorsTextSplitted: searchData.highlight?.authorsText?.[0] ?? searchData._source.authorsText ?? '', + publisherName: searchData.highlight?.publisherName?.[0] ?? searchData._source.publisherName ?? '', + publicationYear: searchData.highlight?.publicationYear?.[0] ?? searchData._source.publicationYear ?? '' + }; + + return { + publicationName: this.sanitizer.sanitize(SecurityContext.HTML, values.publicationName), + authorsText: this.sanitizer.sanitize(SecurityContext.HTML, values.authorsText), + authorsTextSplitted: this.sanitizer.sanitize(SecurityContext.HTML, values.authorsTextSplitted), + publisherName: this.sanitizer.sanitize(SecurityContext.HTML, values.publisherName), + publicationYear: this.sanitizer.sanitize(SecurityContext.HTML, values.publicationYear) + }; + } +} diff --git a/src/app/shared/components/pagination/pagination.component.ts b/src/app/shared/components/pagination/pagination.component.ts index 911bf1581..c89b96dfc 100644 --- a/src/app/shared/components/pagination/pagination.component.ts +++ b/src/app/shared/components/pagination/pagination.component.ts @@ -35,9 +35,6 @@ export class PaginationComponent implements OnInit, OnDestroy { resizeSub: Subscription; desktop = this.window.innerWidth >= 1200; order = this.window.innerWidth >= 768; - previousPage = $localize`:@@previousPage:Edellinen sivu`; - nextPage = $localize`:@@nextPage:Seuraava sivu`; - tooManyResultstext = $localize`:@@tooManyResultsNavigationDisabled:Liikaa tuloksia. Haun loppuun navigoiminen estetty.`; faAngleRight = faAngleRight; faAngleLeft = faAngleLeft; @@ -48,6 +45,10 @@ export class PaginationComponent implements OnInit, OnDestroy { previous = $localize`:@@previous:Edellinen`; next = $localize`:@@next:Seuraava`; + previousPage = $localize`:@@previousPage:Edellinen sivu`; + nextPage = $localize`:@@nextPage:Seuraava sivu`; + tooManyResultstext = $localize`:@@tooManyResultsNavigationDisabled:Liikaa tuloksia. Haun loppuun navigoiminen estetty.`; + constructor( private route: ActivatedRoute, private router: Router, From 69d0c77fc2f131100e282919fb8d1791301431ae Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Tue, 3 Oct 2023 17:55:42 +0300 Subject: [PATCH 03/47] Add better aggregation that can calculate filters additive and subtractive effects --- .../publications2.component.html | 12 + .../publications2/publications2.component.ts | 74 ++--- .../portal/services/publication2.service.ts | 292 +++++++++++++++--- 3 files changed, 295 insertions(+), 83 deletions(-) diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index 2080bdb07..1b240542c 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -37,6 +37,18 @@ ({{yearFilter.count}})
+ +
+ + + + + +
+ {{organizationAdditions$ | async | json}} +
+ +
diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 5e0451fe8..8ed2c5294 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -4,7 +4,12 @@ import { combineLatest, Observable } from 'rxjs'; import { ActivatedRoute, Router } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { AsyncPipe, JsonPipe, NgForOf, NgIf } from '@angular/common'; -import { HighlightedPublication, Publication2Service } from '@portal/services/publication2.service'; +import { + getOrganizationAdditions, + getYearAdditions, + HighlightedPublication, + Publication2Service +} from '@portal/services/publication2.service'; import { map, take } from 'rxjs/operators'; @Component({ @@ -19,42 +24,45 @@ export class Publications2Component implements OnDestroy { router = inject(Router); publications2Service = inject(Publication2Service); + keywords = ""; + displayedColumns: string[] = ['publicationName', 'authorsText', 'publisherName', 'publicationYear']; highlights$ = this.publications2Service.getSearch(); // TODO: /*: Observable*/ dataSource = new PublicationDataSource(this.highlights$); + searchParams$ = this.route.queryParams.pipe( map(splitFields) ); aggregations$ = this.publications2Service.getAggregations(); - // TODO DELETE LEGACY - /*enabledFilters$ = this.route.queryParams.pipe( - map(params => params.filters ?? []), - );*/ - yearCounts$ = this.aggregations$.pipe( - map(aggs => aggs.by_publicationYear.buckets.map((bucket: any) => ({ year: bucket.key.toString(), count: bucket.doc_count }))), /*as { year: string, count: number }*/ - map(aggs => aggs.sort((a, b) => b.year - a.year)), - ); - // Take query parameters and deserialize each "variable" using the splitFields function - searchParams$ = this.route.queryParams.pipe( - map(splitFields), + yearAdditions$ = this.aggregations$.pipe( + map(aggs => getYearAdditions(aggs).map((bucket: any) => ({ year: bucket.key.toString(), count: bucket.doc_count })) ?? []), + map(aggs => aggs.sort((a, b) => b.year - a.year)) ); - // TODO DELETE LEGACY - // Combine yearCounts$ and enabledFilters$ to get the yearFilters$ observable - yearFilters$ = combineLatest([this.yearCounts$, this.searchParams$.pipe(map(params => params.year ?? []))]).pipe( - map(([yearCounts, enabledFilters]) => yearCounts.map(yearCount => ({ - year: yearCount.year, - count: yearCount.count, - enabled: enabledFilters.includes(yearCount.year.toString()) + yearFilters$ = combineLatest([this.yearAdditions$, this.searchParams$.pipe(map(params => params.year ?? []))]).pipe( + map(([yearAdditions, enabledFilters]) => yearAdditions.map(yearAddition => ({ + year: yearAddition.year, + count: yearAddition.count, + enabled: enabledFilters.includes(yearAddition.year.toString()) }))) ); + + organizationAdditions$ = this.aggregations$.pipe( + map(aggs => getOrganizationAdditions(aggs).map((bucket: any) => ({ organization: bucket.key, count: bucket.doc_count })) ?? []), + map(aggs => aggs.sort((a, b) => b.count - a.count)) + ); + + searchParamsSubscription = this.searchParams$.subscribe(searchParams => { this.publications2Service.updateSearchTerms(searchParams); }); + ngOnDestroy() { + this.searchParamsSubscription.unsubscribe(); + } toggleParam(key: string, value: string) { this.searchParams$.pipe(take(1)).subscribe(filterParams => { @@ -65,6 +73,7 @@ export class Publications2Component implements OnDestroy { } const index = queryParams[key].indexOf(value); + if (index === -1) { queryParams[key].push(value); } else { @@ -82,13 +91,6 @@ export class Publications2Component implements OnDestroy { }); } - ngOnDestroy() { - this.searchParamsSubscription.unsubscribe(); - } - - // Model for the search box - keywords = ""; - searchKeywords(keywords: string) { this.router.navigate([], { relativeTo: this.route, @@ -98,19 +100,6 @@ export class Publications2Component implements OnDestroy { nextPage() { // TODO CLEAN UP - // TODO DELETE OLD - /*this.router.navigate([], { - relativeTo: this.route, - queryParams: { page: this.page + 1 }, queryParamsHandling: 'merge' - });*/ - - - - - // Access the existing page query parameter or use 1 if it doesn't exist - // Update the page query parameter by adding 1 and router.navigate - // take(1) from searchParams$ - this.searchParams$.pipe(take(1)).subscribe(searchParams => { const queryParams = { ...searchParams }; const page = parseInt(queryParams.page?.[0] ?? "1"); @@ -123,10 +112,7 @@ export class Publications2Component implements OnDestroy { } previousPage() { // TODO - /*this.router.navigate([], { - relativeTo: this.route, - queryParams: { page: Math.max(this.page - 1, 0) }, queryParamsHandling: 'merge' - });*/ + } setPageSize(size: number) { @@ -149,8 +135,6 @@ export class PublicationDataSource extends DataSource { disconnect() { /**/ } } -// TODO Utility module - function concatParams(strings: string[]): string { return strings.sort().join(","); } diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index ac1b5180b..8b2354384 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -32,6 +32,14 @@ type CachedPublicationSearch = { publicationSearch: PublicationSearch, } +type SearchParams = Record & { + q?: string[], + page?: string[], + size?: string[], + year?: string[], + organization?: string[], +} + @Injectable({ providedIn: 'root' }) @@ -41,16 +49,9 @@ export class Publication2Service { searchParams = new BehaviorSubject>({}); - aggregations ={ - "by_publicationYear": { - "terms": { - "field": "publicationYear" - } - } - } - searchResults$: Observable = this.searchParams.pipe( switchMap(searchParams => this.searchPublications(searchParams)), + tap((data) => console.log("searchPublications", data)), shareReplay({ bufferSize: 1, refCount: true }) ); @@ -75,46 +76,50 @@ export class Publication2Service { this.searchParams.next(searchParams); } - private searchPublications(searchParams: Record): Observable { // TODO Observable is more correct? + private searchPublications(searchParams: Record): Observable { const q = (searchParams.q ?? [""])[0]; const page = parseInt(searchParams.page?.[0] ?? "1"); const size = parseInt(searchParams.size?.[0] ?? "10"); const from = (page - 1) * size; - console.log("TIME TO BUILD AGGREGATIONS", searchParams); - - if (q === "") { - return this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', { - "from": from, - "size": size, - "query": { - "match_all": {} - }, - "aggregations": this.aggregations - }); - } + searchParams = searchParams as SearchParams; // TODO no effect? return this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', { - "from": from, - "size": size, - "query": { - "query_string": { - "query": q, - "fields": ["publicationName", "authorsText", "publisherName"], - "default_operator": "OR" - } + from: from, + size: size, + query: { + bool: { + must: { + ...matchingTerms(q) // TODO searchParams as input? + }, + filter: { + bool: { + must: [ + ...termsForYear(searchParams), + ...termsForTypeCode(searchParams), + ...termsForStatusCode(searchParams), + ...termsForOrganization(searchParams) + ] + } + } + }, }, - "highlight": { - "fields": { - "publicationName": {}, - "authorsText": {}, - "authorsTextSplitted": {}, - "publisherName": {} + highlight: { + fields: { + publicationName: {}, + authorsText: {}, + authorsTextSplitted: {}, + publisherName: {} }, - "pre_tags": [""], - "post_tags": [""] + pre_tags: [""], + post_tags: [""] }, - "aggregations": this.aggregations + aggregations: { + ...additionsFromYear(searchParams), + ...additionsFromTypeCode(searchParams), + ...additionsFromStatusCode(searchParams), + ...additionsFromOrganizationId(searchParams) + } }); } @@ -140,3 +145,214 @@ export class Publication2Service { }; } } + +function matchingTerms(q: string) { + if (q === "") { + return { + match_all: {} + }; + } + + return { + query_string: { + query: q, + fields: ["publicationName", "authorsText", "publisherName"], + default_operator: "OR" + } + }; +} + +function termsForYear(searchParams: SearchParams) { + if (searchParams.publicationYear) { + return [{ + terms: { + "publicationYear": searchParams.publicationYear + } + }]; + } + return []; +} + +function termsForTypeCode(searchParams: SearchParams) { + if (searchParams.publicationTypeCode) { + return [{ + terms: { + "publicationTypeCode.keyword": searchParams.publicationTypeCode + } + }]; + } + return []; +} + +function termsForStatusCode(searchParams: SearchParams) { + if (searchParams.publicationStatusCode) { + return [{ + terms: { + "publicationStatusCode.keyword": searchParams.publicationStatusCode + } + }]; + } + return []; +} + +function termsForOrganization(searchParams: SearchParams) { + if (!searchParams.organization) { return []; } + + return [{ + term: { + 'author.organization.organizationId.keyword': searchParams.organization + } + }]; +} + +// NOTE: additionsFrom functions must use all other "termsFor" functions but not its own + +function additionsFromYear(searchParams: SearchParams) { + return { + "all_data_except_publicationYear": { + "global": {}, + "aggregations": { + "filtered_except_publicationYear": { + "filter": { + "bool": { + "must": [ + ...termsForTypeCode(searchParams), + ...termsForStatusCode(searchParams), + ...termsForOrganization(searchParams) + ] + } + }, + "aggregations": { + "all_publicationYears": { + "terms": { + "field": "publicationYear" + } + } + } + } + } + } + }; +} + +type YearAggregation = { + all_data_except_publicationYear: { + filtered_except_publicationYear: { + all_publicationYears: { + buckets: Array<{ + key: number; + doc_count: number; + }>; + }; + }; + }; +}; + +export function getYearAdditions(aggregations: YearAggregation) { + console.log("getYearAdditions", aggregations); + + return aggregations.all_data_except_publicationYear?.filtered_except_publicationYear.all_publicationYears.buckets ?? []; +} + +function additionsFromTypeCode(searchParams: SearchParams) { + return { + "all_data_except_publicationTypeCode": { + "global": {}, + "aggregations": { + "filtered_except_publicationTypeCode": { + "filter": { + "bool": { + "must": [ + ...termsForYear(searchParams), + ...termsForStatusCode(searchParams), + ...termsForOrganization(searchParams) + ] + } + }, + "aggregations": { + "all_publicationTypeCodes": { + "terms": { + "field": "publicationTypeCode.keyword" + } + } + } + } + } + } + }; +} + +function additionsFromStatusCode(searchParams: SearchParams) { + return { + "all_data_except_publicationStatusCode": { + "global": {}, + "aggregations": { + "filtered_except_publicationStatusCode": { + "filter": { + "bool": { + "must": [ + ...termsForYear(searchParams), + ...termsForTypeCode(searchParams), + ...termsForOrganization(searchParams) + ] + } + }, + "aggregations": { + "all_publicationStatusCodes": { + "terms": { + "field": "publicationStatusCode.keyword" + } + } + } + } + } + } + }; +} + +function additionsFromOrganizationId(searchParams: SearchParams) { + return { + "all_data_except_organizationId": { + "global": {}, + "aggregations": { + "filtered_except_organizationId": { + "filter": { + "bool": { + "must": [ + ...termsForYear(searchParams), + ...termsForTypeCode(searchParams), + ...termsForStatusCode(searchParams) + ] + } + }, + "aggregations": { + "all_organizationIds": { + "terms": { + "field": "author.organization.organizationId.keyword" + } + } + } + } + } + } + }; +} + +type OrganizationAggregation = { + all_data_except_organizationId: { + filtered_except_organizationId: { + all_organizationIds: { + buckets: Array<{ + key: string; + doc_count: number; + }>; + }; + }; + }; +}; + +export function getOrganizationAdditions(aggregations: OrganizationAggregation) { + console.log("getOrganizationAdditions", aggregations); + + return aggregations.all_data_except_organizationId?.filtered_except_organizationId.all_organizationIds.buckets ?? []; +} From fc8fd9d800057acd5c547baace2a8822043c0a3a Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Thu, 5 Oct 2023 09:19:25 +0300 Subject: [PATCH 04/47] Add incomplete organization filter --- .../portal/services/publication2.service.ts | 141 +++++++++++++----- 1 file changed, 103 insertions(+), 38 deletions(-) diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 8b2354384..4d64c7176 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -1,4 +1,4 @@ -import { inject, Injectable, OnInit, SecurityContext } from '@angular/core'; +import { inject, Injectable, LOCALE_ID, OnInit, SecurityContext } from '@angular/core'; // import { object, Output, parse, string } from 'valibot'; import { BehaviorSubject, Observable, shareReplay } from 'rxjs'; import { HttpClient } from '@angular/common/http'; @@ -32,12 +32,16 @@ type CachedPublicationSearch = { publicationSearch: PublicationSearch, } -type SearchParams = Record & { +type SearchParams = { q?: string[], page?: string[], size?: string[], year?: string[], organization?: string[], + + // placeholders + publicationTypeCode?: string[], + publicationStatusCode?: string[], } @Injectable({ @@ -46,6 +50,9 @@ type SearchParams = Record & { export class Publication2Service { http = inject(HttpClient); sanitizer = inject(DomSanitizer); + locale = inject(LOCALE_ID); + + i18n = suffixer(this.locale); searchParams = new BehaviorSubject>({}); @@ -118,7 +125,7 @@ export class Publication2Service { ...additionsFromYear(searchParams), ...additionsFromTypeCode(searchParams), ...additionsFromStatusCode(searchParams), - ...additionsFromOrganizationId(searchParams) + ...additionsFromOrganization(searchParams) } }); } @@ -163,10 +170,10 @@ function matchingTerms(q: string) { } function termsForYear(searchParams: SearchParams) { - if (searchParams.publicationYear) { + if (searchParams.year) { return [{ terms: { - "publicationYear": searchParams.publicationYear + "publicationYear": searchParams.year } }]; } @@ -195,16 +202,6 @@ function termsForStatusCode(searchParams: SearchParams) { return []; } -function termsForOrganization(searchParams: SearchParams) { - if (!searchParams.organization) { return []; } - - return [{ - term: { - 'author.organization.organizationId.keyword': searchParams.organization - } - }]; -} - // NOTE: additionsFrom functions must use all other "termsFor" functions but not its own function additionsFromYear(searchParams: SearchParams) { @@ -217,8 +214,8 @@ function additionsFromYear(searchParams: SearchParams) { "bool": { "must": [ ...termsForTypeCode(searchParams), - ...termsForStatusCode(searchParams), - ...termsForOrganization(searchParams) + ...termsForOrganization(searchParams), + ...termsForStatusCode(searchParams) ] } }, @@ -249,8 +246,6 @@ type YearAggregation = { }; export function getYearAdditions(aggregations: YearAggregation) { - console.log("getYearAdditions", aggregations); - return aggregations.all_data_except_publicationYear?.filtered_except_publicationYear.all_publicationYears.buckets ?? []; } @@ -310,7 +305,72 @@ function additionsFromStatusCode(searchParams: SearchParams) { }; } -function additionsFromOrganizationId(searchParams: SearchParams) { +type OrganizationAggregation = { + all_data_except_organizationId: { + filtered_except_organizationId: { + organization_nested: { + all_organizationIds: { + buckets: Array<{ + key: string; + doc_count: number; + }>; + }; + }; + }; + }; +}; + +export function getOrganizationAdditions(aggregations: OrganizationAggregation) { + console.log("getOrganizationAdditions", aggregations); + + return aggregations.all_data_except_organizationId?.filtered_except_organizationId.organization_nested.all_organizationIds.buckets ?? []; +} + +function suffixer(locale) { + function capitalize(str) { + return str.charAt(0).toUpperCase() + str.slice(1); + } + + return function(strings, ...values) { + let result = ''; + + for(let i = 0; i < values.length; i++) { + result += strings[i] + values[i] + capitalize(locale); + } + + result += strings[strings.length - 1]; + return result; + }; +} + +const i18n = suffixer("fi"); + +function termsForOrganization(searchParams: SearchParams) { // TODO: Add locale argument for adjusting the field name + if (searchParams.organization && searchParams.organization.length > 0) { + return [{ + "nested": { + "path": "author", + "query": { + "bool": { + "must": [ + { + "terms": { + // "author.organization.organizationId.keyword": searchParams.organization + // "author.organization.organizationNameFi.keyword" + // [i18n`author.organization.${"Organization"}.keyword`]: searchParams.organization + [i18n`author.organization.${"OrganizationName"}.keyword`]: searchParams.organization + } + } + ] + } + } + } + }]; + } + return []; +} + +function additionsFromOrganization(searchParams: SearchParams) { return { "all_data_except_organizationId": { "global": {}, @@ -326,9 +386,18 @@ function additionsFromOrganizationId(searchParams: SearchParams) { } }, "aggregations": { - "all_organizationIds": { - "terms": { - "field": "author.organization.organizationId.keyword" + "organization_nested": { + "nested": { + "path": "author" + }, + "aggregations": { + "all_organizationIds": { + "terms": { + // "field": "author.organization.organizationId.keyword" + // author.organization.OrganizationNameFi.keyword + "field": i18n`author.organization.${"OrganizationName"}.keyword` + } + } } } } @@ -338,21 +407,17 @@ function additionsFromOrganizationId(searchParams: SearchParams) { }; } -type OrganizationAggregation = { - all_data_except_organizationId: { - filtered_except_organizationId: { - all_organizationIds: { - buckets: Array<{ - key: string; - doc_count: number; - }>; - }; - }; +/*function suffixer(locale) { + function capitalize(str) { + return str.charAt(0).toUpperCase() + str.slice(1); + } + + return function(strings) { + return strings[0] + capitalize(locale); }; -}; +}*/ -export function getOrganizationAdditions(aggregations: OrganizationAggregation) { - console.log("getOrganizationAdditions", aggregations); +// TODO suffixer to the organization termFor and additionsFrom functions +// Use the plain text values over the id (?) - return aggregations.all_data_except_organizationId?.filtered_except_organizationId.all_organizationIds.buckets ?? []; -} +// aa öö From a1aeedb5bc1df2727fa15eff4be4114ae1ad842f Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Thu, 12 Oct 2023 14:17:39 +0300 Subject: [PATCH 05/47] Get organization names separately --- .../publications2.component.html | 46 +++++--- .../publications2/publications2.component.ts | 36 ++++-- .../portal/services/publication2.service.ts | 108 +++++++++++------- 3 files changed, 129 insertions(+), 61 deletions(-) diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index 1b240542c..cb4855752 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -30,25 +30,37 @@ - -
- - {{yearFilter.year}} - ({{yearFilter.count}}) -
-
+ + +
+ -
+ +
+ + {{yearFilter.year}} + ({{yearFilter.count}}) +
+
+
- - + +
+ +
+ + + + +
+ + {{organizationFilter.name}} + ({{organizationFilter.count}}) +
+
+
- -
- {{organizationAdditions$ | async | json}} -
-
@@ -105,5 +117,9 @@ + +
+ +
diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 8ed2c5294..16dd79d5e 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -11,12 +11,15 @@ import { Publication2Service } from '@portal/services/publication2.service'; import { map, take } from 'rxjs/operators'; +import { SharedModule } from '@shared/shared.module'; @Component({ selector: 'app-publications2', templateUrl: './publications2.component.html', styleUrls: ['./publications2.component.scss'], - imports: [CdkTableModule, FormsModule, AsyncPipe, JsonPipe, NgForOf, NgIf], + imports: [CdkTableModule, FormsModule, AsyncPipe, JsonPipe, NgForOf, NgIf, + SharedModule // TODO not good? + ], standalone: true }) export class Publications2Component implements OnDestroy { @@ -34,6 +37,8 @@ export class Publications2Component implements OnDestroy { searchParams$ = this.route.queryParams.pipe( map(splitFields) ); aggregations$ = this.publications2Service.getAggregations(); + // TODO joka kerta uusi HTTP? + organizationNames$ = this.publications2Service.getOrganizationNames(); yearAdditions$ = this.aggregations$.pipe( @@ -49,15 +54,25 @@ export class Publications2Component implements OnDestroy { }))) ); - organizationAdditions$ = this.aggregations$.pipe( - map(aggs => getOrganizationAdditions(aggs).map((bucket: any) => ({ organization: bucket.key, count: bucket.doc_count })) ?? []), + map(aggs => getOrganizationAdditions(aggs).map((bucket: any) => ({ id: bucket.key, count: bucket.doc_count })) ?? []), map(aggs => aggs.sort((a, b) => b.count - a.count)) ); + organizationFilters$ = combineLatest([this.organizationAdditions$, this.organizationNames$, this.searchParams$.pipe(map(params => params.organization ?? []))]).pipe( + map(([organizationAdditions, organizationNames, enabledFilters]) => organizationAdditions.map(organizationAddition => ({ + id: organizationAddition.id, + count: organizationAddition.count, + name: organizationNames[organizationAddition.id] ?? organizationAddition.id, + enabled: enabledFilters.includes(organizationAddition.id) + }))) + ); searchParamsSubscription = this.searchParams$.subscribe(searchParams => { this.publications2Service.updateSearchTerms(searchParams); + + // Read first q parameter safely + this.keywords = searchParams.q?.[0] ?? ""; }); ngOnDestroy() { @@ -99,7 +114,6 @@ export class Publications2Component implements OnDestroy { } nextPage() { // TODO CLEAN UP - this.searchParams$.pipe(take(1)).subscribe(searchParams => { const queryParams = { ...searchParams }; const page = parseInt(queryParams.page?.[0] ?? "1"); @@ -111,14 +125,22 @@ export class Publications2Component implements OnDestroy { }); } - previousPage() { // TODO - + previousPage() { // TODO CLEAN UP + this.searchParams$.pipe(take(1)).subscribe(searchParams => { + const queryParams = { ...searchParams }; + const page = parseInt(queryParams.page?.[0] ?? "1"); + queryParams.page = [`${page - 1}`]; + this.router.navigate([], { + relativeTo: this.route, + queryParams: queryParams + }); + }); } setPageSize(size: number) { this.router.navigate([], { relativeTo: this.route, - queryParams: { pageSize: size }, queryParamsHandling: 'merge' + queryParams: { size }, queryParamsHandling: 'merge' }); } } diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 4d64c7176..35f56abe3 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -2,7 +2,7 @@ import { inject, Injectable, LOCALE_ID, OnInit, SecurityContext } from '@angular // import { object, Output, parse, string } from 'valibot'; import { BehaviorSubject, Observable, shareReplay } from 'rxjs'; import { HttpClient } from '@angular/common/http'; -import { map, switchMap, tap } from 'rxjs/operators'; +import { map, switchMap, take, tap } from 'rxjs/operators'; import { ActivatedRoute } from '@angular/router'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; @@ -52,7 +52,9 @@ export class Publication2Service { sanitizer = inject(DomSanitizer); locale = inject(LOCALE_ID); - i18n = suffixer(this.locale); + path = suffixer(this.locale); + + organizationNames$ = this.getOrganizationNames(); searchParams = new BehaviorSubject>({}); @@ -84,7 +86,7 @@ export class Publication2Service { } private searchPublications(searchParams: Record): Observable { - const q = (searchParams.q ?? [""])[0]; + const q = searchParams.q?.[0] ?? ""; const page = parseInt(searchParams.page?.[0] ?? "1"); const size = parseInt(searchParams.size?.[0] ?? "10"); const from = (page - 1) * size; @@ -97,7 +99,7 @@ export class Publication2Service { query: { bool: { must: { - ...matchingTerms(q) // TODO searchParams as input? + ...matchingTerms(searchParams) }, filter: { bool: { @@ -151,9 +153,41 @@ export class Publication2Service { publicationYear: this.sanitizer.sanitize(SecurityContext.HTML, values.publicationYear) }; } + + getOrganizationNames() { + // API call to search by doing aggregation on organization names + const res$ = this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', { + "size": 0, + "aggs": { + "organizations": { + "nested": { + "path": "author.organization" + }, + "aggs": { + "composite_orgs": { + "composite": { + "size": 10000, + "sources": [ + { "id": { "terms": { "field": "author.organization.organizationId.keyword" } } }, + { "name": { "terms": { "field": path`author.organization.OrganizationNameEn.keyword` } } } + ] + } + } + } + } + } + }); + + return res$.pipe( + map((data) => toIdNameLookup(data)), + shareReplay({ bufferSize: 1, refCount: true }) + ); + } } -function matchingTerms(q: string) { +function matchingTerms(searchParams: SearchParams) { + const q = (searchParams.q ?? [""])[0]; + if (q === "") { return { match_all: {} @@ -213,6 +247,7 @@ function additionsFromYear(searchParams: SearchParams) { "filter": { "bool": { "must": [ + matchingTerms(searchParams), ...termsForTypeCode(searchParams), ...termsForOrganization(searchParams), ...termsForStatusCode(searchParams) @@ -258,6 +293,7 @@ function additionsFromTypeCode(searchParams: SearchParams) { "filter": { "bool": { "must": [ + matchingTerms(searchParams), ...termsForYear(searchParams), ...termsForStatusCode(searchParams), ...termsForOrganization(searchParams) @@ -286,6 +322,7 @@ function additionsFromStatusCode(searchParams: SearchParams) { "filter": { "bool": { "must": [ + matchingTerms(searchParams), ...termsForYear(searchParams), ...termsForTypeCode(searchParams), ...termsForOrganization(searchParams) @@ -321,31 +358,18 @@ type OrganizationAggregation = { }; export function getOrganizationAdditions(aggregations: OrganizationAggregation) { - console.log("getOrganizationAdditions", aggregations); - return aggregations.all_data_except_organizationId?.filtered_except_organizationId.organization_nested.all_organizationIds.buckets ?? []; } function suffixer(locale) { - function capitalize(str) { - return str.charAt(0).toUpperCase() + str.slice(1); - } + const capitalized = locale.charAt(0).toUpperCase() + locale.slice(1).toLowerCase(); - return function(strings, ...values) { - let result = ''; - - for(let i = 0; i < values.length; i++) { - result += strings[i] + values[i] + capitalize(locale); - } - - result += strings[strings.length - 1]; - return result; - }; + return strings => strings[0].replace(/(Fi|Sv|En)(?=\.|$)/g, capitalized); } -const i18n = suffixer("fi"); +const path = suffixer("fi"); -function termsForOrganization(searchParams: SearchParams) { // TODO: Add locale argument for adjusting the field name +function termsForOrganization(searchParams: SearchParams) { if (searchParams.organization && searchParams.organization.length > 0) { return [{ "nested": { @@ -355,10 +379,7 @@ function termsForOrganization(searchParams: SearchParams) { "must": [ { "terms": { - // "author.organization.organizationId.keyword": searchParams.organization - // "author.organization.organizationNameFi.keyword" - // [i18n`author.organization.${"Organization"}.keyword`]: searchParams.organization - [i18n`author.organization.${"OrganizationName"}.keyword`]: searchParams.organization + "author.organization.organizationId.keyword": searchParams.organization } } ] @@ -379,6 +400,7 @@ function additionsFromOrganization(searchParams: SearchParams) { "filter": { "bool": { "must": [ + matchingTerms(searchParams), ...termsForYear(searchParams), ...termsForTypeCode(searchParams), ...termsForStatusCode(searchParams) @@ -393,9 +415,9 @@ function additionsFromOrganization(searchParams: SearchParams) { "aggregations": { "all_organizationIds": { "terms": { - // "field": "author.organization.organizationId.keyword" + "field": "author.organization.organizationId.keyword" // author.organization.OrganizationNameFi.keyword - "field": i18n`author.organization.${"OrganizationName"}.keyword` + // "field": i18n`author.organization.${"OrganizationName"}.keyword` } } } @@ -407,17 +429,25 @@ function additionsFromOrganization(searchParams: SearchParams) { }; } -/*function suffixer(locale) { - function capitalize(str) { - return str.charAt(0).toUpperCase() + str.slice(1); - } - - return function(strings) { - return strings[0] + capitalize(locale); +type OrgsAggsResponse = { + aggregations: { + organizations: { + composite_orgs: { + buckets: Array<{ + key: { + id: string; + name: string; + }; + doc_count: number; + }>; + }; + }; }; -}*/ +}; -// TODO suffixer to the organization termFor and additionsFrom functions -// Use the plain text values over the id (?) +function toIdNameLookup(data: OrgsAggsResponse): Map { + const pairs = data.aggregations.organizations.composite_orgs.buckets + .map((bucket) => [bucket.key.id, bucket.key.name]) -// aa öö + return Object.fromEntries(pairs); +} From dbdd8f66eaa2be81aed883afb9fc1bf4feb81e3e Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Wed, 18 Oct 2023 09:20:01 +0300 Subject: [PATCH 06/47] Add inputs for pagination --- .../results/publications2/publications2.component.html | 2 +- .../results/publications2/publications2.component.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index cb4855752..c773fb26c 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -119,7 +119,7 @@
- +
diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 16dd79d5e..07d47017e 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -27,7 +27,10 @@ export class Publications2Component implements OnDestroy { router = inject(Router); publications2Service = inject(Publication2Service); + // input and pagination inputs keywords = ""; + page = 1; + size = 10; displayedColumns: string[] = ['publicationName', 'authorsText', 'publisherName', 'publicationYear']; @@ -71,8 +74,9 @@ export class Publications2Component implements OnDestroy { searchParamsSubscription = this.searchParams$.subscribe(searchParams => { this.publications2Service.updateSearchTerms(searchParams); - // Read first q parameter safely this.keywords = searchParams.q?.[0] ?? ""; + this.page = parseInt(searchParams.page?.[0] ?? "1"); + this.size = parseInt(searchParams.size?.[0] ?? "10"); }); ngOnDestroy() { From aca10c6242f2afe152d366bd8af5774285c2be50 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Wed, 25 Oct 2023 17:20:34 +0300 Subject: [PATCH 07/47] Add total count to the new search. Improve the presentation of the new search. --- .../publications2.component.html | 236 +++++++++++------- .../publications2/publications2.component.ts | 12 +- .../components/results/results.component.html | 4 - .../search-bar2/search-bar2.component.html | 9 +- .../search-bar2/search-bar2.component.ts | 10 +- .../portal/services/publication2.service.ts | 9 + .../pagination/pagination.component.ts | 8 +- 7 files changed, 181 insertions(+), 107 deletions(-) diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index c773fb26c..ebcb27408 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -1,125 +1,177 @@ -
- - - +
+
-
- - -
+
+ -
-
Keywords: {{keywords}}
-
Search Parameters: {{searchParams$ | async | json}}
-
-
- - -
+ +
+ + + -
-
-
- + + - - - + +
+ {{tab}} +
+
- + +
+ {{tab}} +
+
+
+
- +
+
+

Julkaisut - {{total$ | async}}

+
-
- +
+ Näytetään tulokset 1 - 10 / 777912 - -
- - {{yearFilter.year}} - ({{yearFilter.count}}) -
-
-
+ - -
+ tulosta / sivu -
- - - - -
- - {{organizationFilter.name}} - ({{organizationFilter.count}}) -
-
-
+ Mitä julkaisutietoja palvelu sisältää? +
+
+ + + +
+
+
+
+ +
+
+ +
+ +
+
+ {{yearFilter.year}} +
+ +
+ ({{yearFilter.count}}) +
+
+ + +
+
+
+ +
+ +
+ +
+
+ +
+ +
+
+ {{organizationFilter.name}} +
+ +
+ ({{organizationFilter.count}}) +
+
+
+
+
+
-
-
-
+
+
- - + + - - -
-
{{yearFilter.year}}
- -
+ + +
+
{{yearFilter.year}}
+ +
+
-
-
+
- - - publicationName +
+ + + Julkaisun nimi + - -
-
-
+ +
+
+ - - authorsText + + + Tekijät + - -
-
-
+ +
+
+
- - publisherName + + + Julkaisukanava + - -
-
-
+ +
+
+
- - publicationYear + + + Julkaisuvuosi + - -
-
-
+ +
+
+
- - -
+ + + -
- +
+ +
diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 07d47017e..ef3df9e4e 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -12,13 +12,15 @@ import { } from '@portal/services/publication2.service'; import { map, take } from 'rxjs/operators'; import { SharedModule } from '@shared/shared.module'; +import { SearchBar2Component } from '@portal/search-bar2/search-bar2.component'; @Component({ selector: 'app-publications2', templateUrl: './publications2.component.html', styleUrls: ['./publications2.component.scss'], imports: [CdkTableModule, FormsModule, AsyncPipe, JsonPipe, NgForOf, NgIf, - SharedModule // TODO not good? + SharedModule, FormsModule, //TODO not good? + SearchBar2Component, ], standalone: true }) @@ -27,7 +29,6 @@ export class Publications2Component implements OnDestroy { router = inject(Router); publications2Service = inject(Publication2Service); - // input and pagination inputs keywords = ""; page = 1; size = 10; @@ -43,7 +44,6 @@ export class Publications2Component implements OnDestroy { // TODO joka kerta uusi HTTP? organizationNames$ = this.publications2Service.getOrganizationNames(); - yearAdditions$ = this.aggregations$.pipe( map(aggs => getYearAdditions(aggs).map((bucket: any) => ({ year: bucket.key.toString(), count: bucket.doc_count })) ?? []), map(aggs => aggs.sort((a, b) => b.year - a.year)) @@ -79,6 +79,8 @@ export class Publications2Component implements OnDestroy { this.size = parseInt(searchParams.size?.[0] ?? "10"); }); + total$ = this.publications2Service.getTotal(); + ngOnDestroy() { this.searchParamsSubscription.unsubscribe(); } @@ -141,7 +143,11 @@ export class Publications2Component implements OnDestroy { }); } + public num = 0; + setPageSize(size: number) { + console.log(size); + this.router.navigate([], { relativeTo: this.route, queryParams: { size }, queryParamsHandling: 'merge' diff --git a/src/app/portal/components/results/results.component.html b/src/app/portal/components/results/results.component.html index d7031a138..2882283a1 100644 --- a/src/app/portal/components/results/results.component.html +++ b/src/app/portal/components/results/results.component.html @@ -8,10 +8,6 @@

-
- -
-
- + + -
diff --git a/src/app/portal/search-bar2/search-bar2.component.ts b/src/app/portal/search-bar2/search-bar2.component.ts index a59b944be..3df1cfd4b 100644 --- a/src/app/portal/search-bar2/search-bar2.component.ts +++ b/src/app/portal/search-bar2/search-bar2.component.ts @@ -1,4 +1,4 @@ -import { Component, inject } from '@angular/core'; +import { Component, EventEmitter, inject, Input, Output } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatRippleModule } from '@angular/material/core'; import { FormsModule } from '@angular/forms'; @@ -15,8 +15,12 @@ export class SearchBar2Component { route = inject(ActivatedRoute); router = inject(Router) - /* Input/Output for keywords; Keep the keyword management in the parent? */ - /* Can be changed later easily */ + // two-way "value" binding that's a string; basically text input + @Input() value = ""; + @Output() valueChange = new EventEmitter(); + + // search is pressed output + @Output() search = new EventEmitter(); public keywords = this.route.snapshot.queryParams.q ?? ""; diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 35f56abe3..7de4e05a2 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -64,6 +64,10 @@ export class Publication2Service { shareReplay({ bufferSize: 1, refCount: true }) ); + resultsTotal$ = this.searchResults$.pipe( + map((data) => data.hits.total.value) + ); + publicationSearch$: Observable = this.searchResults$.pipe( map((data) => parsePublicationSearch(data)), map((publicationSearch: PublicationSearch) => this.createHighlightedPublications(publicationSearch)) @@ -77,6 +81,10 @@ export class Publication2Service { return this.publicationSearch$; } + getTotal() { + return this.resultsTotal$; + } + getAggregations() { return this.publicationAggregations$; } @@ -96,6 +104,7 @@ export class Publication2Service { return this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', { from: from, size: size, + track_total_hits: true, query: { bool: { must: { diff --git a/src/app/shared/components/pagination/pagination.component.ts b/src/app/shared/components/pagination/pagination.component.ts index db7740f7d..182ad63bb 100644 --- a/src/app/shared/components/pagination/pagination.component.ts +++ b/src/app/shared/components/pagination/pagination.component.ts @@ -26,7 +26,7 @@ import { map } from 'rxjs/operators'; export class PaginationComponent implements OnInit { @Input() page: number = 1; @Input() pageSize: number = 10; - @Input() total: any; + @Input() total: number = 0; fromPage: number; // Used for HTML rendering @@ -88,6 +88,8 @@ function countTotalPages(totalDocuments, pageSize): number { } function generatePages(currentPage: number, range: 5 | 9, results: number, pageSize: number): number[] { + console.log(currentPage, range, results, pageSize); + let output: number[] = []; const maxPage = countTotalPages(results, pageSize); const i = currentPage; @@ -100,8 +102,8 @@ function generatePages(currentPage: number, range: 5 | 9, results: number, pageS output = [i-4, i-3, i-2, i-1, i, i+1, i+2, i+3, i+4]; } - let min = Math.min(...output); - let max = Math.max(...output); + const min = Math.min(...output); + const max = Math.max(...output); if (min < 1) { const increment = 1 - output[0]; From 7fe7b0ab75757040233920d64ea27083b9cf311c Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Tue, 7 Nov 2023 13:21:51 +0200 Subject: [PATCH 08/47] Improve the presentation of the search. Add Playwright. --- .gitignore | 3 + package-lock.json | 847 +++++++++++++----- package.json | 2 + playwright.config.ts | 77 ++ .../publications2.component.html | 44 +- .../publications2/publications2.component.ts | 41 +- .../portal/services/publication2.service.ts | 98 +- tests/example.spec.ts | 18 + 8 files changed, 853 insertions(+), 277 deletions(-) create mode 100644 playwright.config.ts create mode 100644 tests/example.spec.ts diff --git a/.gitignore b/.gitignore index af98ecfb6..f37d1f47b 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,6 @@ src/environments/environment.researchfi.ts src/environments/environment.researchfi.prod.ts src/assets/config/config.json src/assets/config/auth_config.json +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/package-lock.json b/package-lock.json index ffb567247..90afe3d6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,6 +55,7 @@ "mississippi": "^4.0.0", "ngx-bootstrap": "^9.0.0", "ngx-countup": "^13.0.0", + "ngx-pipes": "^3.2.2", "nodemailer": "^6.7.8", "popper.js": "^1.16.0", "referrer-policy": "^1.2.0", @@ -84,6 +85,7 @@ "@babel/preset-env": "^7.16.11", "@fortawesome/fontawesome-free": "^6.0.0", "@ngx-i18nsupport/ngx-i18nsupport": "^1.1.6", + "@playwright/test": "^1.39.0", "@types/d3": "^7.1.0", "@types/express": "^4.17.11", "@types/jasmine": "~3.6.6", @@ -3611,10 +3613,9 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -3624,7 +3625,6 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -3935,6 +3935,21 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/@playwright/test": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", + "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==", + "dev": true, + "dependencies": { + "playwright": "1.39.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -4282,7 +4297,6 @@ "version": "8.4.10", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", - "dev": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -4292,7 +4306,6 @@ "version": "3.7.4", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -4301,8 +4314,7 @@ "node_modules/@types/estree": { "version": "0.0.51", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" }, "node_modules/@types/express": { "version": "4.17.14", @@ -4360,8 +4372,7 @@ "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, "node_modules/@types/lodash": { "version": "4.14.188", @@ -4387,8 +4398,7 @@ "node_modules/@types/node": { "version": "17.0.45", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", - "dev": true + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -4740,7 +4750,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1" @@ -4749,26 +4758,22 @@ "node_modules/@webassemblyjs/floating-point-hex-parser": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -4778,14 +4783,12 @@ "node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -4797,7 +4800,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -4806,7 +4808,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -4814,14 +4815,12 @@ "node_modules/@webassemblyjs/utf8": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -4837,7 +4836,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", @@ -4850,7 +4848,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -4862,7 +4859,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -4876,7 +4872,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" @@ -4921,14 +4916,12 @@ "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", @@ -4960,9 +4953,9 @@ } }, "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "bin": { "acorn": "bin/acorn" }, @@ -4991,10 +4984,9 @@ } }, "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "peerDependencies": { "acorn": "^8" } @@ -5102,7 +5094,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5157,7 +5148,6 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, "peerDependencies": { "ajv": "^6.9.1" } @@ -5966,7 +5956,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, "engines": { "node": ">=6.0" } @@ -6096,8 +6085,7 @@ "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "node_modules/commondir": { "version": "1.0.1", @@ -7702,10 +7690,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", - "dev": true, + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -7814,8 +7801,7 @@ "node_modules/es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" }, "node_modules/es6-promise": { "version": "4.2.8", @@ -8317,7 +8303,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -8330,7 +8315,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, "engines": { "node": ">=4.0" } @@ -8668,7 +8652,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -8716,7 +8699,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, "engines": { "node": ">=0.8.x" } @@ -8886,8 +8868,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.2.12", @@ -8920,8 +8901,7 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -9402,8 +9382,7 @@ "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "node_modules/globals": { "version": "11.12.0", @@ -9447,8 +9426,7 @@ "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "node_modules/grapheme-splitter": { "version": "1.0.4", @@ -10764,7 +10742,6 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -10778,7 +10755,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -10787,7 +10763,6 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -10918,8 +10893,7 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema": { "version": "0.4.0", @@ -10930,8 +10904,7 @@ "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -11410,7 +11383,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, "engines": { "node": ">=6.11.5" } @@ -11845,8 +11817,7 @@ "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "node_modules/merge2": { "version": "1.4.1", @@ -12175,9 +12146,15 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -12249,8 +12226,7 @@ "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node_modules/ngx-bootstrap": { "version": "9.0.0", @@ -12280,6 +12256,223 @@ "@angular/core": ">=13.0.0" } }, + "node_modules/ngx-pipes": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/ngx-pipes/-/ngx-pipes-3.2.2.tgz", + "integrity": "sha512-nhNIUJe/bud5ir4RD2YDA6MdrJUh3YASIcNrMFX9WljTxuvAb99SZ88PaKSFGy9SXjHg8MxjxHfCoOLlTkxxlQ==", + "dependencies": { + "postcss": "^8.4.19", + "tslib": "^2.3.0", + "webpack": "^5.75.0" + }, + "peerDependencies": { + "@angular/core": ">=14" + } + }, + "node_modules/ngx-pipes/node_modules/@types/estree": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", + "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==" + }, + "node_modules/ngx-pipes/node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/ngx-pipes/node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" + }, + "node_modules/ngx-pipes/node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" + }, + "node_modules/ngx-pipes/node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" + }, + "node_modules/ngx-pipes/node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/ngx-pipes/node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" + }, + "node_modules/ngx-pipes/node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/ngx-pipes/node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/ngx-pipes/node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/ngx-pipes/node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" + }, + "node_modules/ngx-pipes/node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/ngx-pipes/node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/ngx-pipes/node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/ngx-pipes/node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/ngx-pipes/node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/ngx-pipes/node_modules/es-module-lexer": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", + "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==" + }, + "node_modules/ngx-pipes/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/ngx-pipes/node_modules/webpack": { + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", @@ -13468,6 +13661,36 @@ "node": ">=8" } }, + "node_modules/playwright": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz", + "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==", + "dev": true, + "dependencies": { + "playwright-core": "1.39.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", + "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/popper.js": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", @@ -13479,9 +13702,9 @@ } }, "node_modules/postcss": { - "version": "8.4.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", - "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -13490,10 +13713,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -14730,7 +14957,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -15584,10 +15810,9 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "dependencies": { "randombytes": "^2.1.0" } @@ -15854,7 +16079,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -15904,7 +16128,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -16239,7 +16462,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -16279,16 +16501,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", - "dev": true, + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.14", + "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" }, "engines": { "node": ">= 10.13.0" @@ -16316,7 +16537,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -16330,6 +16550,23 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/terser-webpack-plugin/node_modules/terser": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.22.0.tgz", + "integrity": "sha512-hHZVLgRA2z4NWcN6aS5rQDc+7Dcy58HOf2zbYwmFcQ+ua3h6eEFf5lIDKTzbWwlazPyOZsFQO8V80/IjVNExEw==", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -16795,7 +17032,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -16919,7 +17155,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -16971,7 +17206,6 @@ "version": "5.74.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", - "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", @@ -17280,7 +17514,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, "engines": { "node": ">=10.13.0" } @@ -17310,7 +17543,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -20093,10 +20325,9 @@ "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" }, "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "requires": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -20106,7 +20337,6 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, "requires": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -20346,6 +20576,15 @@ "which": "^2.0.2" } }, + "@playwright/test": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", + "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==", + "dev": true, + "requires": { + "playwright": "1.39.0" + } + }, "@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -20681,7 +20920,6 @@ "version": "8.4.10", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", - "dev": true, "requires": { "@types/estree": "*", "@types/json-schema": "*" @@ -20691,7 +20929,6 @@ "version": "3.7.4", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, "requires": { "@types/eslint": "*", "@types/estree": "*" @@ -20700,8 +20937,7 @@ "@types/estree": { "version": "0.0.51", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" }, "@types/express": { "version": "4.17.14", @@ -20759,8 +20995,7 @@ "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, "@types/lodash": { "version": "4.14.188", @@ -20786,8 +21021,7 @@ "@types/node": { "version": "17.0.45", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", - "dev": true + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" }, "@types/parse-json": { "version": "4.0.0", @@ -21029,7 +21263,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, "requires": { "@webassemblyjs/helper-numbers": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1" @@ -21038,26 +21271,22 @@ "@webassemblyjs/floating-point-hex-parser": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" }, "@webassemblyjs/helper-api-error": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" }, "@webassemblyjs/helper-buffer": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" }, "@webassemblyjs/helper-numbers": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, "requires": { "@webassemblyjs/floating-point-hex-parser": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -21067,14 +21296,12 @@ "@webassemblyjs/helper-wasm-bytecode": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" }, "@webassemblyjs/helper-wasm-section": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -21086,7 +21313,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } @@ -21095,7 +21321,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, "requires": { "@xtuc/long": "4.2.2" } @@ -21103,14 +21328,12 @@ "@webassemblyjs/utf8": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" }, "@webassemblyjs/wasm-edit": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -21126,7 +21349,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", @@ -21139,7 +21361,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -21151,7 +21372,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -21165,7 +21385,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" @@ -21197,14 +21416,12 @@ "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" }, "@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, "@yarnpkg/lockfile": { "version": "1.1.0", @@ -21233,9 +21450,9 @@ } }, "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==" }, "acorn-globals": { "version": "6.0.0", @@ -21254,10 +21471,9 @@ } }, "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "requires": {} }, "acorn-jsx": { @@ -21341,7 +21557,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -21382,7 +21597,6 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, "requires": {} }, "angular-auth-oidc-client": { @@ -21967,8 +22181,7 @@ "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==" }, "clean-stack": { "version": "2.2.0", @@ -22065,8 +22278,7 @@ "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "commondir": { "version": "1.0.1", @@ -23396,10 +23608,9 @@ "dev": true }, "enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", - "dev": true, + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -23489,8 +23700,7 @@ "es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" }, "es6-promise": { "version": "4.2.8", @@ -23941,7 +24151,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -23950,8 +24159,7 @@ "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" } } }, @@ -24007,7 +24215,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "requires": { "estraverse": "^5.2.0" } @@ -24042,8 +24249,7 @@ "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, "execa": { "version": "5.1.1", @@ -24174,8 +24380,7 @@ "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { "version": "3.2.12", @@ -24204,8 +24409,7 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { "version": "2.0.6", @@ -24571,8 +24775,7 @@ "glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "globals": { "version": "11.12.0", @@ -24604,8 +24807,7 @@ "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "grapheme-splitter": { "version": "1.0.4", @@ -25579,7 +25781,6 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -25589,14 +25790,12 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -25692,8 +25891,7 @@ "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "json-schema": { "version": "0.4.0", @@ -25704,8 +25902,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -26088,8 +26285,7 @@ "loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==" }, "loader-utils": { "version": "3.2.1", @@ -26424,8 +26620,7 @@ "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "merge2": { "version": "1.4.1", @@ -26669,9 +26864,9 @@ "dev": true }, "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" }, "natural-compare": { "version": "1.4.0", @@ -26727,8 +26922,7 @@ "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "ngx-bootstrap": { "version": "9.0.0", @@ -26747,6 +26941,200 @@ "tslib": "^2.0.0" } }, + "ngx-pipes": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/ngx-pipes/-/ngx-pipes-3.2.2.tgz", + "integrity": "sha512-nhNIUJe/bud5ir4RD2YDA6MdrJUh3YASIcNrMFX9WljTxuvAb99SZ88PaKSFGy9SXjHg8MxjxHfCoOLlTkxxlQ==", + "requires": { + "postcss": "^8.4.19", + "tslib": "^2.3.0", + "webpack": "^5.75.0" + }, + "dependencies": { + "@types/estree": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", + "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==" + }, + "@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "requires": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "es-module-lexer": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", + "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==" + }, + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "webpack": { + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + } + } + } + }, "nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", @@ -27663,17 +28051,33 @@ "find-up": "^4.0.0" } }, + "playwright": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz", + "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.39.0" + } + }, + "playwright-core": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", + "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", + "dev": true + }, "popper.js": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" }, "postcss": { - "version": "8.4.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", - "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "requires": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } @@ -28504,7 +28908,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -29161,10 +29564,9 @@ } }, "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "requires": { "randombytes": "^2.1.0" } @@ -29382,8 +29784,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-js": { "version": "1.0.2", @@ -29416,7 +29817,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -29666,8 +30066,7 @@ "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" }, "tar": { "version": "6.1.12", @@ -29695,28 +30094,37 @@ } }, "terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", - "dev": true, + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", "requires": { - "@jridgewell/trace-mapping": "^0.3.14", + "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" }, "dependencies": { "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, "requires": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } + }, + "terser": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.22.0.tgz", + "integrity": "sha512-hHZVLgRA2z4NWcN6aS5rQDc+7Dcy58HOf2zbYwmFcQ+ua3h6eEFf5lIDKTzbWwlazPyOZsFQO8V80/IjVNExEw==", + "requires": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + } } } }, @@ -30047,7 +30455,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -30151,7 +30558,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -30194,7 +30600,6 @@ "version": "5.74.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", - "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", @@ -30226,7 +30631,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, "requires": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -30414,8 +30818,7 @@ "webpack-sources": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==" }, "webpack-subresource-integrity": { "version": "5.1.0", diff --git a/package.json b/package.json index 06253ae87..7f44a6962 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "mississippi": "^4.0.0", "ngx-bootstrap": "^9.0.0", "ngx-countup": "^13.0.0", + "ngx-pipes": "^3.2.2", "nodemailer": "^6.7.8", "popper.js": "^1.16.0", "referrer-policy": "^1.2.0", @@ -99,6 +100,7 @@ "@babel/preset-env": "^7.16.11", "@fortawesome/fontawesome-free": "^6.0.0", "@ngx-i18nsupport/ngx-i18nsupport": "^1.1.6", + "@playwright/test": "^1.39.0", "@types/d3": "^7.1.0", "@types/express": "^4.17.11", "@types/jasmine": "~3.6.6", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 000000000..301801ee1 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,77 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index ebcb27408..20f340d60 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -38,7 +38,7 @@

Julkaisut - {{total$ | async}}

- Näytetään tulokset 1 - 10 / 777912 + Näytetään tulokset {{(page - 1) * size + 1}} - {{page * size}} / {{total$ | async}} @@ -77,31 +77,45 @@

Julkaisut - {{total$ | async}}

-
+
+
-
- -
-
- -
+ -
-
- {{organizationFilter.name}} +
+ +

{{sectorName[sectorId]}}

+ + + +
+
+
-
- ({{organizationFilter.count}}) +
+
+ {{organizationFilter.name}} +
+ +
+ ({{organizationFilter.count}}) +
-
+
+ +
diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index ef3df9e4e..60610ce98 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, OnDestroy } from '@angular/core'; +import { Component, inject, OnDestroy, Pipe, PipeTransform } from '@angular/core'; import { CdkTableModule, DataSource } from '@angular/cdk/table'; import { combineLatest, Observable } from 'rxjs'; import { ActivatedRoute, Router } from '@angular/router'; @@ -13,12 +13,27 @@ import { import { map, take } from 'rxjs/operators'; import { SharedModule } from '@shared/shared.module'; import { SearchBar2Component } from '@portal/search-bar2/search-bar2.component'; +import { NgArrayPipesModule } from 'ngx-pipes'; + +@Pipe({ + name: 'limit', + standalone: true +}) +export class LimitPipe implements PipeTransform { + transform(value: any[], limit: number, enabled = true): any[] { + if (enabled) { + return value.slice(0, limit); + } else { + return value; + } + } +} @Component({ selector: 'app-publications2', templateUrl: './publications2.component.html', styleUrls: ['./publications2.component.scss'], - imports: [CdkTableModule, FormsModule, AsyncPipe, JsonPipe, NgForOf, NgIf, + imports: [CdkTableModule, FormsModule, AsyncPipe, JsonPipe, NgForOf, NgIf, LimitPipe, NgArrayPipesModule, SharedModule, FormsModule, //TODO not good? SearchBar2Component, ], @@ -33,6 +48,8 @@ export class Publications2Component implements OnDestroy { page = 1; size = 10; + organizationName = ""; + displayedColumns: string[] = ['publicationName', 'authorsText', 'publisherName', 'publicationYear']; highlights$ = this.publications2Service.getSearch(); // TODO: /*: Observable*/ @@ -41,9 +58,6 @@ export class Publications2Component implements OnDestroy { searchParams$ = this.route.queryParams.pipe( map(splitFields) ); aggregations$ = this.publications2Service.getAggregations(); - // TODO joka kerta uusi HTTP? - organizationNames$ = this.publications2Service.getOrganizationNames(); - yearAdditions$ = this.aggregations$.pipe( map(aggs => getYearAdditions(aggs).map((bucket: any) => ({ year: bucket.key.toString(), count: bucket.doc_count })) ?? []), map(aggs => aggs.sort((a, b) => b.year - a.year)) @@ -57,8 +71,11 @@ export class Publications2Component implements OnDestroy { }))) ); + // TODO joka kerta uusi HTTP? + organizationNames$ = this.publications2Service.getOrganizationNames(); + organizationAdditions$ = this.aggregations$.pipe( - map(aggs => getOrganizationAdditions(aggs).map((bucket: any) => ({ id: bucket.key, count: bucket.doc_count })) ?? []), + map(aggs => getOrganizationAdditions(aggs).map((bucket: any) => ({ id: bucket.key, count: bucket.doc_count})) ?? []), map(aggs => aggs.sort((a, b) => b.count - a.count)) ); @@ -66,11 +83,21 @@ export class Publications2Component implements OnDestroy { map(([organizationAdditions, organizationNames, enabledFilters]) => organizationAdditions.map(organizationAddition => ({ id: organizationAddition.id, count: organizationAddition.count, - name: organizationNames[organizationAddition.id] ?? organizationAddition.id, + name: organizationNames[organizationAddition.id].name, + sectorId: organizationNames[organizationAddition.id].sectorId, enabled: enabledFilters.includes(organizationAddition.id) }))) ); + /* TODO localization solution */ + public sectorName = { + 1: "Yliopisto", + 2: "Ammattikorkeakoulu", + 3: "Tutkimuslaitos", + 4: "Yliopistollisen sairaalan erityisvastuualue", + 6: "Muu" + } + searchParamsSubscription = this.searchParams$.subscribe(searchParams => { this.publications2Service.updateSearchTerms(searchParams); diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 7de4e05a2..48bfa3a6e 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -1,6 +1,6 @@ import { inject, Injectable, LOCALE_ID, OnInit, SecurityContext } from '@angular/core'; // import { object, Output, parse, string } from 'valibot'; -import { BehaviorSubject, Observable, shareReplay } from 'rxjs'; +import { BehaviorSubject, forkJoin, Observable, shareReplay } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { map, switchMap, take, tap } from 'rxjs/operators'; import { ActivatedRoute } from '@angular/router'; @@ -163,23 +163,38 @@ export class Publication2Service { }; } - getOrganizationNames() { - // API call to search by doing aggregation on organization names - const res$ = this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', { - "size": 0, - "aggs": { - "organizations": { - "nested": { - "path": "author.organization" + getOrganizationNames(): Observable> { + const organizationNamesBySector = (sectorId: number) => ({ + 'size': 0, + 'aggs': { + 'filtered_authors': { + 'nested': { + 'path': 'author' }, - "aggs": { - "composite_orgs": { - "composite": { - "size": 10000, - "sources": [ - { "id": { "terms": { "field": "author.organization.organizationId.keyword" } } }, - { "name": { "terms": { "field": path`author.organization.OrganizationNameEn.keyword` } } } - ] + 'aggs': { + 'single_sector': { + 'filter': { + 'term': { + 'author.sectorId': `${sectorId}` + } + }, + 'aggs': { + 'organizations': { + 'nested': { + 'path': 'author.organization' + }, + 'aggs': { + 'composite_orgs': { + 'composite': { + 'size': 65536, + 'sources': [ + { 'id': { 'terms': { 'field': 'author.organization.organizationId.keyword' } } }, + { 'name': { 'terms': { 'field': 'author.organization.OrganizationNameFi.keyword' } } } // TODO path template needed + ] + } + } + } + } } } } @@ -187,10 +202,18 @@ export class Publication2Service { } }); - return res$.pipe( - map((data) => toIdNameLookup(data)), - shareReplay({ bufferSize: 1, refCount: true }) - ); + const sectorIds = [1, 2, 3, 4, /*5,*/ 6]; + + const responses$ = sectorIds.map((sectorId) => { + return this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', organizationNamesBySector(sectorId)).pipe( + map((res) => getOrganizationNameBuckets(res).map((bucket) => [bucket.key.id, {name: bucket.key.name, sectorId}])), + ); + }); + + return forkJoin(responses$) + .pipe(map(responses => responses.flat())) + .pipe(map(pairs => Object.fromEntries(pairs))) + .pipe(shareReplay({ bufferSize: 1, refCount: true })); } } @@ -266,7 +289,8 @@ function additionsFromYear(searchParams: SearchParams) { "aggregations": { "all_publicationYears": { "terms": { - "field": "publicationYear" + "field": "publicationYear", + "size": 250, } } } @@ -424,7 +448,8 @@ function additionsFromOrganization(searchParams: SearchParams) { "aggregations": { "all_organizationIds": { "terms": { - "field": "author.organization.organizationId.keyword" + "field": "author.organization.organizationId.keyword", + "size": 250, // author.organization.OrganizationNameFi.keyword // "field": i18n`author.organization.${"OrganizationName"}.keyword` } @@ -440,23 +465,30 @@ function additionsFromOrganization(searchParams: SearchParams) { type OrgsAggsResponse = { aggregations: { - organizations: { - composite_orgs: { - buckets: Array<{ - key: { - id: string; - name: string; + filtered_authors: { + single_sector: { + organizations: { + composite_orgs: { + buckets: Array<{ + key: { + id: string; + name: string; + }; + doc_count: number; + }>; }; - doc_count: number; - }>; + }; }; }; }; }; +function getOrganizationNameBuckets(response: OrgsAggsResponse) { + return response.aggregations.filtered_authors.single_sector.organizations.composite_orgs.buckets; +} + function toIdNameLookup(data: OrgsAggsResponse): Map { - const pairs = data.aggregations.organizations.composite_orgs.buckets - .map((bucket) => [bucket.key.id, bucket.key.name]) + const pairs = getOrganizationNameBuckets(data).map((bucket) => [bucket.key.id, bucket.key.name]); return Object.fromEntries(pairs); } diff --git a/tests/example.spec.ts b/tests/example.spec.ts new file mode 100644 index 000000000..54a906a4e --- /dev/null +++ b/tests/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects page to have a heading with the name of Installation. + await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +}); From 0065683445e60421ef427bf969674d229a9fb7eb Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Wed, 15 Nov 2023 14:24:00 +0200 Subject: [PATCH 09/47] Add publicationFormat and publicationAudience filters --- .../filter-option.component.html | 15 + .../filter-option.component.scss | 16 + .../filter-option/filter-option.component.ts | 26 ++ .../organization-filter.component.html | 54 ++++ .../organization-filter.component.scss | 53 ++++ .../organization-filter.component.ts | 51 +++ .../publications2.component.html | 65 ++-- .../publications2/publications2.component.ts | 73 +++-- src/app/portal/pipes/limit.pipe.ts | 20 ++ .../portal/services/publication2.service.ts | 290 ++++++++++++++++-- 10 files changed, 594 insertions(+), 69 deletions(-) create mode 100644 src/app/portal/components/filter-option/filter-option.component.html create mode 100644 src/app/portal/components/filter-option/filter-option.component.scss create mode 100644 src/app/portal/components/filter-option/filter-option.component.ts create mode 100644 src/app/portal/components/organization-filter/organization-filter.component.html create mode 100644 src/app/portal/components/organization-filter/organization-filter.component.scss create mode 100644 src/app/portal/components/organization-filter/organization-filter.component.ts create mode 100644 src/app/portal/pipes/limit.pipe.ts diff --git a/src/app/portal/components/filter-option/filter-option.component.html b/src/app/portal/components/filter-option/filter-option.component.html new file mode 100644 index 000000000..d7c087ad4 --- /dev/null +++ b/src/app/portal/components/filter-option/filter-option.component.html @@ -0,0 +1,15 @@ +
+
+ +
+ +
+
+ {{label}} +
+ +
+ ({{count}}) +
+
+
diff --git a/src/app/portal/components/filter-option/filter-option.component.scss b/src/app/portal/components/filter-option/filter-option.component.scss new file mode 100644 index 000000000..66d046b6f --- /dev/null +++ b/src/app/portal/components/filter-option/filter-option.component.scss @@ -0,0 +1,16 @@ +.label-text { + text-overflow: ellipsis; + flex-shrink: 1; +} + +.filter-option-layout { + display: flex; + align-items: center; + padding: 16px 12px; + cursor: pointer; + user-select: none; + + &:hover { + background-color: rgba(0, 0, 0, 0.04); + } +} diff --git a/src/app/portal/components/filter-option/filter-option.component.ts b/src/app/portal/components/filter-option/filter-option.component.ts new file mode 100644 index 000000000..69fe8d1e8 --- /dev/null +++ b/src/app/portal/components/filter-option/filter-option.component.ts @@ -0,0 +1,26 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { MatRippleModule } from '@angular/material/core'; + +@Component({ + selector: 'app-filter-option', + templateUrl: './filter-option.component.html', + styleUrls: ['./filter-option.component.scss'], + imports: [ + FormsModule, + MatRippleModule + ], + standalone: true +}) +export class FilterOptionComponent { + @Input() label = ""; + @Input() count = 0; + + @Input() value: boolean = false; + @Output() valueChange = new EventEmitter(); + + toggleValue() { + this.value = !this.value; + this.valueChange.emit(this.value); + } +} diff --git a/src/app/portal/components/organization-filter/organization-filter.component.html b/src/app/portal/components/organization-filter/organization-filter.component.html new file mode 100644 index 000000000..9a0ec15db --- /dev/null +++ b/src/app/portal/components/organization-filter/organization-filter.component.html @@ -0,0 +1,54 @@ +
+ +
+ - Organisaatio +
+ + + + +
+
{{sectorNames[sectorId]}}
+ + {{ accordionItem.expanded ? 'close' : 'open' }} +
+ +
+
Valitse kaikki
+ + + + + + + + + + + + +
+ +
+
+
+ + +
+ + Organisaatio +
+
+
+ + + + diff --git a/src/app/portal/components/organization-filter/organization-filter.component.scss b/src/app/portal/components/organization-filter/organization-filter.component.scss new file mode 100644 index 000000000..dd50f90b7 --- /dev/null +++ b/src/app/portal/components/organization-filter/organization-filter.component.scss @@ -0,0 +1,53 @@ +.example-accordion { + display: block; + max-width: 500px; +} + +.example-accordion-item { + display: block; + border: solid 1px #ccc; +} + +.example-accordion-item + .example-accordion-item { + border-top: none; +} + +.example-accordion-item-header { + display: flex; + align-content: center; + justify-content: space-between; +} + +.example-accordion-item-description { + font-size: 0.85em; + color: #999; +} + +/*.example-accordion-item-header, +.example-accordion-item-body { + padding: 16px; +}*/ + +.example-accordion-item-header:hover { + cursor: pointer; + background-color: #eee; +} + +.example-accordion-item:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} + +.example-accordion-item:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; +} + +/**/ + +.filter-heading { + font-size: 17.6px; + font-weight: 700; + + padding: 11px 16px; +} diff --git a/src/app/portal/components/organization-filter/organization-filter.component.ts b/src/app/portal/components/organization-filter/organization-filter.component.ts new file mode 100644 index 000000000..1599cb846 --- /dev/null +++ b/src/app/portal/components/organization-filter/organization-filter.component.ts @@ -0,0 +1,51 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { AsyncPipe, NgForOf, NgIf } from '@angular/common'; +import { FilterOptionComponent } from '@portal/components/filter-option/filter-option.component'; +import { NgArrayPipesModule } from 'ngx-pipes'; +import { FormsModule } from '@angular/forms'; +import { LimitPipe } from '@portal/pipes/limit.pipe'; +import { CdkAccordionModule } from '@angular/cdk/accordion'; + +@Component({ + selector: 'app-organization-filter', + templateUrl: './organization-filter.component.html', + styleUrls: ['./organization-filter.component.scss'], + imports: [ + NgIf, + AsyncPipe, + FilterOptionComponent, + LimitPipe, + NgArrayPipesModule, + NgForOf, + FormsModule, + CdkAccordionModule + ], + standalone: true +}) +export class OrganizationFilterComponent { + @Input() filterData: unknown; + @Output() selected = new EventEmitter(); + + expanded = false; + sectorFilter: Record = {}; + + sectorLimits: Record = { + 1: 10, + 2: 10, + 3: 10, + 4: 10, + 6: 10, + }; + + public sectorNames = { + 1: "Yliopisto", + 2: "Ammattikorkeakoulu", + 3: "Tutkimuslaitos", + 4: "Yliopistollisen sairaalan erityisvastuualue", + 6: "Muu" + } + + toggleExpanded() { + this.expanded = !this.expanded; + } +} diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index 20f340d60..81bfffbea 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -81,41 +81,50 @@

Julkaisut - {{total$ | async}}

-
- -
+ +
- + -
- -

{{sectorName[sectorId]}}

- - - -
-
- -
+ +
-
-
- {{organizationFilter.name}} -
+
+ + + + +
-
- ({{organizationFilter.count}}) -
-
-
+ +
+ +
+
+ + + - +
+
+ +
+
+ + + +
diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 60610ce98..a90e4fc88 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -5,7 +5,8 @@ import { ActivatedRoute, Router } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { AsyncPipe, JsonPipe, NgForOf, NgIf } from '@angular/common'; import { - getOrganizationAdditions, + getLanguageCodeAdditions, + getOrganizationAdditions, getPublicationAudienceAdditions, getPublicationFormatAdditions, getYearAdditions, HighlightedPublication, Publication2Service @@ -14,28 +15,18 @@ import { map, take } from 'rxjs/operators'; import { SharedModule } from '@shared/shared.module'; import { SearchBar2Component } from '@portal/search-bar2/search-bar2.component'; import { NgArrayPipesModule } from 'ngx-pipes'; - -@Pipe({ - name: 'limit', - standalone: true -}) -export class LimitPipe implements PipeTransform { - transform(value: any[], limit: number, enabled = true): any[] { - if (enabled) { - return value.slice(0, limit); - } else { - return value; - } - } -} +import { OrganizationFilterComponent } from '@portal/components/organization-filter/organization-filter.component'; +import { FilterOptionComponent } from '@portal/components/filter-option/filter-option.component'; +import { LimitPipe } from '@portal/pipes/limit.pipe'; @Component({ selector: 'app-publications2', templateUrl: './publications2.component.html', styleUrls: ['./publications2.component.scss'], imports: [CdkTableModule, FormsModule, AsyncPipe, JsonPipe, NgForOf, NgIf, LimitPipe, NgArrayPipesModule, - SharedModule, FormsModule, //TODO not good? - SearchBar2Component, + SharedModule, //TODO not good? + FormsModule, + SearchBar2Component, OrganizationFilterComponent, FilterOptionComponent ], standalone: true }) @@ -48,8 +39,6 @@ export class Publications2Component implements OnDestroy { page = 1; size = 10; - organizationName = ""; - displayedColumns: string[] = ['publicationName', 'authorsText', 'publisherName', 'publicationYear']; highlights$ = this.publications2Service.getSearch(); // TODO: /*: Observable*/ @@ -71,7 +60,6 @@ export class Publications2Component implements OnDestroy { }))) ); - // TODO joka kerta uusi HTTP? organizationNames$ = this.publications2Service.getOrganizationNames(); organizationAdditions$ = this.aggregations$.pipe( @@ -89,6 +77,51 @@ export class Publications2Component implements OnDestroy { }))) ); + languageCodeAdditions$ = this.aggregations$.pipe( + map(aggs => getLanguageCodeAdditions(aggs).map((bucket: any) => ({ languageCode: bucket.key, count: bucket.doc_count })) ?? []), + map(aggs => aggs.sort((a, b) => b.count - a.count)) + ); + + languageCodeFilters$ = combineLatest([this.languageCodeAdditions$, this.searchParams$.pipe(map(params => params.language ?? []))]).pipe( + map(([languageCodeAdditions, enabledFilters]) => languageCodeAdditions.map(languageCodeAddition => ({ + name: languageCodeAddition.languageCode, + count: languageCodeAddition.count, + enabled: enabledFilters.includes(languageCodeAddition.languageCode) + }))) + ); + + publicationFormatNames$ = this.publications2Service.getPublicationFormatNames(); + + publicationFormatAdditions$ = this.aggregations$.pipe( + map(aggs => getPublicationFormatAdditions(aggs).map((bucket: any) => ({ id: bucket.key, count: bucket.doc_count })) ?? []), + map(aggs => aggs.sort((a, b) => b.count - a.count)) + ); + + publicationFormatFilters$ = combineLatest([this.publicationFormatAdditions$, this.publicationFormatNames$, this.searchParams$.pipe(map(params => params.format ?? []))]).pipe( + map(([publicationFormatAdditions, publicationFormatNames, enabledFilters]) => publicationFormatAdditions.map(publicationFormatAddition => ({ + id: publicationFormatAddition.id, + count: publicationFormatAddition.count, + name: publicationFormatNames[publicationFormatAddition.id], + enabled: enabledFilters.includes(publicationFormatAddition.id) + }))) + ); + + publicationAudienceNames$ = this.publications2Service.getPublicationAudienceNames(); + + publicationAudienceAdditions$ = this.aggregations$.pipe( + map(aggs => getPublicationAudienceAdditions(aggs).map((bucket: any) => ({ id: bucket.key, count: bucket.doc_count })) ?? []), + map(aggs => aggs.sort((a, b) => b.count - a.count)) + ); + + publicationAudienceFilters$ = combineLatest([this.publicationAudienceAdditions$, this.publicationAudienceNames$, this.searchParams$.pipe(map(params => params.audience ?? []))]).pipe( + map(([publicationAudienceAdditions, publicationAudienceNames, enabledFilters]) => publicationAudienceAdditions.map(publicationAudienceAddition => ({ + id: publicationAudienceAddition.id, + count: publicationAudienceAddition.count, + name: publicationAudienceNames[publicationAudienceAddition.id], + enabled: enabledFilters.includes(publicationAudienceAddition.id) + }))) + ); + /* TODO localization solution */ public sectorName = { 1: "Yliopisto", diff --git a/src/app/portal/pipes/limit.pipe.ts b/src/app/portal/pipes/limit.pipe.ts new file mode 100644 index 000000000..6d193d56b --- /dev/null +++ b/src/app/portal/pipes/limit.pipe.ts @@ -0,0 +1,20 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'limit', + standalone: true +}) +export class LimitPipe implements PipeTransform { + transform(value: any[], limit: number, enabled = true): any[] { + // Async pipes return nulls + if (value == null) { + return []; + } + + if (enabled) { + return value.slice(0, limit); + } else { + return value; + } + } +} diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 48bfa3a6e..3b8c03855 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -39,6 +39,10 @@ type SearchParams = { year?: string[], organization?: string[], + language?: string[], + format?: string[], + audience?: string[], + // placeholders publicationTypeCode?: string[], publicationStatusCode?: string[], @@ -54,7 +58,7 @@ export class Publication2Service { path = suffixer(this.locale); - organizationNames$ = this.getOrganizationNames(); + // organizationNames$ = this.getOrganizationNames(); searchParams = new BehaviorSubject>({}); @@ -116,7 +120,10 @@ export class Publication2Service { ...termsForYear(searchParams), ...termsForTypeCode(searchParams), ...termsForStatusCode(searchParams), - ...termsForOrganization(searchParams) + ...termsForOrganization(searchParams), + ...termsForLanguageCode(searchParams), + ...termsForPublicationFormat(searchParams), + ...termsForPublicationAudience(searchParams) ] } } @@ -136,7 +143,10 @@ export class Publication2Service { ...additionsFromYear(searchParams), ...additionsFromTypeCode(searchParams), ...additionsFromStatusCode(searchParams), - ...additionsFromOrganization(searchParams) + ...additionsFromOrganization(searchParams), + ...additionsFromLanguageCode(searchParams), + ...additionsFromPublicationFormat(searchParams), + ...additionsFromPublicationAudience(searchParams) } }); } @@ -215,6 +225,96 @@ export class Publication2Service { .pipe(map(pairs => Object.fromEntries(pairs))) .pipe(shareReplay({ bufferSize: 1, refCount: true })); } + + getPublicationFormatNames()/*: Observable>*/ { + /*const body = { + "size": 0, + "aggs": { + "nameFiFormat": { + "terms": { + "field": "publicationFormat.id.keyword", + "size": 1000 + } + } + } + }*/ + + const body = { + 'size': 0, + 'aggs': { + 'composite_pairs': { + 'composite': { + 'size': 1000, + 'sources': [ + { 'id': { 'terms': { 'field': 'publicationFormat.id.keyword' } } }, + { 'nameFiFormat': { 'terms': { 'field': 'publicationFormat.nameFiPublicationFormat.keyword' } } } + ] + } + } + } + }; + + type PublicationFormatAggregation = { + aggregations: { + composite_pairs: { + buckets: Array<{ + key: { + id: string; + nameFiFormat: string; + }; + doc_count: number; + }>; + }; + }; + }; + + const response$ = this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', body); + + return response$.pipe( + map((res) => res.aggregations.composite_pairs.buckets.map((bucket) => [bucket.key.id, bucket.key.nameFiFormat])), // TODO localized path needed + map(pairs => Object.fromEntries(pairs)), + shareReplay({ bufferSize: 1, refCount: true }) + ); + } + + getPublicationAudienceNames()/*: Observable>*/ { + const body = { + 'size': 0, + 'aggs': { + 'composite_pairs': { + 'composite': { + 'size': 1000, + 'sources': [ + { 'id': { 'terms': { 'field': 'publicationAudience.id.keyword' } } }, + { 'nameFiAudience': { 'terms': { 'field': 'publicationAudience.nameFiPublicationAudience.keyword' } } } + ] + } + } + } + }; + + type PublicationAudienceAggregation = { + aggregations: { + composite_pairs: { + buckets: Array<{ + key: { + id: string; + nameFiAudience: string; + }; + doc_count: number; + }>; + }; + }; + }; + + const response$ = this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', body); + + return response$.pipe( + map((res) => res.aggregations.composite_pairs.buckets.map((bucket) => [bucket.key.id, bucket.key.nameFiAudience])), // TODO localized path needed + map(pairs => Object.fromEntries(pairs)), + shareReplay({ bufferSize: 1, refCount: true }) + ); + } } function matchingTerms(searchParams: SearchParams) { @@ -268,7 +368,38 @@ function termsForStatusCode(searchParams: SearchParams) { return []; } -// NOTE: additionsFrom functions must use all other "termsFor" functions but not its own +function termsForLanguageCode(searchParams: SearchParams) { + if (searchParams.language) { + return [{ + terms: { + "languages.languageCode.keyword": searchParams.language + } + }]; + } + return []; +} + +function termsForPublicationFormat(searchParams: SearchParams) { + if (searchParams.format) { + return [{ + terms: { + "publicationFormat.id.keyword": searchParams.format + } + }]; + } + return []; +} + +function termsForPublicationAudience(searchParams: SearchParams) { + if (searchParams.audience) { + return [{ + terms: { + "publicationAudience.id.keyword": searchParams.audience + } + }]; + } + return []; +} function additionsFromYear(searchParams: SearchParams) { return { @@ -282,7 +413,10 @@ function additionsFromYear(searchParams: SearchParams) { matchingTerms(searchParams), ...termsForTypeCode(searchParams), ...termsForOrganization(searchParams), - ...termsForStatusCode(searchParams) + ...termsForStatusCode(searchParams), + ...termsForLanguageCode(searchParams), + ...termsForPublicationFormat(searchParams), + ...termsForPublicationAudience(searchParams) ] } }, @@ -313,10 +447,6 @@ type YearAggregation = { }; }; -export function getYearAdditions(aggregations: YearAggregation) { - return aggregations.all_data_except_publicationYear?.filtered_except_publicationYear.all_publicationYears.buckets ?? []; -} - function additionsFromTypeCode(searchParams: SearchParams) { return { "all_data_except_publicationTypeCode": { @@ -329,7 +459,10 @@ function additionsFromTypeCode(searchParams: SearchParams) { matchingTerms(searchParams), ...termsForYear(searchParams), ...termsForStatusCode(searchParams), - ...termsForOrganization(searchParams) + ...termsForOrganization(searchParams), + ...termsForLanguageCode(searchParams), + ...termsForPublicationFormat(searchParams), + ...termsForPublicationAudience(searchParams) ] } }, @@ -358,7 +491,10 @@ function additionsFromStatusCode(searchParams: SearchParams) { matchingTerms(searchParams), ...termsForYear(searchParams), ...termsForTypeCode(searchParams), - ...termsForOrganization(searchParams) + ...termsForOrganization(searchParams), + ...termsForLanguageCode(searchParams), + ...termsForPublicationFormat(searchParams), + ...termsForPublicationAudience(searchParams) ] } }, @@ -375,6 +511,103 @@ function additionsFromStatusCode(searchParams: SearchParams) { }; } +function additionsFromLanguageCode(searchParams: SearchParams) { + return { + "all_data_except_languageCode": { + "global": {}, + "aggregations": { + "filtered_except_languageCode": { + "filter": { + "bool": { + "must": [ + matchingTerms(searchParams), + ...termsForYear(searchParams), + ...termsForTypeCode(searchParams), + ...termsForStatusCode(searchParams), + ...termsForOrganization(searchParams), + ...termsForPublicationFormat(searchParams), + ...termsForPublicationAudience(searchParams) + ] + } + }, + "aggregations": { + "all_languageCodes": { + "terms": { + "field": "languages.languageCode.keyword" + } + } + } + } + } + } + }; +} + +function additionsFromPublicationFormat(searchParams: SearchParams) { + return { + "all_data_except_publicationFormat": { + "global": {}, + "aggregations": { + "filtered_except_publicationFormat": { + "filter": { + "bool": { + "must": [ + matchingTerms(searchParams), + ...termsForYear(searchParams), + ...termsForTypeCode(searchParams), + ...termsForStatusCode(searchParams), + ...termsForOrganization(searchParams), + ...termsForLanguageCode(searchParams), + ...termsForPublicationAudience(searchParams) + ] + } + }, + "aggregations": { + "all_publicationFormats": { + "terms": { + "field": "publicationFormat.id.keyword", + "size": 1000, + } + } + } + } + } + } + }; +} + +function additionsFromPublicationAudience(searchParams: SearchParams) { + return { + "all_data_except_publicationAudience": { + "global": {}, + "aggregations": { + "filtered_except_publicationAudience": { + "filter": { + "bool": { + "must": [ + matchingTerms(searchParams), + ...termsForYear(searchParams), + ...termsForTypeCode(searchParams), + ...termsForStatusCode(searchParams), + ...termsForOrganization(searchParams), + ...termsForLanguageCode(searchParams), + ...termsForPublicationFormat(searchParams) + ] + } + }, + "aggregations": { + "all_publicationAudiences": { + "terms": { + "field": "publicationAudience.id.keyword" + } + } + } + } + } + } + }; +} + type OrganizationAggregation = { all_data_except_organizationId: { filtered_except_organizationId: { @@ -390,10 +623,6 @@ type OrganizationAggregation = { }; }; -export function getOrganizationAdditions(aggregations: OrganizationAggregation) { - return aggregations.all_data_except_organizationId?.filtered_except_organizationId.organization_nested.all_organizationIds.buckets ?? []; -} - function suffixer(locale) { const capitalized = locale.charAt(0).toUpperCase() + locale.slice(1).toLowerCase(); @@ -436,7 +665,8 @@ function additionsFromOrganization(searchParams: SearchParams) { matchingTerms(searchParams), ...termsForYear(searchParams), ...termsForTypeCode(searchParams), - ...termsForStatusCode(searchParams) + ...termsForStatusCode(searchParams), + ...termsForLanguageCode(searchParams) ] } }, @@ -450,8 +680,6 @@ function additionsFromOrganization(searchParams: SearchParams) { "terms": { "field": "author.organization.organizationId.keyword", "size": 250, - // author.organization.OrganizationNameFi.keyword - // "field": i18n`author.organization.${"OrganizationName"}.keyword` } } } @@ -483,12 +711,32 @@ type OrgsAggsResponse = { }; }; +function toIdNameLookup(data: OrgsAggsResponse): Map { + const pairs = getOrganizationNameBuckets(data).map((bucket) => [bucket.key.id, bucket.key.name]); + + return Object.fromEntries(pairs); +} + function getOrganizationNameBuckets(response: OrgsAggsResponse) { return response.aggregations.filtered_authors.single_sector.organizations.composite_orgs.buckets; } -function toIdNameLookup(data: OrgsAggsResponse): Map { - const pairs = getOrganizationNameBuckets(data).map((bucket) => [bucket.key.id, bucket.key.name]); +export function getOrganizationAdditions(aggregations: OrganizationAggregation) { + return aggregations.all_data_except_organizationId?.filtered_except_organizationId.organization_nested.all_organizationIds.buckets ?? []; +} - return Object.fromEntries(pairs); +export function getYearAdditions(aggregations: YearAggregation) { + return aggregations.all_data_except_publicationYear?.filtered_except_publicationYear.all_publicationYears.buckets ?? []; +} + +export function getLanguageCodeAdditions(aggregations: any) { + return aggregations.all_data_except_languageCode?.filtered_except_languageCode.all_languageCodes.buckets ?? []; +} + +export function getPublicationFormatAdditions(aggregations: any) { + return aggregations.all_data_except_publicationFormat?.filtered_except_publicationFormat.all_publicationFormats.buckets ?? []; +} + +export function getPublicationAudienceAdditions(aggregations: any) { + return aggregations.all_data_except_publicationAudience?.filtered_except_publicationAudience.all_publicationAudiences.buckets ?? []; } From 12679aec2257f855f76764d8658b283abac75db4 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Wed, 22 Nov 2023 16:25:48 +0200 Subject: [PATCH 10/47] New filters: publicationType, international, articleType, jufo --- .../publications2.component.html | 88 ++++ .../publications2/publications2.component.ts | 90 +++- .../portal/services/publication2.service.ts | 453 +++++++++++++++++- .../pagination/pagination.component.ts | 2 - 4 files changed, 619 insertions(+), 14 deletions(-) diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index 81bfffbea..3dd9fbb4b 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -127,6 +127,94 @@

Julkaisut - {{total$ | async}}

+ +
+ +
+

Peer Reviewed

+ +
+ {{peerReviewedAdditions$ | async | json}} +
+
+ + +
+ +
+
+ + + + + + + + +
+
+ + +
+ +
+
+

International Publication

+ + + + + + + + +
+
+ + +
+ +
+
+

Article Type

+ + + + + + + + +
+
+ + +
+ +
+

Jufo

+ + + + + + + +
diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index a90e4fc88..0a5aac7c7 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -5,13 +5,19 @@ import { ActivatedRoute, Router } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { AsyncPipe, JsonPipe, NgForOf, NgIf } from '@angular/common'; import { + getArticleTypeCodeAdditions, + getInternationalPublicationAdditions, getJufoClassCodeAdditions, getLanguageCodeAdditions, - getOrganizationAdditions, getPublicationAudienceAdditions, getPublicationFormatAdditions, + getOrganizationAdditions, + getParentPublicationTypeAdditions, + getPeerReviewedAdditions, + getPublicationAudienceAdditions, + getPublicationFormatAdditions, getYearAdditions, HighlightedPublication, Publication2Service } from '@portal/services/publication2.service'; -import { map, take } from 'rxjs/operators'; +import { map, take, tap } from 'rxjs/operators'; import { SharedModule } from '@shared/shared.module'; import { SearchBar2Component } from '@portal/search-bar2/search-bar2.component'; import { NgArrayPipesModule } from 'ngx-pipes'; @@ -122,6 +128,86 @@ export class Publications2Component implements OnDestroy { }))) ); + peerReviewedAdditions$ = this.aggregations$.pipe( + map(aggs => getPeerReviewedAdditions(aggs).map((bucket: any) => ({ id: bucket.key, count: bucket.doc_count })) ?? []), + map(aggs => aggs.sort((a, b) => b.count - a.count)) + ); + + // getParentPublicationTypeAdditions + // getInternationalPublicationAdditions + // getArticleTypeCodeAdditions + // getJufoClassCodeAdditions + + parentPublicationTypeAdditions$ = this.aggregations$.pipe( + map(aggs => getParentPublicationTypeAdditions(aggs).map((bucket: any) => ({ id: bucket.key, count: bucket.doc_count })) ?? []), + map(aggs => aggs.sort((a, b) => b.count - a.count)) + ); + + internationalPublicationAdditions$ = this.aggregations$.pipe( + map(aggs => getInternationalPublicationAdditions(aggs).map((bucket: any) => ({ id: bucket.key.toString(), count: bucket.doc_count })) ?? []), + map(aggs => aggs.sort((a, b) => b.count - a.count)) + ); + + internationalPublicationNames$ = this.publications2Service.getInternationalPublicationNames(); + + internationalPublicationFilters$ = combineLatest([this.internationalPublicationAdditions$, this.internationalPublicationNames$, this.searchParams$.pipe(map(params => params.international ?? []))]).pipe( + map(([internationalPublicationAdditions, internationalPublicationNames, enabledFilters]) => internationalPublicationAdditions.map(internationalPublicationAddition => ({ + id: internationalPublicationAddition.id, + count: internationalPublicationAddition.count, + name: internationalPublicationNames[internationalPublicationAddition.id], + enabled: enabledFilters.includes(internationalPublicationAddition.id) + }))) + ); + + articleTypeCodeAdditions$ = this.aggregations$.pipe( + map(aggs => getArticleTypeCodeAdditions(aggs).map((bucket: any) => ({ id: bucket.key.toString(), count: bucket.doc_count })) ?? []), + map(aggs => aggs.sort((a, b) => b.count - a.count)) + ); + + articleTypeCodeNames$ = this.publications2Service.getArticleTypeCodeNames(); + + articleTypeCodeFilters$ = combineLatest([this.articleTypeCodeAdditions$, this.articleTypeCodeNames$, this.searchParams$.pipe(map(params => params.articleType ?? []))]).pipe( + map(([articleTypeCodeAdditions, articleTypeCodeNames, enabledFilters]) => articleTypeCodeAdditions.map(articleTypeCodeAddition => ({ + id: articleTypeCodeAddition.id, + count: articleTypeCodeAddition.count, + name: articleTypeCodeNames[articleTypeCodeAddition.id], + enabled: enabledFilters.includes(articleTypeCodeAddition.id) + }))) + ); + + jufoClassCodeAdditions$ = this.aggregations$.pipe( + map(aggs => getJufoClassCodeAdditions(aggs).map((bucket: any) => ({ id: bucket.key.toString(), count: bucket.doc_count })) ?? []), + map(aggs => aggs.sort((a, b) => b.count - a.count)) + ); + + jufoClassCodeFilters$ = combineLatest([this.jufoClassCodeAdditions$, this.searchParams$.pipe(map(params => params.jufo ?? []))]).pipe( + map(([jufoClassCodeAdditions, enabledFilters]) => jufoClassCodeAdditions.map(jufoClassCodeAddition => ({ + id: jufoClassCodeAddition.id, + count: jufoClassCodeAddition.count, + name: jufoClassCodeAddition.id, + enabled: enabledFilters.includes(jufoClassCodeAddition.id) + }))) + ); + + // getParentPublicationTypeNames + // getInternationalPublicationNames + // getArticleTypeCodeNames + + parentPublicationTypeNames$ = this.publications2Service.getParentPublicationTypeNames(); + + // TODO these give just ids and not names + // internationalPublicationNames$ = this.publications2Service.getInternationalPublicationNames(); + // articleTypeCodeNames$ = this.publications2Service.getArticleTypeCodeNames(); + + parentPublicationTypeFilters$ = combineLatest([this.parentPublicationTypeAdditions$, this.parentPublicationTypeNames$, this.searchParams$.pipe(map(params => params.parentPublicationType ?? []))]).pipe( + map(([parentPublicationTypeAdditions, parentPublicationTypeNames, enabledFilters]) => parentPublicationTypeAdditions.map(parentPublicationTypeAddition => ({ + id: parentPublicationTypeAddition.id, + count: parentPublicationTypeAddition.count, + name: parentPublicationTypeNames[parentPublicationTypeAddition.id], + enabled: enabledFilters.includes(parentPublicationTypeAddition.id) + }))) + ); + /* TODO localization solution */ public sectorName = { 1: "Yliopisto", diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 3b8c03855..3c1e866d4 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -1,6 +1,6 @@ import { inject, Injectable, LOCALE_ID, OnInit, SecurityContext } from '@angular/core'; // import { object, Output, parse, string } from 'valibot'; -import { BehaviorSubject, forkJoin, Observable, shareReplay } from 'rxjs'; +import { BehaviorSubject, forkJoin, Observable, of, shareReplay } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { map, switchMap, take, tap } from 'rxjs/operators'; import { ActivatedRoute } from '@angular/router'; @@ -42,6 +42,13 @@ type SearchParams = { language?: string[], format?: string[], audience?: string[], + peerReviewed?: string[], + + // new terms + parentPublicationType?: string[], + international?: string[], + articleType?: string[], + jufo?: string[], // placeholders publicationTypeCode?: string[], @@ -123,7 +130,13 @@ export class Publication2Service { ...termsForOrganization(searchParams), ...termsForLanguageCode(searchParams), ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams) + ...termsForPublicationAudience(searchParams), + ...termsForPeerReviewed(searchParams), + // Add new terms + ...termsForParentPublicationType(searchParams), + ...termsForInternationalPublication(searchParams), + ...termsForArticleTypeCode(searchParams), + ...termsForJufoClassCode(searchParams) ] } } @@ -146,7 +159,13 @@ export class Publication2Service { ...additionsFromOrganization(searchParams), ...additionsFromLanguageCode(searchParams), ...additionsFromPublicationFormat(searchParams), - ...additionsFromPublicationAudience(searchParams) + ...additionsFromPublicationAudience(searchParams), + ...additionsFromPeerReviewed(searchParams), + // Add new terms + ...additionsFromParentPublicationType(searchParams), + ...additionsFromInternationalPublication(searchParams), + ...additionsFromArticleTypeCode(searchParams), + ...additionsFromJufoClassCode(searchParams) } }); } @@ -315,6 +334,66 @@ export class Publication2Service { shareReplay({ bufferSize: 1, refCount: true }) ); } + +// getParentPublicationTypeNames +// getInternationalPublicationNames +// getArticleTypeCodeNames + + + getParentPublicationTypeNames()/*: Observable>*/ { + const body = { + "size": 0, + "aggs": { + "composite_pairs": { + "composite": { + "size": 1000, + "sources": [ + { "id": { "terms": { "field": "parentPublicationType.id.keyword" } } }, + { "nameFiParentPublicationType": { "terms": { "field": "parentPublicationType.nameFiParentPublicationType.keyword" } } } + ] + } + } + } + } + + type ParentPublicationTypeAggregation = { + aggregations: { + composite_pairs: { + buckets: Array<{ + key: { + id: string; + nameFiParentPublicationType: string; + }; + doc_count: number; + }>; + }; + }; + }; + + const response$ = this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', body); + + return response$.pipe( + map((res) => res.aggregations.composite_pairs.buckets.map((bucket) => [bucket.key.id, bucket.key.nameFiParentPublicationType])), // TODO localized path needed + map(pairs => Object.fromEntries(pairs)), + shareReplay({ bufferSize: 1, refCount: true }) + ); + } + + getInternationalPublicationNames(): Observable> { + return of({ + "0": "Kotimainen julkaisu", // TODO use localize`` + "1": "Kansainvälinen julkaisu" // TODO use localize`` + }); + } + + getArticleTypeCodeNames(): Observable> { + return of({ + "0": "Lehti", // TODO use localize`` + "1": "Kokoomateos", // TODO use localize`` + "2": "Konferenssi", // TODO use localize`` + "3": "Verkkoalusta" // TODO use localize`` + }); + } } function matchingTerms(searchParams: SearchParams) { @@ -401,6 +480,100 @@ function termsForPublicationAudience(searchParams: SearchParams) { return []; } +/* Peer reviewed +"terms": { + "field": "peerReviewed.id.keyword", + "size": 100 +} +*/ +function termsForPeerReviewed(searchParams: SearchParams) { + if (searchParams.peerReviewed) { + return [{ + terms: { + "peerReviewed.id.keyword": searchParams.peerReviewed + } + }]; + } + return []; +} + +/* Parent Publication Type +{ + "size": 0, + "aggs": { + "parentPublicationType": { + "terms": { + "field": "parentPublicationType.id.keyword", + "size": 100 + } + } + } +} +*/ +function termsForParentPublicationType(searchParams: SearchParams) { + if (searchParams.parentPublicationType) { + return [{ + terms: { + "parentPublicationType.id.keyword": searchParams.parentPublicationType + } + }]; + } + return []; +} + +/* International Publication +"terms": { + "field": "internationalPublication", + "size": 100 +} +*/ +function termsForInternationalPublication(searchParams: SearchParams) { + if (searchParams.international) { + return [{ + terms: { + "internationalPublication": searchParams.international + } + }]; + } + return []; +} + +/* +"terms": { + "field": "articleTypeCode", + "size": 100 +} +*/ +function termsForArticleTypeCode(searchParams: SearchParams) { + if (searchParams.articleType) { + return [{ + terms: { + "articleTypeCode": searchParams.articleType + } + }]; + } + return []; +} + +/* +"jufoClassCode": { + "terms": { + "field": "jufoClassCode.keyword", + "size": 100 + } +} +*/ +function termsForJufoClassCode(searchParams: SearchParams) { + if (searchParams.jufo) { + return [{ + terms: { + "jufoClassCode.keyword": searchParams.jufo + } + }]; + } + return []; +} + function additionsFromYear(searchParams: SearchParams) { return { "all_data_except_publicationYear": { @@ -416,7 +589,13 @@ function additionsFromYear(searchParams: SearchParams) { ...termsForStatusCode(searchParams), ...termsForLanguageCode(searchParams), ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams) + ...termsForPublicationAudience(searchParams), + ...termsForPeerReviewed(searchParams), + // Add new terms + ...termsForParentPublicationType(searchParams), + ...termsForInternationalPublication(searchParams), + ...termsForArticleTypeCode(searchParams), + ...termsForJufoClassCode(searchParams) ] } }, @@ -462,7 +641,13 @@ function additionsFromTypeCode(searchParams: SearchParams) { ...termsForOrganization(searchParams), ...termsForLanguageCode(searchParams), ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams) + ...termsForPublicationAudience(searchParams), + ...termsForPeerReviewed(searchParams), + // Add new terms + ...termsForParentPublicationType(searchParams), + ...termsForInternationalPublication(searchParams), + ...termsForArticleTypeCode(searchParams), + ...termsForJufoClassCode(searchParams) ] } }, @@ -494,7 +679,13 @@ function additionsFromStatusCode(searchParams: SearchParams) { ...termsForOrganization(searchParams), ...termsForLanguageCode(searchParams), ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams) + ...termsForPublicationAudience(searchParams), + ...termsForPeerReviewed(searchParams), + // Add new terms + ...termsForParentPublicationType(searchParams), + ...termsForInternationalPublication(searchParams), + ...termsForArticleTypeCode(searchParams), + ...termsForJufoClassCode(searchParams) ] } }, @@ -526,7 +717,13 @@ function additionsFromLanguageCode(searchParams: SearchParams) { ...termsForStatusCode(searchParams), ...termsForOrganization(searchParams), ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams) + ...termsForPublicationAudience(searchParams), + ...termsForPeerReviewed(searchParams), + // Add new terms + ...termsForParentPublicationType(searchParams), + ...termsForInternationalPublication(searchParams), + ...termsForArticleTypeCode(searchParams), + ...termsForJufoClassCode(searchParams) ] } }, @@ -558,7 +755,13 @@ function additionsFromPublicationFormat(searchParams: SearchParams) { ...termsForStatusCode(searchParams), ...termsForOrganization(searchParams), ...termsForLanguageCode(searchParams), - ...termsForPublicationAudience(searchParams) + ...termsForPublicationAudience(searchParams), + ...termsForPeerReviewed(searchParams), + // Add new terms + ...termsForParentPublicationType(searchParams), + ...termsForInternationalPublication(searchParams), + ...termsForArticleTypeCode(searchParams), + ...termsForJufoClassCode(searchParams) ] } }, @@ -591,7 +794,13 @@ function additionsFromPublicationAudience(searchParams: SearchParams) { ...termsForStatusCode(searchParams), ...termsForOrganization(searchParams), ...termsForLanguageCode(searchParams), - ...termsForPublicationFormat(searchParams) + ...termsForPublicationFormat(searchParams), + ...termsForPeerReviewed(searchParams), + // Add new terms + ...termsForParentPublicationType(searchParams), + ...termsForInternationalPublication(searchParams), + ...termsForArticleTypeCode(searchParams), + ...termsForJufoClassCode(searchParams) ] } }, @@ -608,6 +817,201 @@ function additionsFromPublicationAudience(searchParams: SearchParams) { }; } +function additionsFromPeerReviewed(searchParams: SearchParams) { + return { + "all_data_except_peerReviewed": { + "global": {}, + "aggregations": { + "filtered_except_peerReviewed": { + "filter": { + "bool": { + "must": [ + matchingTerms(searchParams), + ...termsForYear(searchParams), + ...termsForTypeCode(searchParams), + ...termsForStatusCode(searchParams), + ...termsForOrganization(searchParams), + ...termsForLanguageCode(searchParams), + ...termsForPublicationFormat(searchParams), + ...termsForPublicationAudience(searchParams), + // Add new terms + ...termsForParentPublicationType(searchParams), + ...termsForInternationalPublication(searchParams), + ...termsForArticleTypeCode(searchParams), + ...termsForJufoClassCode(searchParams) + ] + } + }, + "aggregations": { + "all_peerReviewed": { + "terms": { + "field": "peerReviewed.id.keyword", + "size": 100 + } + } + } + } + } + } + }; +} + +function additionsFromParentPublicationType(searchParams: SearchParams) { + return { + "all_data_except_parentPublicationType": { + "global": {}, + "aggregations": { + "filtered_except_parentPublicationType": { + "filter": { + "bool": { + "must": [ + matchingTerms(searchParams), + ...termsForYear(searchParams), + ...termsForTypeCode(searchParams), + ...termsForStatusCode(searchParams), + ...termsForOrganization(searchParams), + ...termsForLanguageCode(searchParams), + ...termsForPublicationFormat(searchParams), + ...termsForPublicationAudience(searchParams), + ...termsForPeerReviewed(searchParams), + // Add new terms + ...termsForInternationalPublication(searchParams), + ...termsForArticleTypeCode(searchParams), + ...termsForJufoClassCode(searchParams) + ] + } + }, + "aggregations": { + "all_parentPublicationTypes": { + "terms": { + "field": "parentPublicationType.id.keyword", + "size": 100 + } + } + } + } + } + } + } +} + +function additionsFromInternationalPublication(searchParams: SearchParams) { + return { + "all_data_except_internationalPublication": { + "global": {}, + "aggregations": { + "filtered_except_internationalPublication": { + "filter": { + "bool": { + "must": [ + matchingTerms(searchParams), + ...termsForYear(searchParams), + ...termsForTypeCode(searchParams), + ...termsForStatusCode(searchParams), + ...termsForOrganization(searchParams), + ...termsForLanguageCode(searchParams), + ...termsForPublicationFormat(searchParams), + ...termsForPublicationAudience(searchParams), + ...termsForPeerReviewed(searchParams), + // Add new terms + ...termsForParentPublicationType(searchParams), + ...termsForArticleTypeCode(searchParams), + ...termsForJufoClassCode(searchParams) + ] + } + }, + "aggregations": { + "all_internationalPublications": { + "terms": { + "field": "internationalPublication", + "size": 100 + } + } + } + } + } + } + } +} + +function additionsFromArticleTypeCode(searchParams: SearchParams) { +return { + "all_data_except_articleTypeCode": { + "global": {}, + "aggregations": { + "filtered_except_articleTypeCode": { + "filter": { + "bool": { + "must": [ + matchingTerms(searchParams), + ...termsForYear(searchParams), + ...termsForTypeCode(searchParams), + ...termsForStatusCode(searchParams), + ...termsForOrganization(searchParams), + ...termsForLanguageCode(searchParams), + ...termsForPublicationFormat(searchParams), + ...termsForPublicationAudience(searchParams), + ...termsForPeerReviewed(searchParams), + // Add new terms + ...termsForParentPublicationType(searchParams), + ...termsForInternationalPublication(searchParams), + ...termsForJufoClassCode(searchParams) + ] + } + }, + "aggregations": { + "all_articleTypeCodes": { + "terms": { + "field": "articleTypeCode", + "size": 100 + } + } + } + } + } + } + } +} + +function additionsFromJufoClassCode(searchParams: SearchParams) { + return { + "all_data_except_jufoClassCode": { + "global": {}, + "aggregations": { + "filtered_except_jufoClassCode": { + "filter": { + "bool": { + "must": [ + matchingTerms(searchParams), + ...termsForYear(searchParams), + ...termsForTypeCode(searchParams), + ...termsForStatusCode(searchParams), + ...termsForOrganization(searchParams), + ...termsForLanguageCode(searchParams), + ...termsForPublicationFormat(searchParams), + ...termsForPublicationAudience(searchParams), + ...termsForPeerReviewed(searchParams), + // Add new terms + ...termsForParentPublicationType(searchParams), + ...termsForInternationalPublication(searchParams), + ...termsForArticleTypeCode(searchParams) + ] + } + }, + "aggregations": { + "all_jufoClassCodes": { + "terms": { + "field": "jufoClassCode.keyword", + "size": 100 + } + } + } + } + } + } + } +} + type OrganizationAggregation = { all_data_except_organizationId: { filtered_except_organizationId: { @@ -666,7 +1070,16 @@ function additionsFromOrganization(searchParams: SearchParams) { ...termsForYear(searchParams), ...termsForTypeCode(searchParams), ...termsForStatusCode(searchParams), - ...termsForLanguageCode(searchParams) + ...termsForLanguageCode(searchParams), + // ? + ...termsForPublicationFormat(searchParams), + ...termsForPublicationAudience(searchParams), + ...termsForPeerReviewed(searchParams), + // Add new terms + ...termsForParentPublicationType(searchParams), + ...termsForInternationalPublication(searchParams), + ...termsForArticleTypeCode(searchParams), + ...termsForJufoClassCode(searchParams) ] } }, @@ -740,3 +1153,23 @@ export function getPublicationFormatAdditions(aggregations: any) { export function getPublicationAudienceAdditions(aggregations: any) { return aggregations.all_data_except_publicationAudience?.filtered_except_publicationAudience.all_publicationAudiences.buckets ?? []; } + +export function getPeerReviewedAdditions(aggregations: any) { + return aggregations.all_data_except_peerReviewed?.filtered_except_peerReviewed.all_peerReviewed.buckets ?? []; +} + +export function getParentPublicationTypeAdditions(aggregations: any) { + return aggregations.all_data_except_parentPublicationType?.filtered_except_parentPublicationType.all_parentPublicationTypes.buckets ?? []; +} + +export function getInternationalPublicationAdditions(aggregations: any) { + return aggregations.all_data_except_internationalPublication?.filtered_except_internationalPublication.all_internationalPublications.buckets ?? []; +} + +export function getArticleTypeCodeAdditions(aggregations: any) { + return aggregations.all_data_except_articleTypeCode?.filtered_except_articleTypeCode.all_articleTypeCodes.buckets ?? []; +} + +export function getJufoClassCodeAdditions(aggregations: any) { + return aggregations.all_data_except_jufoClassCode?.filtered_except_jufoClassCode.all_jufoClassCodes.buckets ?? []; +} diff --git a/src/app/shared/components/pagination/pagination.component.ts b/src/app/shared/components/pagination/pagination.component.ts index 182ad63bb..978d9d3a4 100644 --- a/src/app/shared/components/pagination/pagination.component.ts +++ b/src/app/shared/components/pagination/pagination.component.ts @@ -88,8 +88,6 @@ function countTotalPages(totalDocuments, pageSize): number { } function generatePages(currentPage: number, range: 5 | 9, results: number, pageSize: number): number[] { - console.log(currentPage, range, results, pageSize); - let output: number[] = []; const maxPage = countTotalPages(results, pageSize); const i = currentPage; From 4fa8b8f0906cd793e07cdf5a251df01272fc2176 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Tue, 28 Nov 2023 16:12:46 +0200 Subject: [PATCH 11/47] Create collapsible parent for filters, adjust colors --- .../collapsible/collapsible.component.html | 19 ++ .../collapsible/collapsible.component.scss | 51 ++++++ .../collapsible/collapsible.component.spec.ts | 23 +++ .../collapsible/collapsible.component.ts | 26 +++ .../publications2.component.html | 162 ++++++++++-------- .../publications2/publications2.component.ts | 27 ++- .../portal/services/publication2.service.ts | 78 ++++++--- 7 files changed, 284 insertions(+), 102 deletions(-) create mode 100644 src/app/portal/components/collapsible/collapsible.component.html create mode 100644 src/app/portal/components/collapsible/collapsible.component.scss create mode 100644 src/app/portal/components/collapsible/collapsible.component.spec.ts create mode 100644 src/app/portal/components/collapsible/collapsible.component.ts diff --git a/src/app/portal/components/collapsible/collapsible.component.html b/src/app/portal/components/collapsible/collapsible.component.html new file mode 100644 index 000000000..99effae04 --- /dev/null +++ b/src/app/portal/components/collapsible/collapsible.component.html @@ -0,0 +1,19 @@ +
+
+ {{label}} + + + + +
+ + + +
+ + + + + +
+
diff --git a/src/app/portal/components/collapsible/collapsible.component.scss b/src/app/portal/components/collapsible/collapsible.component.scss new file mode 100644 index 000000000..b55197004 --- /dev/null +++ b/src/app/portal/components/collapsible/collapsible.component.scss @@ -0,0 +1,51 @@ +.collapsible-container { + background-color: rgb(247, 247, 251); +} + +.collapsible-label { + font-size: 1.1rem; + font-weight: bolder; + + padding: 11px 16px; + border-radius: 4px; + + user-select: none; + cursor: pointer; + + display: flex; + justify-content: space-between; +} + +.collapsible-icon { + transition: transform 0.1s ease-out; +} + +/* icon can be flipped */ +.icon-collapsed { + transition: transform 0.25s ease-out; + transform: rotate(0deg); +} + +.icon-expanded { + transition: transform 0.25s ease-out; + transform: rotate(180deg); +} + +/*.collapsible-container .content { + +} + +.collapsible-container .content.collapsed { + // max-height: 1000px; !* Adjust as needed *! +}*/ + + +.collapsible-content { + overflow: hidden; + transition: max-height 0.2s ease-out; + max-height: 1000px; +} + +.collapsed { + max-height: 0; +} diff --git a/src/app/portal/components/collapsible/collapsible.component.spec.ts b/src/app/portal/components/collapsible/collapsible.component.spec.ts new file mode 100644 index 000000000..08e8431f1 --- /dev/null +++ b/src/app/portal/components/collapsible/collapsible.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CollapsibleComponent } from './collapsible.component'; + +describe('CollapsibleComponent', () => { + let component: CollapsibleComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CollapsibleComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CollapsibleComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/portal/components/collapsible/collapsible.component.ts b/src/app/portal/components/collapsible/collapsible.component.ts new file mode 100644 index 000000000..b49866255 --- /dev/null +++ b/src/app/portal/components/collapsible/collapsible.component.ts @@ -0,0 +1,26 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { NgClass, NgIf } from '@angular/common'; +import { MatIconModule } from '@angular/material/icon'; + +@Component({ + selector: 'app-collapsible', + templateUrl: './collapsible.component.html', + styleUrls: ['./collapsible.component.scss'], + imports: [ + NgIf, + MatIconModule, + NgClass + ], + standalone: true +}) +export class CollapsibleComponent { + @Input() label = ''; + + @Input() isOpen = false; + @Output() isOpenChange = new EventEmitter(); + + toggle() { + this.isOpen = !this.isOpen; + this.isOpenChange.emit(this.isOpen); + } +} diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index 3dd9fbb4b..a1787dfe2 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -25,7 +25,7 @@ -
+
{{tab}}
@@ -58,9 +58,11 @@

Julkaisut - {{total$ | async}}

-->
-
+
-
+ + + + + +
+ + +
+ + + + + +
@@ -89,64 +107,78 @@

Julkaisut - {{total$ | async}}

-
- - - - +
+ + + + + + + + + +
-
+
- - - - + + + + + + + +
-
- - - - +
+ + + + + +
-
-

Peer Reviewed

- -
- {{peerReviewedAdditions$ | async | json}} -
+
+ + + + + +
-
-
- - - - +
+ Peer Reviewed (valueChange)='toggleParam("parentPublicationType", parentPublicationTypeFilter.id)'> -
+
-
-
-

International Publication

- - - - +
+ International Publication (valueChange)='toggleParam("international", internationalPublicationFilter.id)'> -
+
-
-
-

Article Type

- - - - +
+ Article Type (valueChange)='toggleParam("articleType", articleTypeCodeFilter.id)'> -
+
-
-

Jufo

- - - - - - - +
+ + + + + +
-
-
+
+
diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 0a5aac7c7..f5962efee 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -24,6 +24,7 @@ import { NgArrayPipesModule } from 'ngx-pipes'; import { OrganizationFilterComponent } from '@portal/components/organization-filter/organization-filter.component'; import { FilterOptionComponent } from '@portal/components/filter-option/filter-option.component'; import { LimitPipe } from '@portal/pipes/limit.pipe'; +import { CollapsibleComponent } from '@portal/components/collapsible/collapsible.component'; @Component({ selector: 'app-publications2', @@ -32,7 +33,7 @@ import { LimitPipe } from '@portal/pipes/limit.pipe'; imports: [CdkTableModule, FormsModule, AsyncPipe, JsonPipe, NgForOf, NgIf, LimitPipe, NgArrayPipesModule, SharedModule, //TODO not good? FormsModule, - SearchBar2Component, OrganizationFilterComponent, FilterOptionComponent + SearchBar2Component, OrganizationFilterComponent, FilterOptionComponent, CollapsibleComponent ], standalone: true }) @@ -88,10 +89,13 @@ export class Publications2Component implements OnDestroy { map(aggs => aggs.sort((a, b) => b.count - a.count)) ); - languageCodeFilters$ = combineLatest([this.languageCodeAdditions$, this.searchParams$.pipe(map(params => params.language ?? []))]).pipe( - map(([languageCodeAdditions, enabledFilters]) => languageCodeAdditions.map(languageCodeAddition => ({ - name: languageCodeAddition.languageCode, + languageCodeNames$ = this.publications2Service.getLanguageCodeNames(); + + languageCodeFilters$ = combineLatest([this.languageCodeAdditions$, this.languageCodeNames$, this.searchParams$.pipe(map(params => params.language ?? []))]).pipe( + map(([languageCodeAdditions, languageCodeNames, enabledFilters]) => languageCodeAdditions.map(languageCodeAddition => ({ + id: languageCodeAddition.languageCode, count: languageCodeAddition.count, + name: languageCodeNames[languageCodeAddition.languageCode], enabled: enabledFilters.includes(languageCodeAddition.languageCode) }))) ); @@ -133,6 +137,17 @@ export class Publications2Component implements OnDestroy { map(aggs => aggs.sort((a, b) => b.count - a.count)) ); + peerReviewedNames$ = this.publications2Service.getPeerReviewedNames(); + + peerReviewedFilters$ = combineLatest([this.peerReviewedAdditions$, this.peerReviewedNames$, this.searchParams$.pipe(map(params => params.peerReviewed ?? []))]).pipe( + map(([peerReviewedAdditions, peerReviewedNames, enabledFilters]) => peerReviewedAdditions.map(peerReviewedAddition => ({ + id: peerReviewedAddition.id, + count: peerReviewedAddition.count, + name: peerReviewedNames[peerReviewedAddition.id], + enabled: enabledFilters.includes(peerReviewedAddition.id) + }))) + ); + // getParentPublicationTypeAdditions // getInternationalPublicationAdditions // getArticleTypeCodeAdditions @@ -217,6 +232,10 @@ export class Publications2Component implements OnDestroy { 6: "Muu" } + /*public collapseStates = { + "language": false, + }*/ + searchParamsSubscription = this.searchParams$.subscribe(searchParams => { this.publications2Service.updateSearchTerms(searchParams); diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 3c1e866d4..22ed79103 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -132,7 +132,6 @@ export class Publication2Service { ...termsForPublicationFormat(searchParams), ...termsForPublicationAudience(searchParams), ...termsForPeerReviewed(searchParams), - // Add new terms ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), @@ -161,7 +160,6 @@ export class Publication2Service { ...additionsFromPublicationFormat(searchParams), ...additionsFromPublicationAudience(searchParams), ...additionsFromPeerReviewed(searchParams), - // Add new terms ...additionsFromParentPublicationType(searchParams), ...additionsFromInternationalPublication(searchParams), ...additionsFromArticleTypeCode(searchParams), @@ -245,19 +243,53 @@ export class Publication2Service { .pipe(shareReplay({ bufferSize: 1, refCount: true })); } - getPublicationFormatNames()/*: Observable>*/ { - /*const body = { - "size": 0, - "aggs": { - "nameFiFormat": { - "terms": { - "field": "publicationFormat.id.keyword", - "size": 1000 +/* +"terms": { + "field": "languages.languageFi.keyword", + "size": 1000 +} +*/ + + getLanguageCodeNames(): Observable> { + const body = { + 'size': 0, + 'aggs': { + 'composite_pairs': { + 'composite': { + 'size': 1000, + 'sources': [ + { 'id': { 'terms': { 'field': 'languages.languageCode.keyword' } } }, + { 'nameFiLanguage': { 'terms': { 'field': 'languages.languageFi.keyword' } } } + ] } } } - }*/ + }; + type LanguageAggregation = { + aggregations: { + composite_pairs: { + buckets: Array<{ + key: { + id: string; + nameFiLanguage: string; + }; + doc_count: number; + }>; + }; + }; + }; + + const response$ = this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', body); + + return response$.pipe( + map((res) => res.aggregations.composite_pairs.buckets.map((bucket) => [bucket.key.id, bucket.key.nameFiLanguage])), // TODO localized path needed + map(pairs => Object.fromEntries(pairs)), + shareReplay({ bufferSize: 1, refCount: true }) + ); + } + + getPublicationFormatNames(): Observable> { const body = { 'size': 0, 'aggs': { @@ -296,7 +328,7 @@ export class Publication2Service { ); } - getPublicationAudienceNames()/*: Observable>*/ { + getPublicationAudienceNames(): Observable> { const body = { 'size': 0, 'aggs': { @@ -340,7 +372,7 @@ export class Publication2Service { // getArticleTypeCodeNames - getParentPublicationTypeNames()/*: Observable>*/ { + getParentPublicationTypeNames(): Observable> { const body = { "size": 0, "aggs": { @@ -379,6 +411,13 @@ export class Publication2Service { ); } + getPeerReviewedNames(): Observable> { + return of({ + "0": "Ei-vertaisarvioitu", // TODO use localize`` + "1": "Vertaisarvioitu" // TODO use localize`` + }); + } + getInternationalPublicationNames(): Observable> { return of({ "0": "Kotimainen julkaisu", // TODO use localize`` @@ -591,7 +630,6 @@ function additionsFromYear(searchParams: SearchParams) { ...termsForPublicationFormat(searchParams), ...termsForPublicationAudience(searchParams), ...termsForPeerReviewed(searchParams), - // Add new terms ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), @@ -643,7 +681,6 @@ function additionsFromTypeCode(searchParams: SearchParams) { ...termsForPublicationFormat(searchParams), ...termsForPublicationAudience(searchParams), ...termsForPeerReviewed(searchParams), - // Add new terms ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), @@ -681,7 +718,6 @@ function additionsFromStatusCode(searchParams: SearchParams) { ...termsForPublicationFormat(searchParams), ...termsForPublicationAudience(searchParams), ...termsForPeerReviewed(searchParams), - // Add new terms ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), @@ -719,7 +755,6 @@ function additionsFromLanguageCode(searchParams: SearchParams) { ...termsForPublicationFormat(searchParams), ...termsForPublicationAudience(searchParams), ...termsForPeerReviewed(searchParams), - // Add new terms ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), @@ -757,7 +792,6 @@ function additionsFromPublicationFormat(searchParams: SearchParams) { ...termsForLanguageCode(searchParams), ...termsForPublicationAudience(searchParams), ...termsForPeerReviewed(searchParams), - // Add new terms ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), @@ -796,7 +830,6 @@ function additionsFromPublicationAudience(searchParams: SearchParams) { ...termsForLanguageCode(searchParams), ...termsForPublicationFormat(searchParams), ...termsForPeerReviewed(searchParams), - // Add new terms ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), @@ -834,7 +867,6 @@ function additionsFromPeerReviewed(searchParams: SearchParams) { ...termsForLanguageCode(searchParams), ...termsForPublicationFormat(searchParams), ...termsForPublicationAudience(searchParams), - // Add new terms ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), @@ -874,7 +906,6 @@ function additionsFromParentPublicationType(searchParams: SearchParams) { ...termsForPublicationFormat(searchParams), ...termsForPublicationAudience(searchParams), ...termsForPeerReviewed(searchParams), - // Add new terms ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), ...termsForJufoClassCode(searchParams) @@ -913,7 +944,6 @@ function additionsFromInternationalPublication(searchParams: SearchParams) { ...termsForPublicationFormat(searchParams), ...termsForPublicationAudience(searchParams), ...termsForPeerReviewed(searchParams), - // Add new terms ...termsForParentPublicationType(searchParams), ...termsForArticleTypeCode(searchParams), ...termsForJufoClassCode(searchParams) @@ -952,7 +982,6 @@ return { ...termsForPublicationFormat(searchParams), ...termsForPublicationAudience(searchParams), ...termsForPeerReviewed(searchParams), - // Add new terms ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForJufoClassCode(searchParams) @@ -991,7 +1020,6 @@ function additionsFromJufoClassCode(searchParams: SearchParams) { ...termsForPublicationFormat(searchParams), ...termsForPublicationAudience(searchParams), ...termsForPeerReviewed(searchParams), - // Add new terms ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams) @@ -1071,11 +1099,9 @@ function additionsFromOrganization(searchParams: SearchParams) { ...termsForTypeCode(searchParams), ...termsForStatusCode(searchParams), ...termsForLanguageCode(searchParams), - // ? ...termsForPublicationFormat(searchParams), ...termsForPublicationAudience(searchParams), ...termsForPeerReviewed(searchParams), - // Add new terms ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), From 48fd34130b233120ae0216e7de7b99585f005b74 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Thu, 30 Nov 2023 14:16:17 +0200 Subject: [PATCH 12/47] Add show more and less buttons. Fix language filter to have all languages selectable --- .../collapsible/collapsible.component.scss | 1 - .../filter-limit-button.component.html | 16 +++ .../filter-limit-button.component.scss | 7 ++ .../filter-limit-button.component.spec.ts | 23 ++++ .../filter-limit-button.component.ts | 23 ++++ .../publications2.component.html | 111 ++++++++---------- .../publications2/publications2.component.ts | 24 +++- .../portal/services/publication2.service.ts | 3 +- 8 files changed, 139 insertions(+), 69 deletions(-) create mode 100644 src/app/portal/components/filter-limit-button/filter-limit-button.component.html create mode 100644 src/app/portal/components/filter-limit-button/filter-limit-button.component.scss create mode 100644 src/app/portal/components/filter-limit-button/filter-limit-button.component.spec.ts create mode 100644 src/app/portal/components/filter-limit-button/filter-limit-button.component.ts diff --git a/src/app/portal/components/collapsible/collapsible.component.scss b/src/app/portal/components/collapsible/collapsible.component.scss index b55197004..8c31e524d 100644 --- a/src/app/portal/components/collapsible/collapsible.component.scss +++ b/src/app/portal/components/collapsible/collapsible.component.scss @@ -43,7 +43,6 @@ .collapsible-content { overflow: hidden; transition: max-height 0.2s ease-out; - max-height: 1000px; } .collapsed { diff --git a/src/app/portal/components/filter-limit-button/filter-limit-button.component.html b/src/app/portal/components/filter-limit-button/filter-limit-button.component.html new file mode 100644 index 000000000..33c7cbcf2 --- /dev/null +++ b/src/app/portal/components/filter-limit-button/filter-limit-button.component.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/src/app/portal/components/filter-limit-button/filter-limit-button.component.scss b/src/app/portal/components/filter-limit-button/filter-limit-button.component.scss new file mode 100644 index 000000000..b13f833f6 --- /dev/null +++ b/src/app/portal/components/filter-limit-button/filter-limit-button.component.scss @@ -0,0 +1,7 @@ +.limit-button { + height: 3rem; +} + +.limit-button:enabled { + color: #4546b9; +} diff --git a/src/app/portal/components/filter-limit-button/filter-limit-button.component.spec.ts b/src/app/portal/components/filter-limit-button/filter-limit-button.component.spec.ts new file mode 100644 index 000000000..be14282fd --- /dev/null +++ b/src/app/portal/components/filter-limit-button/filter-limit-button.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FilterLimitButtonComponent } from './filter-limit-button.component'; + +describe('FilterLimitButtonComponent', () => { + let component: FilterLimitButtonComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ FilterLimitButtonComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FilterLimitButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/portal/components/filter-limit-button/filter-limit-button.component.ts b/src/app/portal/components/filter-limit-button/filter-limit-button.component.ts new file mode 100644 index 000000000..3d0e481ee --- /dev/null +++ b/src/app/portal/components/filter-limit-button/filter-limit-button.component.ts @@ -0,0 +1,23 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatButtonModule } from '@angular/material/button'; + +@Component({ + selector: 'app-filter-limit-button', + standalone: true, + imports: [CommonModule, MatButtonModule], + templateUrl: './filter-limit-button.component.html', + styleUrls: ['./filter-limit-button.component.scss'] +}) +export class FilterLimitButtonComponent { + @Input() value = 0; + @Output() valueChange = new EventEmitter(); + + @Input() step = 0; + @Input() min = 0; + @Input() max = 0; + + clamp(value: number) { + return Math.min(Math.max(value, this.min), this.max); + } +} diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index a1787dfe2..127df0889 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -3,21 +3,10 @@
- - - +
- - - - - -
{{tab}} @@ -25,13 +14,14 @@ -
+
{{tab}}
+

Julkaisut - {{total$ | async}}

@@ -51,51 +41,40 @@

Julkaisut - {{total$ | async}}

- - -
-
+
- - +
+ +

Rajaa hakua

+
-
- - - + + + + + + +
+ + Näytä enemmän + + + + Näytä vähemmän + +
+
@@ -108,21 +87,29 @@

Julkaisut - {{total$ | async}}

- - - - - - + + + + + + +
+ + Näytä enemmän + + + + Näytä vähemmän + +
-
+ +
@@ -130,7 +117,6 @@

Julkaisut - {{total$ | async}}

- Julkaisut - {{total$ | async}} -
@@ -237,7 +222,7 @@

Julkaisut - {{total$ | async}}

-
+
diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index f5962efee..9929b0e59 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -3,7 +3,7 @@ import { CdkTableModule, DataSource } from '@angular/cdk/table'; import { combineLatest, Observable } from 'rxjs'; import { ActivatedRoute, Router } from '@angular/router'; import { FormsModule } from '@angular/forms'; -import { AsyncPipe, JsonPipe, NgForOf, NgIf } from '@angular/common'; +import { AsyncPipe, JsonPipe, NgForOf, NgIf, NgStyle } from '@angular/common'; import { getArticleTypeCodeAdditions, getInternationalPublicationAdditions, getJufoClassCodeAdditions, @@ -25,6 +25,8 @@ import { OrganizationFilterComponent } from '@portal/components/organization-fil import { FilterOptionComponent } from '@portal/components/filter-option/filter-option.component'; import { LimitPipe } from '@portal/pipes/limit.pipe'; import { CollapsibleComponent } from '@portal/components/collapsible/collapsible.component'; +import { MatButtonModule } from '@angular/material/button'; +import { FilterLimitButtonComponent } from '@portal/components/filter-limit-button/filter-limit-button.component'; @Component({ selector: 'app-publications2', @@ -33,7 +35,7 @@ import { CollapsibleComponent } from '@portal/components/collapsible/collapsible imports: [CdkTableModule, FormsModule, AsyncPipe, JsonPipe, NgForOf, NgIf, LimitPipe, NgArrayPipesModule, SharedModule, //TODO not good? FormsModule, - SearchBar2Component, OrganizationFilterComponent, FilterOptionComponent, CollapsibleComponent + SearchBar2Component, OrganizationFilterComponent, FilterOptionComponent, CollapsibleComponent, MatButtonModule, NgStyle, FilterLimitButtonComponent ], standalone: true }) @@ -230,11 +232,17 @@ export class Publications2Component implements OnDestroy { 3: "Tutkimuslaitos", 4: "Yliopistollisen sairaalan erityisvastuualue", 6: "Muu" - } + }; /*public collapseStates = { "language": false, - }*/ + };*/ + + public filterLimits = { + year: 10, + language: 10, + }; + searchParamsSubscription = this.searchParams$.subscribe(searchParams => { this.publications2Service.updateSearchTerms(searchParams); @@ -318,6 +326,14 @@ export class Publications2Component implements OnDestroy { queryParams: { size }, queryParamsHandling: 'merge' }); } + + clamp(value: number, min: number, max: number) { + return Math.min(Math.max(value, min), max); + } + + makeYearLimit20() { + this.filterLimits.year = 20; + } } export class PublicationDataSource extends DataSource { diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 22ed79103..7a3503b9f 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -765,7 +765,8 @@ function additionsFromLanguageCode(searchParams: SearchParams) { "aggregations": { "all_languageCodes": { "terms": { - "field": "languages.languageCode.keyword" + "field": "languages.languageCode.keyword", + "size": 1000, } } } From 7203232027256a7ad39004af2b14ce79e147fe71 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Thu, 7 Dec 2023 14:26:11 +0200 Subject: [PATCH 13/47] Add summary of the filters to the top of the search page --- .../publications2.component.html | 135 ++++++++++++++++-- .../publications2.component.scss | 8 ++ .../publications2/publications2.component.ts | 44 ++++-- 3 files changed, 165 insertions(+), 22 deletions(-) diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index 127df0889..b4621ff63 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -222,21 +222,134 @@

Rajaa hakua

-
- - - - -
-
{{yearFilter.year}}
- -
+ +
+
Rajaukset ({{filterCount$ | async}}):
+ + + + + +
+
{{yearFilter.year}}
+ +
+ +
- -
+ + + + +
+
{{organizationFilter.name}}
+ +
+ +
+
+ + + + + +
+
{{languageCodeFilter.name}}
+ +
+ +
+
+ + + + + +
+
{{publicationFormatFilter.name}}
+ +
+ +
+
+ + + + + + +
+
{{publicationAudienceFilter.name}}
+ +
+ +
+
+ + + + + +
+
{{peerReviewedFilter.name}}
+ +
+ +
+
+ + + + + +
+
{{parentPublicationTypeFilter.name}}
+ +
+ +
+
+ + + + + +
+
{{internationalPublicationFilter.name}}
+ +
+ +
+
+ + + + + +
+
{{articleTypeCodeFilter.name}}
+ +
+ +
+
+ + + + + +
+
{{jufoClassCodeFilter.id}}
+ +
+ +
+
+
+ diff --git a/src/app/portal/components/results/publications2/publications2.component.scss b/src/app/portal/components/results/publications2/publications2.component.scss index c421a6099..99a7bdac8 100644 --- a/src/app/portal/components/results/publications2/publications2.component.scss +++ b/src/app/portal/components/results/publications2/publications2.component.scss @@ -37,3 +37,11 @@ em { font-style: normal; padding: 0.1em 0; } + +.filters-summary { + background-color: #e8e8f5; + padding: 16px; + border: 1px solid #4546b9; + border-radius: 4px; + margin-bottom: 2rem; +} diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 9929b0e59..931cebcd5 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -1,6 +1,6 @@ import { Component, inject, OnDestroy, Pipe, PipeTransform } from '@angular/core'; import { CdkTableModule, DataSource } from '@angular/cdk/table'; -import { combineLatest, Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest, merge, Observable } from 'rxjs'; import { ActivatedRoute, Router } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { AsyncPipe, JsonPipe, NgForOf, NgIf, NgStyle } from '@angular/common'; @@ -48,6 +48,16 @@ export class Publications2Component implements OnDestroy { page = 1; size = 10; + filterCount$ = new BehaviorSubject(0); + filterCountLookup: Record = {}; + + updateFilterCount(key: string, value: number) { + this.filterCountLookup[key] = value; + const count = Object.values(this.filterCountLookup).reduce((a, b) => a + b, 0); + + this.filterCount$.next(count); + } + displayedColumns: string[] = ['publicationName', 'authorsText', 'publisherName', 'publicationYear']; highlights$ = this.publications2Service.getSearch(); // TODO: /*: Observable*/ @@ -66,7 +76,8 @@ export class Publications2Component implements OnDestroy { year: yearAddition.year, count: yearAddition.count, enabled: enabledFilters.includes(yearAddition.year.toString()) - }))) + }))), + tap(filters => this.updateFilterCount("year", filters.filter(filter => filter.enabled).length)) ); organizationNames$ = this.publications2Service.getOrganizationNames(); @@ -83,7 +94,8 @@ export class Publications2Component implements OnDestroy { name: organizationNames[organizationAddition.id].name, sectorId: organizationNames[organizationAddition.id].sectorId, enabled: enabledFilters.includes(organizationAddition.id) - }))) + }))), + tap(filters => this.updateFilterCount("organization", filters.filter(filter => filter.enabled).length)) ); languageCodeAdditions$ = this.aggregations$.pipe( @@ -99,7 +111,8 @@ export class Publications2Component implements OnDestroy { count: languageCodeAddition.count, name: languageCodeNames[languageCodeAddition.languageCode], enabled: enabledFilters.includes(languageCodeAddition.languageCode) - }))) + }))), + tap(filters => this.updateFilterCount("language", filters.filter(filter => filter.enabled).length)) ); publicationFormatNames$ = this.publications2Service.getPublicationFormatNames(); @@ -115,7 +128,8 @@ export class Publications2Component implements OnDestroy { count: publicationFormatAddition.count, name: publicationFormatNames[publicationFormatAddition.id], enabled: enabledFilters.includes(publicationFormatAddition.id) - }))) + }))), + tap(filters => this.updateFilterCount("format", filters.filter(filter => filter.enabled).length)) ); publicationAudienceNames$ = this.publications2Service.getPublicationAudienceNames(); @@ -131,7 +145,8 @@ export class Publications2Component implements OnDestroy { count: publicationAudienceAddition.count, name: publicationAudienceNames[publicationAudienceAddition.id], enabled: enabledFilters.includes(publicationAudienceAddition.id) - }))) + }))), + tap(filters => this.updateFilterCount("audience", filters.filter(filter => filter.enabled).length)) ); peerReviewedAdditions$ = this.aggregations$.pipe( @@ -147,7 +162,8 @@ export class Publications2Component implements OnDestroy { count: peerReviewedAddition.count, name: peerReviewedNames[peerReviewedAddition.id], enabled: enabledFilters.includes(peerReviewedAddition.id) - }))) + }))), + tap(filters => this.updateFilterCount("peerReviewed", filters.filter(filter => filter.enabled).length)) ); // getParentPublicationTypeAdditions @@ -173,7 +189,8 @@ export class Publications2Component implements OnDestroy { count: internationalPublicationAddition.count, name: internationalPublicationNames[internationalPublicationAddition.id], enabled: enabledFilters.includes(internationalPublicationAddition.id) - }))) + }))), + tap(filters => this.updateFilterCount("international", filters.filter(filter => filter.enabled).length)) ); articleTypeCodeAdditions$ = this.aggregations$.pipe( @@ -189,7 +206,8 @@ export class Publications2Component implements OnDestroy { count: articleTypeCodeAddition.count, name: articleTypeCodeNames[articleTypeCodeAddition.id], enabled: enabledFilters.includes(articleTypeCodeAddition.id) - }))) + }))), + tap(filters => this.updateFilterCount("articleType", filters.filter(filter => filter.enabled).length)) ); jufoClassCodeAdditions$ = this.aggregations$.pipe( @@ -203,7 +221,8 @@ export class Publications2Component implements OnDestroy { count: jufoClassCodeAddition.count, name: jufoClassCodeAddition.id, enabled: enabledFilters.includes(jufoClassCodeAddition.id) - }))) + }))), + tap(filters => this.updateFilterCount("jufo", filters.filter(filter => filter.enabled).length)) ); // getParentPublicationTypeNames @@ -222,9 +241,12 @@ export class Publications2Component implements OnDestroy { count: parentPublicationTypeAddition.count, name: parentPublicationTypeNames[parentPublicationTypeAddition.id], enabled: enabledFilters.includes(parentPublicationTypeAddition.id) - }))) + }))), + tap(filters => this.updateFilterCount("parentPublicationType", filters.filter(filter => filter.enabled).length)) ); + + /* TODO localization solution */ public sectorName = { 1: "Yliopisto", From 669e4e455dcd3e3727b0ae59794dab2a0b73ecb9 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Thu, 14 Dec 2023 17:24:40 +0200 Subject: [PATCH 14/47] Add fieldsOfScience and publicationTypeCode filters --- .../collapsible/collapsible.component.html | 26 +- .../collapsible/collapsible.component.scss | 21 +- .../collapsible/collapsible.component.ts | 11 +- .../organization-filter.component.html | 75 ++--- .../organization-filter.component.ts | 6 +- .../publications2.component.html | 123 +++++-- .../publications2/publications2.component.ts | 56 +++- .../portal/services/publication2.service.ts | 312 +++++++++++++++++- src/app/shared/pipes/first-digit.pipe.ts | 12 + src/app/shared/pipes/first-letter.pipe.ts | 11 + 10 files changed, 522 insertions(+), 131 deletions(-) create mode 100644 src/app/shared/pipes/first-digit.pipe.ts create mode 100644 src/app/shared/pipes/first-letter.pipe.ts diff --git a/src/app/portal/components/collapsible/collapsible.component.html b/src/app/portal/components/collapsible/collapsible.component.html index 99effae04..b7343781c 100644 --- a/src/app/portal/components/collapsible/collapsible.component.html +++ b/src/app/portal/components/collapsible/collapsible.component.html @@ -1,19 +1,31 @@
- {{label}} +
+ +
-
+
+ + +
+
+
+ +
{{label}}
+
+ - - + <!––> - + <!––> +
--> + +
+
diff --git a/src/app/portal/components/collapsible/collapsible.component.scss b/src/app/portal/components/collapsible/collapsible.component.scss index 8c31e524d..372b2fdd0 100644 --- a/src/app/portal/components/collapsible/collapsible.component.scss +++ b/src/app/portal/components/collapsible/collapsible.component.scss @@ -16,13 +16,8 @@ justify-content: space-between; } -.collapsible-icon { - transition: transform 0.1s ease-out; -} - -/* icon can be flipped */ .icon-collapsed { - transition: transform 0.25s ease-out; + transition: transform 0.25s linear; transform: rotate(0deg); } @@ -31,20 +26,14 @@ transform: rotate(180deg); } -/*.collapsible-container .content { - -} - -.collapsible-container .content.collapsed { - // max-height: 1000px; !* Adjust as needed *! -}*/ - - .collapsible-content { + transition: max-height 0.4s ease-out; overflow: hidden; - transition: max-height 0.2s ease-out; + + max-height: 1000px; } .collapsed { + // transition: 0.2s linear; max-height: 0; } diff --git a/src/app/portal/components/collapsible/collapsible.component.ts b/src/app/portal/components/collapsible/collapsible.component.ts index b49866255..97f51dca1 100644 --- a/src/app/portal/components/collapsible/collapsible.component.ts +++ b/src/app/portal/components/collapsible/collapsible.component.ts @@ -1,6 +1,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { NgClass, NgIf } from '@angular/common'; import { MatIconModule } from '@angular/material/icon'; +import { animate, state, style, transition, trigger } from '@angular/animations'; @Component({ selector: 'app-collapsible', @@ -11,10 +12,18 @@ import { MatIconModule } from '@angular/material/icon'; MatIconModule, NgClass ], - standalone: true + standalone: true, + animations: [ + trigger('expandCollapse', [ + state('collapsed', style({ height: '0px', overflow: 'hidden' })), + state('expanded', style({ height: '*', overflow: 'hidden' })), + transition('expanded <=> collapsed', animate('0.1s ease-out')) + ]) + ] }) export class CollapsibleComponent { @Input() label = ''; + @Input() decoration = "none"; @Input() isOpen = false; @Output() isOpenChange = new EventEmitter(); diff --git a/src/app/portal/components/organization-filter/organization-filter.component.html b/src/app/portal/components/organization-filter/organization-filter.component.html index 9a0ec15db..e366e9d00 100644 --- a/src/app/portal/components/organization-filter/organization-filter.component.html +++ b/src/app/portal/components/organization-filter/organization-filter.component.html @@ -1,54 +1,31 @@
- -
- - Organisaatio -
- - - - -
-
{{sectorNames[sectorId]}}
- - {{ accordionItem.expanded ? 'close' : 'open' }} -
- -
-
Valitse kaikki
- - - - - - - - - - - - -
- -
-
-
- - -
- + Organisaatio -
-
+ + + +
Valitse kaikki
+ + + + + + + + +
+ + + +
+ +
- diff --git a/src/app/portal/components/organization-filter/organization-filter.component.ts b/src/app/portal/components/organization-filter/organization-filter.component.ts index 1599cb846..e28ff7b90 100644 --- a/src/app/portal/components/organization-filter/organization-filter.component.ts +++ b/src/app/portal/components/organization-filter/organization-filter.component.ts @@ -5,6 +5,7 @@ import { NgArrayPipesModule } from 'ngx-pipes'; import { FormsModule } from '@angular/forms'; import { LimitPipe } from '@portal/pipes/limit.pipe'; import { CdkAccordionModule } from '@angular/cdk/accordion'; +import { CollapsibleComponent } from '@portal/components/collapsible/collapsible.component'; @Component({ selector: 'app-organization-filter', @@ -18,7 +19,8 @@ import { CdkAccordionModule } from '@angular/cdk/accordion'; NgArrayPipesModule, NgForOf, FormsModule, - CdkAccordionModule + CdkAccordionModule, + CollapsibleComponent ], standalone: true }) @@ -26,6 +28,8 @@ export class OrganizationFilterComponent { @Input() filterData: unknown; @Output() selected = new EventEmitter(); + selectedSector = -1; + expanded = false; sectorFilter: Record = {}; diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index b4621ff63..547cca5b2 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -87,28 +87,49 @@

Rajaa hakua

- - - - - - + + -
- - Näytä enemmän - + + + + + + + + + + - - Näytä vähemmän - -
+
+
+ + +
+
+ + + + + + + + + + + + + +
@@ -144,16 +165,17 @@

Rajaa hakua

+
- - - + + + @@ -163,12 +185,27 @@

Rajaa hakua

- - - + + + + + + +
+ + +
+ +
+ + + @@ -193,14 +230,28 @@

Rajaa hakua

- - - - + + + + + + + +
+ + Näytä enemmän + + + + Näytä vähemmän + +
+ +
diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 931cebcd5..4231b00e2 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -5,14 +5,14 @@ import { ActivatedRoute, Router } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { AsyncPipe, JsonPipe, NgForOf, NgIf, NgStyle } from '@angular/common'; import { - getArticleTypeCodeAdditions, + getArticleTypeCodeAdditions, getFieldsOfScienceAdditions, getInternationalPublicationAdditions, getJufoClassCodeAdditions, getLanguageCodeAdditions, getOrganizationAdditions, getParentPublicationTypeAdditions, getPeerReviewedAdditions, getPublicationAudienceAdditions, - getPublicationFormatAdditions, + getPublicationFormatAdditions, getPublicationTypeCodeAdditions, getYearAdditions, HighlightedPublication, Publication2Service @@ -27,6 +27,8 @@ import { LimitPipe } from '@portal/pipes/limit.pipe'; import { CollapsibleComponent } from '@portal/components/collapsible/collapsible.component'; import { MatButtonModule } from '@angular/material/button'; import { FilterLimitButtonComponent } from '@portal/components/filter-limit-button/filter-limit-button.component'; +import { FirstDigitPipe } from '@shared/pipes/first-digit.pipe'; +import { FirstLetterPipe } from '@shared/pipes/first-letter.pipe'; @Component({ selector: 'app-publications2', @@ -35,7 +37,7 @@ import { FilterLimitButtonComponent } from '@portal/components/filter-limit-butt imports: [CdkTableModule, FormsModule, AsyncPipe, JsonPipe, NgForOf, NgIf, LimitPipe, NgArrayPipesModule, SharedModule, //TODO not good? FormsModule, - SearchBar2Component, OrganizationFilterComponent, FilterOptionComponent, CollapsibleComponent, MatButtonModule, NgStyle, FilterLimitButtonComponent + SearchBar2Component, OrganizationFilterComponent, FilterOptionComponent, CollapsibleComponent, MatButtonModule, NgStyle, FilterLimitButtonComponent, FirstDigitPipe, FirstLetterPipe ], standalone: true }) @@ -225,6 +227,38 @@ export class Publications2Component implements OnDestroy { tap(filters => this.updateFilterCount("jufo", filters.filter(filter => filter.enabled).length)) ); + fieldsOfScienceNames$ = this.publications2Service.getFieldsOfScienceNames(); + + fieldsOfScienceAdditions$ = this.aggregations$.pipe( + map(aggs => getFieldsOfScienceAdditions(aggs).map((bucket: any) => ({ id: bucket.key.toString(), count: bucket.doc_count })) ?? []), + map(aggs => aggs.sort((a, b) => b.count - a.count)) + ); + + fieldsOfScienceFilters$ = combineLatest([this.fieldsOfScienceAdditions$, this.fieldsOfScienceNames$, this.searchParams$.pipe(map(params => params.fieldsOfScience ?? []))]).pipe( + map(([fieldsOfScienceAdditions, fieldsOfScienceNames, enabledFilters]) => fieldsOfScienceAdditions.map(fieldsOfScienceAddition => ({ + id: fieldsOfScienceAddition.id, + count: fieldsOfScienceAddition.count, + name: fieldsOfScienceNames[fieldsOfScienceAddition.id], + enabled: enabledFilters.includes(fieldsOfScienceAddition.id) + }))), + tap(filters => this.updateFilterCount("fieldsOfScience", filters.filter(filter => filter.enabled).length)) + ); + + publicationTypeCodeAdditions$ = this.aggregations$.pipe( + map(aggs => getPublicationTypeCodeAdditions(aggs).map((bucket: any) => ({ id: bucket.key.toString(), count: bucket.doc_count })) ?? []), + map(aggs => aggs.sort((a, b) => b.count - a.count)) + ); + + publicationTypeCodeFilters$ = combineLatest([this.publicationTypeCodeAdditions$, this.searchParams$.pipe(map(params => params.publicationTypeCode ?? []))]).pipe( + map(([publicationTypeCodeAdditions, enabledFilters]) => publicationTypeCodeAdditions.map(publicationTypeCodeAddition => ({ + id: publicationTypeCodeAddition.id, + count: publicationTypeCodeAddition.count, + name: publicationTypeCodeAddition.id, + enabled: enabledFilters.includes(publicationTypeCodeAddition.id) + }))), + tap(filters => this.updateFilterCount("publicationTypeCode", filters.filter(filter => filter.enabled).length)) + ); + // getParentPublicationTypeNames // getInternationalPublicationNames // getArticleTypeCodeNames @@ -256,6 +290,18 @@ export class Publications2Component implements OnDestroy { 6: "Muu" }; + /* TODO localization solution */ + public mainFieldOfScienceName = { + "1": "Luonnontieteet", + "2": "Tekniikka", + "3": "Lääke- ja terveystieteet", + "4": "Maatalous- ja metsätieteet", + "5": "Yhteiskuntatieteet", + "6": "Humanistiset tieteet", + // 7 not used + "8": "Taiteenala", + } + /*public collapseStates = { "language": false, };*/ @@ -352,10 +398,6 @@ export class Publications2Component implements OnDestroy { clamp(value: number, min: number, max: number) { return Math.min(Math.max(value, min), max); } - - makeYearLimit20() { - this.filterLimits.year = 20; - } } export class PublicationDataSource extends DataSource { diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 7a3503b9f..224f9dd78 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -53,6 +53,8 @@ type SearchParams = { // placeholders publicationTypeCode?: string[], publicationStatusCode?: string[], + + fieldsOfScience?: string[], } @Injectable({ @@ -135,7 +137,9 @@ export class Publication2Service { ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams) + ...termsForJufoClassCode(searchParams), + ...termsForFieldsOfScience(searchParams), + ...termsForPublicationTypeCode(searchParams), ] } } @@ -163,7 +167,9 @@ export class Publication2Service { ...additionsFromParentPublicationType(searchParams), ...additionsFromInternationalPublication(searchParams), ...additionsFromArticleTypeCode(searchParams), - ...additionsFromJufoClassCode(searchParams) + ...additionsFromJufoClassCode(searchParams), + ...additionsFromFieldsOfScience(searchParams), + ...additionsFromPublicationTypeCode(searchParams), } }); } @@ -433,6 +439,66 @@ export class Publication2Service { "3": "Verkkoalusta" // TODO use localize`` }); } + + getFieldsOfScienceNames(): Observable> { + const body = { + "size": 0, + "aggs": { + "fieldsOfScience_nested": { + "nested": { + "path": "fieldsOfScience" + }, + "aggs": { + "composite_field_of_science": { + "composite": { + "size": 65536, + "sources": [ + { + "id": { + "terms": { + "field": "fieldsOfScience.fieldIdScience" + } + } + }, + { + "name": { + "terms": { + "field": "fieldsOfScience.nameFiScience.keyword" // TODO localized path needed + } + } + } + ] + } + } + } + } + } + }; + + type FieldsOfScienceAggregation = { + aggregations: { + fieldsOfScience_nested: { + composite_field_of_science: { + buckets: Array<{ + key: { + id: string; + name: string; + }; + doc_count: number; + }>; + }; + }; + }; + }; + + const response$ = this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', body); + + return response$.pipe( + map((res) => res.aggregations.fieldsOfScience_nested.composite_field_of_science.buckets.map((bucket) => [bucket.key.id, bucket.key.name])), + map(pairs => Object.fromEntries(pairs)), + shareReplay({ bufferSize: 1, refCount: true }) + ); + } } function matchingTerms(searchParams: SearchParams) { @@ -633,7 +699,9 @@ function additionsFromYear(searchParams: SearchParams) { ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams) + ...termsForJufoClassCode(searchParams), + ...termsForFieldsOfScience(searchParams), + ...termsForPublicationTypeCode(searchParams), ] } }, @@ -684,7 +752,9 @@ function additionsFromTypeCode(searchParams: SearchParams) { ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams) + ...termsForJufoClassCode(searchParams), + ...termsForFieldsOfScience(searchParams), + ...termsForPublicationTypeCode(searchParams), ] } }, @@ -721,7 +791,9 @@ function additionsFromStatusCode(searchParams: SearchParams) { ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams) + ...termsForJufoClassCode(searchParams), + ...termsForFieldsOfScience(searchParams), + ...termsForPublicationTypeCode(searchParams), ] } }, @@ -758,7 +830,9 @@ function additionsFromLanguageCode(searchParams: SearchParams) { ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams) + ...termsForJufoClassCode(searchParams), + ...termsForFieldsOfScience(searchParams), + ...termsForPublicationTypeCode(searchParams), ] } }, @@ -796,7 +870,9 @@ function additionsFromPublicationFormat(searchParams: SearchParams) { ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams) + ...termsForJufoClassCode(searchParams), + ...termsForFieldsOfScience(searchParams), + ...termsForPublicationTypeCode(searchParams), ] } }, @@ -834,7 +910,9 @@ function additionsFromPublicationAudience(searchParams: SearchParams) { ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams) + ...termsForJufoClassCode(searchParams), + ...termsForFieldsOfScience(searchParams), + ...termsForPublicationTypeCode(searchParams), ] } }, @@ -871,7 +949,9 @@ function additionsFromPeerReviewed(searchParams: SearchParams) { ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams) + ...termsForJufoClassCode(searchParams), + ...termsForFieldsOfScience(searchParams), + ...termsForPublicationTypeCode(searchParams), ] } }, @@ -909,7 +989,9 @@ function additionsFromParentPublicationType(searchParams: SearchParams) { ...termsForPeerReviewed(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams) + ...termsForJufoClassCode(searchParams), + ...termsForFieldsOfScience(searchParams), + ...termsForPublicationTypeCode(searchParams), ] } }, @@ -947,7 +1029,9 @@ function additionsFromInternationalPublication(searchParams: SearchParams) { ...termsForPeerReviewed(searchParams), ...termsForParentPublicationType(searchParams), ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams) + ...termsForJufoClassCode(searchParams), + ...termsForFieldsOfScience(searchParams), + ...termsForPublicationTypeCode(searchParams), ] } }, @@ -985,7 +1069,9 @@ return { ...termsForPeerReviewed(searchParams), ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), - ...termsForJufoClassCode(searchParams) + ...termsForJufoClassCode(searchParams), + ...termsForFieldsOfScience(searchParams), + ...termsForPublicationTypeCode(searchParams), ] } }, @@ -1023,7 +1109,9 @@ function additionsFromJufoClassCode(searchParams: SearchParams) { ...termsForPeerReviewed(searchParams), ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), - ...termsForArticleTypeCode(searchParams) + ...termsForArticleTypeCode(searchParams), + ...termsForFieldsOfScience(searchParams), + ...termsForPublicationTypeCode(searchParams), ] } }, @@ -1041,6 +1129,53 @@ function additionsFromJufoClassCode(searchParams: SearchParams) { } } +function additionsFromFieldsOfScience(searchParams: SearchParams) { + return { + "all_data_except_fieldsOfScience": { + "global": {}, + "aggregations": { + "filtered_except_fieldsOfScience": { + "filter": { + "bool": { + "must": [ + matchingTerms(searchParams), + ...termsForYear(searchParams), + ...termsForTypeCode(searchParams), + ...termsForStatusCode(searchParams), + ...termsForOrganization(searchParams), + ...termsForLanguageCode(searchParams), + ...termsForPublicationFormat(searchParams), + ...termsForPublicationAudience(searchParams), + ...termsForPeerReviewed(searchParams), + ...termsForParentPublicationType(searchParams), + ...termsForInternationalPublication(searchParams), + ...termsForArticleTypeCode(searchParams), + ...termsForJufoClassCode(searchParams), + ...termsForPublicationTypeCode(searchParams), + ] + } + }, + "aggregations": { + "fieldsOfScience_nested": { + "nested": { + "path": "fieldsOfScience" + }, + "aggregations": { + "all_fieldsOfScience": { + "terms": { + "field": "fieldsOfScience.fieldIdScience", + "size": 1000, + } + } + } + } + } + } + } + } + }; +} + type OrganizationAggregation = { all_data_except_organizationId: { filtered_except_organizationId: { @@ -1064,6 +1199,7 @@ function suffixer(locale) { const path = suffixer("fi"); +// NOTE: nested is needed for array type mapping function termsForOrganization(searchParams: SearchParams) { if (searchParams.organization && searchParams.organization.length > 0) { return [{ @@ -1086,6 +1222,103 @@ function termsForOrganization(searchParams: SearchParams) { return []; } +// NOTE: nested is needed for array type mapping +function termsForFieldsOfScience(searchParams: SearchParams) { + if (searchParams.fieldsOfScience && searchParams.fieldsOfScience.length > 0) { + return [{ + "nested": { + "path": "fieldsOfScience", + "query": { + "bool": { + "must": [ + { + "terms": { + "fieldsOfScience.fieldIdScience": searchParams.fieldsOfScience + } + } + ] + } + } + } + }]; + } + return []; +} + +/* +GET publication/_search +{ + "query": { + "match": { + "publicationTypeCode": "A1" + } + } +} + +GET publication/_search +{ + "size": 0, + "aggs": { + "publicationTypeCode": { + "terms": { + "field": "publicationTypeCode.keyword", + "size": 100 + } + } + } +} +*/ + +function termsForPublicationTypeCode(searchParams: SearchParams) { + if (searchParams.publicationTypeCode) { + return [{ + terms: { + "publicationTypeCode.keyword": searchParams.publicationTypeCode + } + }]; + } + return []; +} + +function additionsFromPublicationTypeCode(searchParams: SearchParams) { + return { + "all_data_except_publicationTypeCode": { + "global": {}, + "aggregations": { + "filtered_except_publicationTypeCode": { + "filter": { + "bool": { + "must": [ + matchingTerms(searchParams), + ...termsForYear(searchParams), + ...termsForStatusCode(searchParams), + ...termsForOrganization(searchParams), + ...termsForLanguageCode(searchParams), + ...termsForPublicationFormat(searchParams), + ...termsForPublicationAudience(searchParams), + ...termsForPeerReviewed(searchParams), + ...termsForParentPublicationType(searchParams), + ...termsForInternationalPublication(searchParams), + ...termsForJufoClassCode(searchParams), + ...termsForArticleTypeCode(searchParams), + ...termsForFieldsOfScience(searchParams) + ] + } + }, + "aggregations": { + "all_publicationTypeCodes": { + "terms": { + "field": "publicationTypeCode.keyword", + "size": 100 + } + } + } + } + } + } + }; +} + function additionsFromOrganization(searchParams: SearchParams) { return { "all_data_except_organizationId": { @@ -1106,7 +1339,9 @@ function additionsFromOrganization(searchParams: SearchParams) { ...termsForParentPublicationType(searchParams), ...termsForInternationalPublication(searchParams), ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams) + ...termsForJufoClassCode(searchParams), + ...termsForFieldsOfScience(searchParams), + ...termsForPublicationTypeCode(searchParams), ] } }, @@ -1151,6 +1386,47 @@ type OrgsAggsResponse = { }; }; +// helper for getting all terms instead of using a array literal +function allMatchingTerms(searchParams: SearchParams) { + return [ + matchingTerms(searchParams), + ...termsForYear(searchParams), + ...termsForTypeCode(searchParams), + ...termsForStatusCode(searchParams), + ...termsForOrganization(searchParams), + ...termsForLanguageCode(searchParams), + ...termsForPublicationFormat(searchParams), + ...termsForPublicationAudience(searchParams), + ...termsForPeerReviewed(searchParams), + ...termsForParentPublicationType(searchParams), + ...termsForInternationalPublication(searchParams), + ...termsForArticleTypeCode(searchParams), + ...termsForJufoClassCode(searchParams), + ...termsForFieldsOfScience(searchParams), + // ...termsForPublicationTypeCode(searchParams) // TODO ENABLE + ]; +} + +function allMatchingTermsExcept(excluded: string, searchParams: SearchParams) { + return [ + matchingTerms(searchParams), + ...(excluded === "year" ? [] : termsForYear(searchParams)), + ...(excluded === "typeCode" ? [] : termsForTypeCode(searchParams)), + ...(excluded === "statusCode" ? [] : termsForStatusCode(searchParams)), + ...(excluded === "organization" ? [] : termsForOrganization(searchParams)), + ...(excluded === "languageCode" ? [] : termsForLanguageCode(searchParams)), + ...(excluded === "publicationFormat" ? [] : termsForPublicationFormat(searchParams)), + ...(excluded === "publicationAudience" ? [] : termsForPublicationAudience(searchParams)), + ...(excluded === "peerReviewed" ? [] : termsForPeerReviewed(searchParams)), + ...(excluded === "parentPublicationType" ? [] : termsForParentPublicationType(searchParams)), + ...(excluded === "internationalPublication" ? [] : termsForInternationalPublication(searchParams)), + ...(excluded === "articleTypeCode" ? [] : termsForArticleTypeCode(searchParams)), + ...(excluded === "jufoClassCode" ? [] : termsForJufoClassCode(searchParams)), + ...(excluded === "fieldsOfScience" ? [] : termsForFieldsOfScience(searchParams)), + // ...(excluded === "publicationTypeCode" ? [] : termsForPublicationTypeCode(searchParams)), // TODO ENABLE + ]; +} + function toIdNameLookup(data: OrgsAggsResponse): Map { const pairs = getOrganizationNameBuckets(data).map((bucket) => [bucket.key.id, bucket.key.name]); @@ -1200,3 +1476,11 @@ export function getArticleTypeCodeAdditions(aggregations: any) { export function getJufoClassCodeAdditions(aggregations: any) { return aggregations.all_data_except_jufoClassCode?.filtered_except_jufoClassCode.all_jufoClassCodes.buckets ?? []; } + +export function getFieldsOfScienceAdditions(aggregations: any) { + return aggregations.all_data_except_fieldsOfScience?.filtered_except_fieldsOfScience.fieldsOfScience_nested.all_fieldsOfScience.buckets ?? []; +} + +export function getPublicationTypeCodeAdditions(aggregations: any) { + return aggregations.all_data_except_publicationTypeCode?.filtered_except_publicationTypeCode.all_publicationTypeCodes.buckets ?? []; +} diff --git a/src/app/shared/pipes/first-digit.pipe.ts b/src/app/shared/pipes/first-digit.pipe.ts new file mode 100644 index 000000000..629c18b00 --- /dev/null +++ b/src/app/shared/pipes/first-digit.pipe.ts @@ -0,0 +1,12 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'firstDigit', + standalone: true +}) +export class FirstDigitPipe implements PipeTransform { + transform(value: number): number { + const firstDigit = Math.abs(value).toString()[0]; + return parseInt(firstDigit); + } +} diff --git a/src/app/shared/pipes/first-letter.pipe.ts b/src/app/shared/pipes/first-letter.pipe.ts new file mode 100644 index 000000000..15240c59f --- /dev/null +++ b/src/app/shared/pipes/first-letter.pipe.ts @@ -0,0 +1,11 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'firstLetter', + standalone: true +}) +export class FirstLetterPipe implements PipeTransform { + transform(value: string): string { + return value[0]; + } +} From 6e71a2ae1df9cbf4e8f41c1533a36b0fdbec4c11 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Wed, 3 Jan 2024 20:26:19 +0200 Subject: [PATCH 15/47] Replacing strings with i18n enabled equivalent --- .../collapsible/collapsible.component.html | 20 ++- .../collapsible/collapsible.component.ts | 17 +- .../organization-filter.component.html | 6 +- .../publications2.component.html | 163 ++++++++++++++++-- .../publications2/publications2.component.ts | 40 +++-- .../portal/services/publication2.service.ts | 61 ++++++- 6 files changed, 267 insertions(+), 40 deletions(-) diff --git a/src/app/portal/components/collapsible/collapsible.component.html b/src/app/portal/components/collapsible/collapsible.component.html index b7343781c..2f03b52f3 100644 --- a/src/app/portal/components/collapsible/collapsible.component.html +++ b/src/app/portal/components/collapsible/collapsible.component.html @@ -1,3 +1,9 @@ + +
+ +
+
+
@@ -10,6 +16,12 @@
{{label}}
+ + +
+ +
+
@@ -17,14 +29,6 @@
- -
diff --git a/src/app/portal/components/collapsible/collapsible.component.ts b/src/app/portal/components/collapsible/collapsible.component.ts index 97f51dca1..e40ba2c66 100644 --- a/src/app/portal/components/collapsible/collapsible.component.ts +++ b/src/app/portal/components/collapsible/collapsible.component.ts @@ -1,7 +1,17 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { + Component, ContentChild, + ContentChildren, + ElementRef, + EventEmitter, + Input, + Output, + QueryList, TemplateRef, + ViewChild +} from '@angular/core'; import { NgClass, NgIf } from '@angular/common'; import { MatIconModule } from '@angular/material/icon'; import { animate, state, style, transition, trigger } from '@angular/animations'; +import { TooltipModule } from 'ngx-bootstrap/tooltip'; @Component({ selector: 'app-collapsible', @@ -10,7 +20,8 @@ import { animate, state, style, transition, trigger } from '@angular/animations' imports: [ NgIf, MatIconModule, - NgClass + NgClass, + TooltipModule ], standalone: true, animations: [ @@ -28,6 +39,8 @@ export class CollapsibleComponent { @Input() isOpen = false; @Output() isOpenChange = new EventEmitter(); + @Input() hasTooltip = false; + toggle() { this.isOpen = !this.isOpen; this.isOpenChange.emit(this.isOpen); diff --git a/src/app/portal/components/organization-filter/organization-filter.component.html b/src/app/portal/components/organization-filter/organization-filter.component.html index e366e9d00..58bb1c93e 100644 --- a/src/app/portal/components/organization-filter/organization-filter.component.html +++ b/src/app/portal/components/organization-filter/organization-filter.component.html @@ -1,5 +1,9 @@
- + + + Julkaisun tekijän suomalainen organisaatio. Palvelu ei toistaiseksi sisällä tietoja julkaisujen ulkomaisista organisaatioista. + +
Valitse kaikki
diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index 547cca5b2..31703c6c5 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -54,7 +54,7 @@

Rajaa hakua

- + Rajaa hakua
- + + + Tilastokeskuksen tieteenalaluokitus. Taiteenalat OKM:n luokituksen mukaisesti. Julkaisulla voi olla 1-6 tieteen- tai taiteenalaa. + + @@ -112,7 +116,11 @@

Rajaa hakua

- + + + OKM:n julkaisutiedonkeruun mukainen julkaisutyyppi A–G. + + @@ -138,7 +146,40 @@

Rajaa hakua

- + + +

+ Artikkeli: + Sisältää alkuperäis- ja katsausartikkelit, kirjan tai lehden johdannot ja esipuheet, lyhyet tutkimusselostukset, pääkirjoitukset, keskustelupuheenvuorot ja kommentit. +

+ +

+ Erillisteos: + Sisältää monografiat/kirjat, tutkimus- ja kehitystyöhön perustuva kehittämis- tai tutkimusraportti, selvitykset, ns. white paperit sekä working papers ja discussion papers -tyyppiset julkaisut. +

+ + +

+ Toimitustyö: + Sisältää useista eri kirjoittajien artikkeleista koostuvan tieteellisen kirjan tai lehden erikoisnumeron toimitustyöt. +

+ +

+ Abstrakti: + Sisältää konferenssiesitelmien abstraktit sekä laajennetut abstraktit. +

+ +

+ Posteri: + Sisältää konferenssiesitelmien posterit. +

+ +

+ Blogikirjoitus: + Sisältää blogimuotoiset julkaisut, joiden julkaisemisesta on päättänyt riippumaton toimituskunta tai joiden julkaisualustalla on ISSN-tunnus. +

+
+ Rajaa hakua
- + + +

+ Julkaisukanavan kohdeyleisö +

+ +

+ Tieteellinen julkaisu: + Julkaisut, jotka on tarkoitettu edistämään tiedettä sekä tuottamaan uutta tietoa. +

+ +

+ Ammatillinen julkaisu: + Julkaisut, jotka levittävät tutkimukseen ja kehitystyöhön perustuvaa tietoa ammattiyhteisön käyttöön. +

+ +

+ Yleistajuinen julkaisu: + Julkaisut, jotka levittävät tutkimus- ja kehitystyöhön perustuvaa tietoa suurelle yleisölle ja joiden sisällön ymmärtäminen ei edellytä erityistä perehtyneisyyttä alaan. +

+
+ Rajaa hakua
- + + +

+ Lehti: + sisältää tieteelliset aikakauslehdet ja ammattilehdet. +

+ +

+ Kokoomateos: + Sisältää tieteelliset kokoomateokset, tieteelliset vuosikirjat ja vastaavat, ammatilliset käsi- tai opaskirjat, ammatilliset tietojärjestelmät tai kokoomateokset, oppikirja-aineistot sekä lyhyet ensyklopediatekstit. +

+ +

+ Konferenssi: + Sisältää konferenssin painetut tai julkisesti saatavilla olevat julkaisut, ns. proceedings-julkaisut. +

+ +

+ Verkkoalusta: + Sisältää muilla sähköisillä alustoilla julkaistut julkaisut. +

+
+ Rajaa hakua
- + + +

+ Alkuperäisartikkeli: + + on pääosin aiemmin julkaisemattomasta materiaalista koostuva tieteellinen artikkeli. +

+ +

+ Katsausartikkeli: + + perustuu aikaisempiin samasta aihepiiristä tehtyihin julkaisuihin. +

+ +

+ Data-artikkeli: + + sisältää ns. data journals -julkaisuissa ilmestyneet, tutkimusaineistoja kuvailevat artikkelit. +

+ +

+ Muu artikkeli: + + sisältää muihin luokkiin kuulumattomat artikkelit. +

+
+ + Rajaa hakua
- + + Tieteellisten julkaisujen vertaisarvioinnilla tarkoitetaan menettelyä, jossa tutkimustuloksia julkaiseva lehti, konferenssi tai kirjakustantaja pyytää tieteenalan asiantuntijoita suorittamaan ennakkoarvion julkaistavaksi tarjottujen käsikirjoitusten tieteellisestä julkaisukelpoisuudesta. + Rajaa hakua
- - + + + Kotimaisen julkaisun kustantaja on suomalainen tai se on ensisijaisesti julkaistu Suomessa. Kansainvälisen julkaisun kustantaja ei ole suomalainen tai se on ensisijaisesti julkaistu muualla kuin Suomessa. + + + Rajaa hakua
- + + + Kieli, jolla julkaisu on kirjoitettu. + + Rajaa hakua
- + + + Julkaisufoorumin (www.julkaisufoorumi.fi) mukainen julkaisukanavan (kirjakustantaja, konferenssi tai julkaisusarja) tasoluokitus: 1 = perustaso, 2 = johtava taso, 3 = korkein taso. Tasolla 0 ovat kanavat, jotka eivät joltain osin täytä tason 1 vaatimuksia tai ovat uusia. Julkaisufoorumitaso määräytyy julkaisun julkaisuvuoden mukaan. + + Rajaa hakua - +
@@ -405,17 +530,19 @@

Rajaa hakua

- Julkaisun nimi +
Julkaisun nimi
-
+ +
+
- Tekijät + Tekijät @@ -425,7 +552,7 @@

Rajaa hakua

- Julkaisukanava + Julkaisukanava @@ -435,7 +562,7 @@

Rajaa hakua

- Julkaisuvuosi + Julkaisuvuosi diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 4231b00e2..099fdcfc9 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -1,12 +1,12 @@ import { Component, inject, OnDestroy, Pipe, PipeTransform } from '@angular/core'; import { CdkTableModule, DataSource } from '@angular/cdk/table'; import { BehaviorSubject, combineLatest, merge, Observable } from 'rxjs'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { AsyncPipe, JsonPipe, NgForOf, NgIf, NgStyle } from '@angular/common'; import { getArticleTypeCodeAdditions, getFieldsOfScienceAdditions, - getInternationalPublicationAdditions, getJufoClassCodeAdditions, + getPublisherInternationalityAdditions, getJufoClassCodeAdditions, getLanguageCodeAdditions, getOrganizationAdditions, getParentPublicationTypeAdditions, @@ -37,7 +37,7 @@ import { FirstLetterPipe } from '@shared/pipes/first-letter.pipe'; imports: [CdkTableModule, FormsModule, AsyncPipe, JsonPipe, NgForOf, NgIf, LimitPipe, NgArrayPipesModule, SharedModule, //TODO not good? FormsModule, - SearchBar2Component, OrganizationFilterComponent, FilterOptionComponent, CollapsibleComponent, MatButtonModule, NgStyle, FilterLimitButtonComponent, FirstDigitPipe, FirstLetterPipe + SearchBar2Component, OrganizationFilterComponent, FilterOptionComponent, CollapsibleComponent, MatButtonModule, NgStyle, FilterLimitButtonComponent, FirstDigitPipe, FirstLetterPipe, RouterLink ], standalone: true }) @@ -50,6 +50,21 @@ export class Publications2Component implements OnDestroy { page = 1; size = 10; + labelText = { + yearOfPublication: $localize`:@@yearOfPublication:Julkaisuvuosi`, + organization: $localize`:@@organization:Organisaatio`, + fieldOfScience: $localize`:@@fieldOfScience:Tieteenala`, + publicationType: $localize`:@@publicationType:Julkaisutyyppi`, + publicationFormat: $localize`:@@publicationFormat:Julkaisumuoto`, + publicationAudience: $localize`:@@publicationAudience:Julkaisun yleisö`, + parentPublicationType: $localize`:@@parentPublicationType:Emojulkaisun tyyppi`, + articleType: $localize`:@@articleType:Artikkelin tyyppi`, + peerReviewed: $localize`:@@peerReviewed:Vertaisarvioitu`, + publisherInternationality: $localize`:@@publisherInternationality:Kustantajan kansainvälisyys`, + language: $localize`:@@language:Kieli`, + jufoLevel: $localize`:@@jufoLevel:Julkaisufoorumitaso`, + } + filterCount$ = new BehaviorSubject(0); filterCountLookup: Record = {}; @@ -178,14 +193,17 @@ export class Publications2Component implements OnDestroy { map(aggs => aggs.sort((a, b) => b.count - a.count)) ); - internationalPublicationAdditions$ = this.aggregations$.pipe( - map(aggs => getInternationalPublicationAdditions(aggs).map((bucket: any) => ({ id: bucket.key.toString(), count: bucket.doc_count })) ?? []), + // publisherInternationalityAdditions$ + publisherInternationalityAdditions$ = this.aggregations$.pipe( + map(aggs => getPublisherInternationalityAdditions(aggs).map((bucket: any) => ({ id: bucket.key.toString(), count: bucket.doc_count })) ?? []), map(aggs => aggs.sort((a, b) => b.count - a.count)) ); - internationalPublicationNames$ = this.publications2Service.getInternationalPublicationNames(); + // publisherInternationalityNames$ + publisherInternationalityNames$ = this.publications2Service.getInternationalPublicationNames(); - internationalPublicationFilters$ = combineLatest([this.internationalPublicationAdditions$, this.internationalPublicationNames$, this.searchParams$.pipe(map(params => params.international ?? []))]).pipe( + // publisherInternationalityFilters$ + publisherInternationalityFilters$ = combineLatest([this.publisherInternationalityAdditions$, this.publisherInternationalityNames$, this.searchParams$.pipe(map(params => params.international ?? []))]).pipe( map(([internationalPublicationAdditions, internationalPublicationNames, enabledFilters]) => internationalPublicationAdditions.map(internationalPublicationAddition => ({ id: internationalPublicationAddition.id, count: internationalPublicationAddition.count, @@ -244,16 +262,18 @@ export class Publications2Component implements OnDestroy { tap(filters => this.updateFilterCount("fieldsOfScience", filters.filter(filter => filter.enabled).length)) ); + publicationTypeCodeNames$ = this.publications2Service.getPublicationTypeCodeNames(); + publicationTypeCodeAdditions$ = this.aggregations$.pipe( map(aggs => getPublicationTypeCodeAdditions(aggs).map((bucket: any) => ({ id: bucket.key.toString(), count: bucket.doc_count })) ?? []), map(aggs => aggs.sort((a, b) => b.count - a.count)) ); - publicationTypeCodeFilters$ = combineLatest([this.publicationTypeCodeAdditions$, this.searchParams$.pipe(map(params => params.publicationTypeCode ?? []))]).pipe( - map(([publicationTypeCodeAdditions, enabledFilters]) => publicationTypeCodeAdditions.map(publicationTypeCodeAddition => ({ + publicationTypeCodeFilters$ = combineLatest([this.publicationTypeCodeAdditions$, this.publicationTypeCodeNames$, this.searchParams$.pipe(map(params => params.publicationTypeCode ?? []))]).pipe( + map(([publicationTypeCodeAdditions, publicationTypeCodeNames, enabledFilters]) => publicationTypeCodeAdditions.map(publicationTypeCodeAddition => ({ id: publicationTypeCodeAddition.id, count: publicationTypeCodeAddition.count, - name: publicationTypeCodeAddition.id, + name: publicationTypeCodeNames[publicationTypeCodeAddition.id], enabled: enabledFilters.includes(publicationTypeCodeAddition.id) }))), tap(filters => this.updateFilterCount("publicationTypeCode", filters.filter(filter => filter.enabled).length)) diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 224f9dd78..242a8c4c9 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -20,6 +20,7 @@ function parsePublicationSearch(data: any): PublicationSearch { } export interface HighlightedPublication { + id: string; publicationName: SafeHtml; authorsText: SafeHtml; authorsTextSplitted: SafeHtml; @@ -188,6 +189,7 @@ export class Publication2Service { }; return { + id: searchData._id, publicationName: this.sanitizer.sanitize(SecurityContext.HTML, values.publicationName), authorsText: this.sanitizer.sanitize(SecurityContext.HTML, values.authorsText), authorsTextSplitted: this.sanitizer.sanitize(SecurityContext.HTML, values.authorsTextSplitted), @@ -499,6 +501,60 @@ export class Publication2Service { shareReplay({ bufferSize: 1, refCount: true }) ); } + + getPublicationTypeCodeNames(): Observable> { + const labels = [ + { value: `A`, name: $localize`@@publicationClassA:Vertaisarvioidut tieteelliset artikkelit` }, + { value: `A1`, name: $localize`@@publicationClassA1:Alkuperäisartikkeli tieteellisessä aikakauslehdessä` }, + { value: `A2`, name: $localize`@@publicationClassA2:Katsausartikkeli tieteellisessä aikakauslehdessä` }, + { value: `A3`, name: $localize`@@publicationClassA3:Kirjan tai muun kokoomateoksen osa` }, + { value: `A4`, name: $localize`@@publicationClassA4:Artikkeli konferenssijulkaisussa` }, + { value: `B`, name: $localize`@@publicationClassB:Vertaisarvioimattomat tieteelliset kirjoitukset` }, + { value: `B1`, name: $localize`@@publicationClassB1:Kirjoitus tieteellisessä aikakausilehdessä` }, + { value: `B2`, name: $localize`@@publicationClassB2:Kirjan tai muun kokoomateoksen osa` }, + { value: `B3`, name: $localize`@@publicationClassB3:Vertaisarvioimaton artikkeli konferenssijulkaisussa` }, + { value: `C`, name: $localize`@@publicationClassC:Tieteelliset kirjat` }, + { value: `C1`, name: $localize`@@publicationClassC1:Kustannettu tieteellinen erillisteos` }, + { value: `C2`, name: $localize`@@publicationClassC2:Toimitettu kirja, kokoomateos, konferenssijulkaisu tai lehden erikoisnumero` }, + { value: `D`, name: $localize`@@publicationClassD:Ammattiyhteisölle suunnatut julkaisut` }, + { value: `D1`, name: $localize`@@publicationClassD1:Artikkeli ammattilehdessä` }, + { value: `D2`, name: $localize`@@publicationClassD2:Artikkeli ammatillisessa kokoomateoksessa` }, + { value: `D3`, name: $localize`@@publicationClassD3:Artikkeli ammatillisessa konferenssijulkaisussa` }, + { value: `D4`, name: $localize`@@publicationClassD4:Julkaistu kehittämis- tai tutkimusraportti taikka -selvitys` }, + { value: `D5`, name: $localize`@@publicationClassD5:Ammatillinen kirja` }, + { value: `D6`, name: $localize`@@publicationClassD6:Toimitettu ammatillinen teos` }, + { value: `E`, name: $localize`@@publicationClassE:Suurelle yleisölle suunnatut julkaisut` }, + { value: `E1`, name: $localize`@@publicationClassE1:Yleistajuinen artikkeli, sanomalehtiartikkeli` }, + { value: `E2`, name: $localize`@@publicationClassE2:Yleistajuinen monografia` }, + { value: `E3`, name: $localize`@@publicationClassE3:Toimitettu yleistajuinen teos` }, + { value: `F`, name: $localize`@@publicationClassF:Julkinen taiteellinen ja taideteollinen toiminta` }, + { value: `F1`, name: $localize`@@publicationClassF1:Itsenäinen teos tai esitys` }, + { value: `F2`, name: $localize`@@publicationClassF2:Taiteellisen teoksen tai esityksen osatoteutus` }, + { value: `F3`, name: $localize`@@publicationClassF3:Ei-taiteellisen julkaisun taiteellinen osa` }, + { value: `G`, name: $localize`@@publicationClassG:Opinnäytteet ` }, + { value: `G1`, name: $localize`@@publicationClassG1:Ammattikorkeakoulututkinnon opinnäytetyö, kandidaatintyö` }, + { value: `G2`, name: $localize`@@publicationClassG2:Pro gradu, diplomityö, ylempi amk-opinnäytetyö` }, + { value: `G3`, name: $localize`@@publicationClassG3:Lisensiaatintyö` }, + { value: `G4`, name: $localize`@@publicationClassG4:Monografiaväitöskirja` }, + { value: `G5`, name: $localize`@@publicationClassG5:Artikkeliväitöskirja` }, + { value: `H`, name: $localize`@@publicationClassH:Patentit ja keksintöilmoitukset` }, + { value: `H1`, name: $localize`@@publicationClassH1:Myönnetty patentti` }, + { value: `H2`, name: $localize`@@publicationClassH2:Keksintöilmoitus` }, + { value: `I`, name: $localize`@@publicationClassI:Audiovisuaaliset julkaisut ja tieto- ja viestintätekniset sovellukset` }, + { value: `I1`, name: $localize`@@publicationClassI1:Audiovisuaaliset julkaisut` }, + { value: `I2`, name: $localize`@@publicationClassI2:Tieto- ja viestintätekniset sovellukset` } + ]; + + // Turn into key value object + const lookup = labels.reduce((acc, label) => { + acc[label.value] = label.name; + return acc; + }); + + return of({ + ...lookup + }); + } } function matchingTerms(searchParams: SearchParams) { @@ -1465,7 +1521,10 @@ export function getParentPublicationTypeAdditions(aggregations: any) { return aggregations.all_data_except_parentPublicationType?.filtered_except_parentPublicationType.all_parentPublicationTypes.buckets ?? []; } -export function getInternationalPublicationAdditions(aggregations: any) { +export function getPublisherInternationalityAdditions(aggregations: any) { + // used to be called "getInternationalPublicationAdditions" + + // TODO rename all the way to with "PublisherInternationality" return aggregations.all_data_except_internationalPublication?.filtered_except_internationalPublication.all_internationalPublications.buckets ?? []; } From 06564b77d1377ff08c741a68d467fef43711f9e8 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Thu, 4 Jan 2024 11:23:54 +0200 Subject: [PATCH 16/47] Add secondary sorting based on publicationYear --- .../results/publications2/publications2.component.ts | 3 ++- src/app/portal/services/publication2.service.ts | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 099fdcfc9..fb09290c9 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -1,7 +1,7 @@ import { Component, inject, OnDestroy, Pipe, PipeTransform } from '@angular/core'; import { CdkTableModule, DataSource } from '@angular/cdk/table'; import { BehaviorSubject, combineLatest, merge, Observable } from 'rxjs'; -import { ActivatedRoute, Router, RouterLink } from '@angular/router'; +import { ActivatedRoute, Router, RouterLink, RouterModule } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { AsyncPipe, JsonPipe, NgForOf, NgIf, NgStyle } from '@angular/common'; import { @@ -37,6 +37,7 @@ import { FirstLetterPipe } from '@shared/pipes/first-letter.pipe'; imports: [CdkTableModule, FormsModule, AsyncPipe, JsonPipe, NgForOf, NgIf, LimitPipe, NgArrayPipesModule, SharedModule, //TODO not good? FormsModule, + RouterModule, SearchBar2Component, OrganizationFilterComponent, FilterOptionComponent, CollapsibleComponent, MatButtonModule, NgStyle, FilterLimitButtonComponent, FirstDigitPipe, FirstLetterPipe, RouterLink ], standalone: true diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 242a8c4c9..718ef0a07 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -119,6 +119,10 @@ export class Publication2Service { from: from, size: size, track_total_hits: true, + sort: [ + "_score", + { "publicationYear": { "order": "desc" } } + ], query: { bool: { must: { From b9809775d3246d611fef38248fe0cfeed2e33537 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Wed, 10 Jan 2024 14:39:12 +0200 Subject: [PATCH 17/47] Add locale module for non-Angular code. Fix placeholders into i18n strings --- angular.json | 12 ++ .../collapsible/collapsible.component.html | 18 ++- .../organization-filter.component.ts | 11 +- .../publications2.component.html | 6 +- .../publications2/publications2.component.ts | 38 ++--- .../portal/services/publication2.service.ts | 138 ++++++++++-------- src/environments/locale.en.ts | 1 + src/environments/locale.sv.ts | 1 + src/environments/locale.ts | 2 + src/i18n/messages.en.xlf | 40 +++++ src/i18n/messages.sv.xlf | 40 +++++ src/i18n/messages.xlf | 38 +++++ 12 files changed, 246 insertions(+), 99 deletions(-) create mode 100644 src/environments/locale.en.ts create mode 100644 src/environments/locale.sv.ts create mode 100644 src/environments/locale.ts diff --git a/angular.json b/angular.json index 10a7014ca..8795a6481 100644 --- a/angular.json +++ b/angular.json @@ -92,11 +92,23 @@ "en": { "localize": [ "en" + ], + "fileReplacements": [ + { + "replace": "src/environments/locale.ts", + "with": "src/environments/locale.en.ts" + } ] }, "sv": { "localize": [ "sv" + ], + "fileReplacements": [ + { + "replace": "src/environments/locale.ts", + "with": "src/environments/locale.sv.ts" + } ] }, "es5": { diff --git a/src/app/portal/components/collapsible/collapsible.component.html b/src/app/portal/components/collapsible/collapsible.component.html index 2f03b52f3..449cf2a1a 100644 --- a/src/app/portal/components/collapsible/collapsible.component.html +++ b/src/app/portal/components/collapsible/collapsible.component.html @@ -6,7 +6,7 @@
-
+
-
@@ -15,17 +15,19 @@
+
-
{{label}}
+
+ {{label}} - -
- -
-
+ +
+ +
+
+
- +
diff --git a/src/app/portal/components/organization-filter/organization-filter.component.ts b/src/app/portal/components/organization-filter/organization-filter.component.ts index e28ff7b90..fa735d5d8 100644 --- a/src/app/portal/components/organization-filter/organization-filter.component.ts +++ b/src/app/portal/components/organization-filter/organization-filter.component.ts @@ -41,12 +41,13 @@ export class OrganizationFilterComponent { 6: 10, }; + /* TODO localization solution */ // TODO ? public sectorNames = { - 1: "Yliopisto", - 2: "Ammattikorkeakoulu", - 3: "Tutkimuslaitos", - 4: "Yliopistollisen sairaalan erityisvastuualue", - 6: "Muu" + 1: $localize`:@@organizationSector1:Yliopisto`, + 2: $localize`:@@organizationSector2:Ammattikorkeakoulu`, + 3: $localize`:@@organizationSector3:Tutkimuslaitos`, + 4: $localize`:@@organizationSector4:Yliopistollisen sairaalan erityisvastuualue`, + 6: $localize`:@@organizationSector5:Muu` } toggleExpanded() { diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index 31703c6c5..cf8504341 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -121,12 +121,12 @@

Rajaa hakua

OKM:n julkaisutiedonkeruun mukainen julkaisutyyppi A–G. - + - + - + = {}; @@ -300,27 +312,15 @@ export class Publications2Component implements OnDestroy { tap(filters => this.updateFilterCount("parentPublicationType", filters.filter(filter => filter.enabled).length)) ); - - - /* TODO localization solution */ - public sectorName = { - 1: "Yliopisto", - 2: "Ammattikorkeakoulu", - 3: "Tutkimuslaitos", - 4: "Yliopistollisen sairaalan erityisvastuualue", - 6: "Muu" - }; - - /* TODO localization solution */ public mainFieldOfScienceName = { - "1": "Luonnontieteet", - "2": "Tekniikka", - "3": "Lääke- ja terveystieteet", - "4": "Maatalous- ja metsätieteet", - "5": "Yhteiskuntatieteet", - "6": "Humanistiset tieteet", + "1": $localize`:@@naturalSciences:Luonnontieteet`, + "2": $localize`:@@engineeringTecnology:Tekniikka`, + "3": $localize`:@@medicalHealthSciences:Lääke- ja terveystieteet`, + "4": $localize`:@@agriculturalSciences:Maatalous- ja metsätieteet`, + "5": $localize`:@@socialSciences:Yhteiskuntatieteet`, + "6": $localize`:@@humanities:Humanistiset tieteet`, // 7 not used - "8": "Taiteenala", + "8": $localize`:@@fieldsOfArt:Taiteenala`, } /*public collapseStates = { diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 718ef0a07..29577bd08 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -6,6 +6,11 @@ import { map, switchMap, take, tap } from 'rxjs/operators'; import { ActivatedRoute } from '@angular/router'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { locale } from "../../../environments/locale"; +const path = suffixer(locale); + +console.log("LOOK HERE:", path`fieldsOfScience.nameFiScience.keyword`); + /*const PublicationSearchSchema = object({ publicationId: string(), title: string(), @@ -66,7 +71,9 @@ export class Publication2Service { sanitizer = inject(DomSanitizer); locale = inject(LOCALE_ID); - path = suffixer(this.locale); + // path = suffixer(this.locale); + + searchUrl = 'https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?' // organizationNames$ = this.getOrganizationNames(); @@ -115,7 +122,7 @@ export class Publication2Service { searchParams = searchParams as SearchParams; // TODO no effect? - return this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', { + return this.http.post(this.searchUrl, { from: from, size: size, track_total_hits: true, @@ -228,7 +235,7 @@ export class Publication2Service { 'size': 65536, 'sources': [ { 'id': { 'terms': { 'field': 'author.organization.organizationId.keyword' } } }, - { 'name': { 'terms': { 'field': 'author.organization.OrganizationNameFi.keyword' } } } // TODO path template needed + { 'name': { 'terms': { 'field': path`author.organization.OrganizationNameFi.keyword` } } } // TODO path template needed ] } } @@ -244,7 +251,7 @@ export class Publication2Service { const sectorIds = [1, 2, 3, 4, /*5,*/ 6]; const responses$ = sectorIds.map((sectorId) => { - return this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', organizationNamesBySector(sectorId)).pipe( + return this.http.post(this.searchUrl, organizationNamesBySector(sectorId)).pipe( map((res) => getOrganizationNameBuckets(res).map((bucket) => [bucket.key.id, {name: bucket.key.name, sectorId}])), ); }); @@ -271,7 +278,7 @@ export class Publication2Service { 'size': 1000, 'sources': [ { 'id': { 'terms': { 'field': 'languages.languageCode.keyword' } } }, - { 'nameFiLanguage': { 'terms': { 'field': 'languages.languageFi.keyword' } } } + { 'nameFiLanguage': { 'terms': { 'field': path`languages.languageFi.keyword` } } } // TODO: does the key need to be localized? // TODO remove "fi" ? ] } } @@ -292,7 +299,7 @@ export class Publication2Service { }; }; - const response$ = this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', body); + const response$ = this.http.post(this.searchUrl, body); return response$.pipe( map((res) => res.aggregations.composite_pairs.buckets.map((bucket) => [bucket.key.id, bucket.key.nameFiLanguage])), // TODO localized path needed @@ -310,7 +317,7 @@ export class Publication2Service { 'size': 1000, 'sources': [ { 'id': { 'terms': { 'field': 'publicationFormat.id.keyword' } } }, - { 'nameFiFormat': { 'terms': { 'field': 'publicationFormat.nameFiPublicationFormat.keyword' } } } + { 'nameFiFormat': { 'terms': { 'field': path`publicationFormat.nameFiPublicationFormat.keyword` } } } // TODO Does the key need to be localized? // TODO remove "fi" ? ] } } @@ -331,7 +338,7 @@ export class Publication2Service { }; }; - const response$ = this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', body); + const response$ = this.http.post(this.searchUrl, body); return response$.pipe( map((res) => res.aggregations.composite_pairs.buckets.map((bucket) => [bucket.key.id, bucket.key.nameFiFormat])), // TODO localized path needed @@ -349,7 +356,7 @@ export class Publication2Service { 'size': 1000, 'sources': [ { 'id': { 'terms': { 'field': 'publicationAudience.id.keyword' } } }, - { 'nameFiAudience': { 'terms': { 'field': 'publicationAudience.nameFiPublicationAudience.keyword' } } } + { 'nameFiAudience': { 'terms': { 'field': path`publicationAudience.nameFiPublicationAudience.keyword` } } } // TODO Does the key need to be localized? // TODO remove "fi" ? ] } } @@ -370,7 +377,7 @@ export class Publication2Service { }; }; - const response$ = this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', body); + const response$ = this.http.post(this.searchUrl, body); return response$.pipe( map((res) => res.aggregations.composite_pairs.buckets.map((bucket) => [bucket.key.id, bucket.key.nameFiAudience])), // TODO localized path needed @@ -393,7 +400,7 @@ export class Publication2Service { "size": 1000, "sources": [ { "id": { "terms": { "field": "parentPublicationType.id.keyword" } } }, - { "nameFiParentPublicationType": { "terms": { "field": "parentPublicationType.nameFiParentPublicationType.keyword" } } } + { "nameFiParentPublicationType": { "terms": { "field": path`parentPublicationType.nameFiParentPublicationType.keyword` } } } // TODO Does the key need to be localized? // TODO remove "fi" ? ] } } @@ -414,7 +421,7 @@ export class Publication2Service { }; }; - const response$ = this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', body); + const response$ = this.http.post(this.searchUrl, body); return response$.pipe( map((res) => res.aggregations.composite_pairs.buckets.map((bucket) => [bucket.key.id, bucket.key.nameFiParentPublicationType])), // TODO localized path needed @@ -425,24 +432,24 @@ export class Publication2Service { getPeerReviewedNames(): Observable> { return of({ - "0": "Ei-vertaisarvioitu", // TODO use localize`` - "1": "Vertaisarvioitu" // TODO use localize`` + "0": $localize`:@@notPeerReviewed:Ei-vertaisarvioitu`, // TODO does not exist in localization file + "1": $localize`:@@peerReviewed:Vertaisarvioitu` }); } getInternationalPublicationNames(): Observable> { return of({ - "0": "Kotimainen julkaisu", // TODO use localize`` - "1": "Kansainvälinen julkaisu" // TODO use localize`` + "0": $localize`:@@domesticPublication:Kotimainen julkaisu`, + "1": $localize`:@@internationalPublication:Kansainvälinen julkaisu` }); } getArticleTypeCodeNames(): Observable> { return of({ - "0": "Lehti", // TODO use localize`` - "1": "Kokoomateos", // TODO use localize`` - "2": "Konferenssi", // TODO use localize`` - "3": "Verkkoalusta" // TODO use localize`` + "0": $localize`:@@journal:Lehti`, + "1": $localize`:@@researchBook:Kokoomateos`, + "2": $localize`:@@Conference:Konferenssi`, + "3": $localize`:@@onlinePlatform:Verkkoalusta` }); } @@ -469,7 +476,7 @@ export class Publication2Service { { "name": { "terms": { - "field": "fieldsOfScience.nameFiScience.keyword" // TODO localized path needed + 'field': path`fieldsOfScience.nameFiScience.keyword` // TODO localized path needed } } } @@ -497,7 +504,7 @@ export class Publication2Service { }; }; - const response$ = this.http.post('https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?', body); + const response$ = this.http.post(this.searchUrl, body); return response$.pipe( map((res) => res.aggregations.fieldsOfScience_nested.composite_field_of_science.buckets.map((bucket) => [bucket.key.id, bucket.key.name])), @@ -508,45 +515,45 @@ export class Publication2Service { getPublicationTypeCodeNames(): Observable> { const labels = [ - { value: `A`, name: $localize`@@publicationClassA:Vertaisarvioidut tieteelliset artikkelit` }, - { value: `A1`, name: $localize`@@publicationClassA1:Alkuperäisartikkeli tieteellisessä aikakauslehdessä` }, - { value: `A2`, name: $localize`@@publicationClassA2:Katsausartikkeli tieteellisessä aikakauslehdessä` }, - { value: `A3`, name: $localize`@@publicationClassA3:Kirjan tai muun kokoomateoksen osa` }, - { value: `A4`, name: $localize`@@publicationClassA4:Artikkeli konferenssijulkaisussa` }, - { value: `B`, name: $localize`@@publicationClassB:Vertaisarvioimattomat tieteelliset kirjoitukset` }, - { value: `B1`, name: $localize`@@publicationClassB1:Kirjoitus tieteellisessä aikakausilehdessä` }, - { value: `B2`, name: $localize`@@publicationClassB2:Kirjan tai muun kokoomateoksen osa` }, - { value: `B3`, name: $localize`@@publicationClassB3:Vertaisarvioimaton artikkeli konferenssijulkaisussa` }, - { value: `C`, name: $localize`@@publicationClassC:Tieteelliset kirjat` }, - { value: `C1`, name: $localize`@@publicationClassC1:Kustannettu tieteellinen erillisteos` }, - { value: `C2`, name: $localize`@@publicationClassC2:Toimitettu kirja, kokoomateos, konferenssijulkaisu tai lehden erikoisnumero` }, - { value: `D`, name: $localize`@@publicationClassD:Ammattiyhteisölle suunnatut julkaisut` }, - { value: `D1`, name: $localize`@@publicationClassD1:Artikkeli ammattilehdessä` }, - { value: `D2`, name: $localize`@@publicationClassD2:Artikkeli ammatillisessa kokoomateoksessa` }, - { value: `D3`, name: $localize`@@publicationClassD3:Artikkeli ammatillisessa konferenssijulkaisussa` }, - { value: `D4`, name: $localize`@@publicationClassD4:Julkaistu kehittämis- tai tutkimusraportti taikka -selvitys` }, - { value: `D5`, name: $localize`@@publicationClassD5:Ammatillinen kirja` }, - { value: `D6`, name: $localize`@@publicationClassD6:Toimitettu ammatillinen teos` }, - { value: `E`, name: $localize`@@publicationClassE:Suurelle yleisölle suunnatut julkaisut` }, - { value: `E1`, name: $localize`@@publicationClassE1:Yleistajuinen artikkeli, sanomalehtiartikkeli` }, - { value: `E2`, name: $localize`@@publicationClassE2:Yleistajuinen monografia` }, - { value: `E3`, name: $localize`@@publicationClassE3:Toimitettu yleistajuinen teos` }, - { value: `F`, name: $localize`@@publicationClassF:Julkinen taiteellinen ja taideteollinen toiminta` }, - { value: `F1`, name: $localize`@@publicationClassF1:Itsenäinen teos tai esitys` }, - { value: `F2`, name: $localize`@@publicationClassF2:Taiteellisen teoksen tai esityksen osatoteutus` }, - { value: `F3`, name: $localize`@@publicationClassF3:Ei-taiteellisen julkaisun taiteellinen osa` }, - { value: `G`, name: $localize`@@publicationClassG:Opinnäytteet ` }, - { value: `G1`, name: $localize`@@publicationClassG1:Ammattikorkeakoulututkinnon opinnäytetyö, kandidaatintyö` }, - { value: `G2`, name: $localize`@@publicationClassG2:Pro gradu, diplomityö, ylempi amk-opinnäytetyö` }, - { value: `G3`, name: $localize`@@publicationClassG3:Lisensiaatintyö` }, - { value: `G4`, name: $localize`@@publicationClassG4:Monografiaväitöskirja` }, - { value: `G5`, name: $localize`@@publicationClassG5:Artikkeliväitöskirja` }, - { value: `H`, name: $localize`@@publicationClassH:Patentit ja keksintöilmoitukset` }, - { value: `H1`, name: $localize`@@publicationClassH1:Myönnetty patentti` }, - { value: `H2`, name: $localize`@@publicationClassH2:Keksintöilmoitus` }, - { value: `I`, name: $localize`@@publicationClassI:Audiovisuaaliset julkaisut ja tieto- ja viestintätekniset sovellukset` }, - { value: `I1`, name: $localize`@@publicationClassI1:Audiovisuaaliset julkaisut` }, - { value: `I2`, name: $localize`@@publicationClassI2:Tieto- ja viestintätekniset sovellukset` } + { value: `A`, name: $localize`:@@publicationClassA:Vertaisarvioidut tieteelliset artikkelit` }, + { value: `A1`, name: $localize`:@@publicationClassA1:Alkuperäisartikkeli tieteellisessä aikakauslehdessä` }, + { value: `A2`, name: $localize`:@@publicationClassA2:Katsausartikkeli tieteellisessä aikakauslehdessä` }, + { value: `A3`, name: $localize`:@@publicationClassA3:Kirjan tai muun kokoomateoksen osa` }, + { value: `A4`, name: $localize`:@@publicationClassA4:Artikkeli konferenssijulkaisussa` }, + { value: `B`, name: $localize`:@@publicationClassB:Vertaisarvioimattomat tieteelliset kirjoitukset` }, + { value: `B1`, name: $localize`:@@publicationClassB1:Kirjoitus tieteellisessä aikakausilehdessä` }, + { value: `B2`, name: $localize`:@@publicationClassB2:Kirjan tai muun kokoomateoksen osa` }, + { value: `B3`, name: $localize`:@@publicationClassB3:Vertaisarvioimaton artikkeli konferenssijulkaisussa` }, + { value: `C`, name: $localize`:@@publicationClassC:Tieteelliset kirjat` }, + { value: `C1`, name: $localize`:@@publicationClassC1:Kustannettu tieteellinen erillisteos` }, + { value: `C2`, name: $localize`:@@publicationClassC2:Toimitettu kirja, kokoomateos, konferenssijulkaisu tai lehden erikoisnumero` }, + { value: `D`, name: $localize`:@@publicationClassD:Ammattiyhteisölle suunnatut julkaisut` }, + { value: `D1`, name: $localize`:@@publicationClassD1:Artikkeli ammattilehdessä` }, + { value: `D2`, name: $localize`:@@publicationClassD2:Artikkeli ammatillisessa kokoomateoksessa` }, + { value: `D3`, name: $localize`:@@publicationClassD3:Artikkeli ammatillisessa konferenssijulkaisussa` }, + { value: `D4`, name: $localize`:@@publicationClassD4:Julkaistu kehittämis- tai tutkimusraportti taikka -selvitys` }, + { value: `D5`, name: $localize`:@@publicationClassD5:Ammatillinen kirja` }, + { value: `D6`, name: $localize`:@@publicationClassD6:Toimitettu ammatillinen teos` }, + { value: `E`, name: $localize`:@@publicationClassE:Suurelle yleisölle suunnatut julkaisut` }, + { value: `E1`, name: $localize`:@@publicationClassE1:Yleistajuinen artikkeli, sanomalehtiartikkeli` }, + { value: `E2`, name: $localize`:@@publicationClassE2:Yleistajuinen monografia` }, + { value: `E3`, name: $localize`:@@publicationClassE3:Toimitettu yleistajuinen teos` }, + { value: `F`, name: $localize`:@@publicationClassF:Julkinen taiteellinen ja taideteollinen toiminta` }, + { value: `F1`, name: $localize`:@@publicationClassF1:Itsenäinen teos tai esitys` }, + { value: `F2`, name: $localize`:@@publicationClassF2:Taiteellisen teoksen tai esityksen osatoteutus` }, + { value: `F3`, name: $localize`:@@publicationClassF3:Ei-taiteellisen julkaisun taiteellinen osa` }, + { value: `G`, name: $localize`:@@publicationClassG:Opinnäytteet` }, + { value: `G1`, name: $localize`:@@publicationClassG1:Ammattikorkeakoulututkinnon opinnäytetyö, kandidaatintyö` }, + { value: `G2`, name: $localize`:@@publicationClassG2:Pro gradu, diplomityö, ylempi amk-opinnäytetyö` }, + { value: `G3`, name: $localize`:@@publicationClassG3:Lisensiaatintyö` }, + { value: `G4`, name: $localize`:@@publicationClassG4:Monografiaväitöskirja` }, + { value: `G5`, name: $localize`:@@publicationClassG5:Artikkeliväitöskirja` }, + { value: `H`, name: $localize`:@@publicationClassH:Patentit ja keksintöilmoitukset` }, + { value: `H1`, name: $localize`:@@publicationClassH1:Myönnetty patentti` }, + { value: `H2`, name: $localize`:@@publicationClassH2:Keksintöilmoitus` }, + { value: `I`, name: $localize`:@@publicationClassI:Audiovisuaaliset julkaisut ja tieto- ja viestintätekniset sovellukset` }, + { value: `I1`, name: $localize`:@@publicationClassI1:Audiovisuaaliset julkaisut` }, + { value: `I2`, name: $localize`:@@publicationClassI2:Tieto- ja viestintätekniset sovellukset` } ]; // Turn into key value object @@ -1254,10 +1261,13 @@ type OrganizationAggregation = { function suffixer(locale) { const capitalized = locale.charAt(0).toUpperCase() + locale.slice(1).toLowerCase(); - return strings => strings[0].replace(/(Fi|Sv|En)(?=\.|$)/g, capitalized); -} -const path = suffixer("fi"); + // replace Fi with capitalized from middle or end of the string + // return strings => strings[0].replace(/(Fi|Sv|En)(?=\.|$)/g, capitalized); + + // replace Fi with capitalized if the following character is a dot or a capital letter or end of string + return strings => strings[0].replace(/(Fi|Sv|En)(?=[\.A-Z]|$)/g, capitalized); +} // NOTE: nested is needed for array type mapping function termsForOrganization(searchParams: SearchParams) { diff --git a/src/environments/locale.en.ts b/src/environments/locale.en.ts new file mode 100644 index 000000000..a1f375fae --- /dev/null +++ b/src/environments/locale.en.ts @@ -0,0 +1 @@ +export const locale = "en"; diff --git a/src/environments/locale.sv.ts b/src/environments/locale.sv.ts new file mode 100644 index 000000000..8d8ad389c --- /dev/null +++ b/src/environments/locale.sv.ts @@ -0,0 +1 @@ +export const locale = "sv"; diff --git a/src/environments/locale.ts b/src/environments/locale.ts new file mode 100644 index 000000000..bd6bfcf3c --- /dev/null +++ b/src/environments/locale.ts @@ -0,0 +1,2 @@ +// Intended to be used outside of Angular's Dependency Injection system. +export const locale = "fi"; diff --git a/src/i18n/messages.en.xlf b/src/i18n/messages.en.xlf index b1ee06746..ab047b703 100644 --- a/src/i18n/messages.en.xlf +++ b/src/i18n/messages.en.xlf @@ -570,6 +570,10 @@ 50 + + Ei-vertaisarvioitu + Non Peer-Reviewed + Avoin saatavuus Open access @@ -6475,6 +6479,42 @@ Tieto- ja viestintätekniset sovellukset ICT applications + + + Yliopisto + University + + + + Ammattikorkeakoulu + University of Applied Sciences + + + + Tutkimuslaitos + Research institute + + + + Yliopistollisen sairaalan erityisvastuualue + University Hospital + + + + Muu + Other + + + + Kotimainen + Domestic + + + + Kansainvälinen + International + + diff --git a/src/i18n/messages.sv.xlf b/src/i18n/messages.sv.xlf index 3f3370ab1..d4c1114d8 100644 --- a/src/i18n/messages.sv.xlf +++ b/src/i18n/messages.sv.xlf @@ -564,6 +564,10 @@ 50 + + Ei-vertaisarvioitu + Inte kollegialt utvärderad + Avoin saatavuus Öppen tillgång @@ -6479,6 +6483,42 @@ uppgifter till datalagret för forskningsinformation. De gemensamma personuppgif Tieto- ja viestintätekniset sovellukset Informations- och kommunikationstekniktillämpningar + + + Yliopisto + Universitet + + + + Ammattikorkeakoulu + Yrkeshögskola + + + + Tutkimuslaitos + Forskningsinstitut + + + + Yliopistollisen sairaalan erityisvastuualue + Universitetssjukhuset + + + + Muu + Övrig + + + + Kotimainen + Inhemsk + + + + Kansainvälinen + Internationell + + diff --git a/src/i18n/messages.xlf b/src/i18n/messages.xlf index 23ce6c10c..16b43819b 100644 --- a/src/i18n/messages.xlf +++ b/src/i18n/messages.xlf @@ -394,6 +394,9 @@ 50 + + Ei-vertaisarvioitu + Avoin saatavuus @@ -1850,6 +1853,12 @@ Muut tieteet + + Taidealat + + + Taiteenalat + Vertaisarvioidut tieteelliset artikkelit @@ -2514,6 +2523,35 @@ Tieto- ja viestintätekniset sovellukset + + + Yliopisto + + + + Ammattikorkeakoulu + + + + Tutkimuslaitos + + + + Yliopistollisen sairaalan erityisvastuualue + + + + Muu + + + + Kotimainen + + + + Kansainvälinen + + From 413771b8f97bcd48e662fe3193edc539bd59be9b Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Wed, 17 Jan 2024 11:32:15 +0200 Subject: [PATCH 18/47] Adjust the presentation of side panel filters --- .../filter-option/filter-option.component.html | 17 +++++++++++------ .../filter-option/filter-option.component.scss | 6 +++++- .../filter-option/filter-option.component.ts | 4 +++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/app/portal/components/filter-option/filter-option.component.html b/src/app/portal/components/filter-option/filter-option.component.html index d7c087ad4..5ba7644a1 100644 --- a/src/app/portal/components/filter-option/filter-option.component.html +++ b/src/app/portal/components/filter-option/filter-option.component.html @@ -1,15 +1,20 @@ -
-
- +
+
+ + +
+ +
+
-
+
{{label}}
-
- ({{count}}) +
+ {{count}}
diff --git a/src/app/portal/components/filter-option/filter-option.component.scss b/src/app/portal/components/filter-option/filter-option.component.scss index 66d046b6f..0fcbfe167 100644 --- a/src/app/portal/components/filter-option/filter-option.component.scss +++ b/src/app/portal/components/filter-option/filter-option.component.scss @@ -5,8 +5,12 @@ .filter-option-layout { display: flex; + justify-content: center; align-items: center; - padding: 16px 12px; + + // padding: 16px 12px; + padding: 8px; + cursor: pointer; user-select: none; diff --git a/src/app/portal/components/filter-option/filter-option.component.ts b/src/app/portal/components/filter-option/filter-option.component.ts index 69fe8d1e8..b9d37ef15 100644 --- a/src/app/portal/components/filter-option/filter-option.component.ts +++ b/src/app/portal/components/filter-option/filter-option.component.ts @@ -1,6 +1,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatRippleModule } from '@angular/material/core'; +import { MatCheckboxModule } from '@angular/material/checkbox'; @Component({ selector: 'app-filter-option', @@ -8,7 +9,8 @@ import { MatRippleModule } from '@angular/material/core'; styleUrls: ['./filter-option.component.scss'], imports: [ FormsModule, - MatRippleModule + MatRippleModule, + MatCheckboxModule ], standalone: true }) From 892ffd234d540bb9ecd14e496f86022338f24e55 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Wed, 17 Jan 2024 16:50:51 +0200 Subject: [PATCH 19/47] Create functions repeated search terms and add openAccess as filter --- .../publications2.component.html | 48 +++ .../publications2/publications2.component.ts | 22 +- .../portal/services/publication2.service.ts | 387 +++++++----------- 3 files changed, 211 insertions(+), 246 deletions(-) diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index cf8504341..6a93347b6 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -394,6 +394,54 @@

Rajaa hakua

+ + +
+ +
+ + +

+ Open access -julkaisukanava: + Julkaisu on ilmestynyt julkaisukanavassa, jonka kaikki julkaisut ovat avoimesti saatavilla. +

+ +

+ Viivästetty avoin saatavuus: + Julkaisukanavan tieteelliset artikkelit avataan kustantajan määrittelemän viiveajan jälkeen. Sisältää sekä jo avoimesti saatavilla että yhä maksumuurin takana olevia julkaisuja. +

+ +

+ Rinnakkaistallennettu: + Julkaisu on tallennettu organisaatio- tai tieteenalakohtaiseen julkaisuarkistoon joko välittömästi tai kustantajan määrittämän kohtuullisen embargoajan jälkeen. +

+ +

+ Muu avoin saatavuus: + Julkaisu on avoimesti saatavilla, mutta se on ilmestynyt ns. hybridijulkaisukanavassa, jossa kaikki muut julkaisut eivät ole avoimesti saatavilla. +

+ +

+ Ei avoin: + Julkaisu ei ole ilmestynyt avoimessa julkaisukanavassa, eikä julkaisua ole saatavilla avoimena rinnakkaistallenteena. Huom. myös julkaisut jotka ovat tilapäisesti avoimesti saatavilla, esimerkiksi ajankohtaisen yhteiskunnallisesti merkittävän aiheen tai kustantajan markkinointikampanjan takia, tulevat näkyville tämän kategorian kautta. +

+ +

+ Ei tietoa: + Julkaisun avoimen saatavuuden tilaa ei ole raportoitu. +

+
+ + + + + +
+
+
diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 44b2e767e..506f59ba8 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -15,7 +15,7 @@ import { getPublicationFormatAdditions, getPublicationTypeCodeAdditions, getYearAdditions, HighlightedPublication, - Publication2Service + Publication2Service, getOpenAccessAdditions } from '@portal/services/publication2.service'; import { map, take, tap } from 'rxjs/operators'; import { SharedModule } from '@shared/shared.module'; @@ -64,6 +64,7 @@ export class Publications2Component implements OnDestroy { publisherInternationality: $localize`:@@publisherInternationality:Kustantajan kansainvälisyys`, language: $localize`:@@language:Kieli`, jufoLevel: $localize`:@@jufoLevel:Julkaisufoorumitaso`, + openAccess: $localize`:@@openAccess:Avoin saatavuus`, } publicationTypeLabels = [ @@ -312,6 +313,25 @@ export class Publications2Component implements OnDestroy { tap(filters => this.updateFilterCount("parentPublicationType", filters.filter(filter => filter.enabled).length)) ); + // TODO: OPEN ACCESS FILTER + + additionsFromOpenAccess$ = this.aggregations$.pipe( + map(aggs => getOpenAccessAdditions(aggs).map((bucket: any) => ({ id: bucket.key.toString(), count: bucket.doc_count })) ?? []), + map(aggs => aggs.sort((a, b) => b.count - a.count)) + ); + + openAccessFilters$ = combineLatest([this.additionsFromOpenAccess$, this.searchParams$.pipe(map(params => params.openAccess ?? []))]).pipe( + map(([additionsFromOpenAccess, enabledFilters]) => additionsFromOpenAccess.map(additionFromOpenAccess => ({ + id: additionFromOpenAccess.id, + count: additionFromOpenAccess.count, + name: additionFromOpenAccess.id, // TODO TODO TODO TODO + enabled: enabledFilters.includes(additionFromOpenAccess.id) + }))), + tap(filters => this.updateFilterCount("openAccess", filters.filter(filter => filter.enabled).length)) + ); + + + public mainFieldOfScienceName = { "1": $localize`:@@naturalSciences:Luonnontieteet`, "2": $localize`:@@engineeringTecnology:Tekniikka`, diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 29577bd08..70ec81140 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -61,6 +61,10 @@ type SearchParams = { publicationStatusCode?: string[], fieldsOfScience?: string[], + + openAccess?: string[], + publisherOpenAccessCode?: string[], + selfArchivedCode?: string[], } @Injectable({ @@ -138,20 +142,7 @@ export class Publication2Service { filter: { bool: { must: [ - ...termsForYear(searchParams), - ...termsForTypeCode(searchParams), - ...termsForStatusCode(searchParams), - ...termsForOrganization(searchParams), - ...termsForLanguageCode(searchParams), - ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams), - ...termsForPeerReviewed(searchParams), - ...termsForParentPublicationType(searchParams), - ...termsForInternationalPublication(searchParams), - ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams), - ...termsForFieldsOfScience(searchParams), - ...termsForPublicationTypeCode(searchParams), + ...filteringTerms(searchParams), ] } } @@ -168,20 +159,7 @@ export class Publication2Service { post_tags: [""] }, aggregations: { - ...additionsFromYear(searchParams), - ...additionsFromTypeCode(searchParams), - ...additionsFromStatusCode(searchParams), - ...additionsFromOrganization(searchParams), - ...additionsFromLanguageCode(searchParams), - ...additionsFromPublicationFormat(searchParams), - ...additionsFromPublicationAudience(searchParams), - ...additionsFromPeerReviewed(searchParams), - ...additionsFromParentPublicationType(searchParams), - ...additionsFromInternationalPublication(searchParams), - ...additionsFromArticleTypeCode(searchParams), - ...additionsFromJufoClassCode(searchParams), - ...additionsFromFieldsOfScience(searchParams), - ...additionsFromPublicationTypeCode(searchParams), + ... aggregationTerms(searchParams), } }); } @@ -755,20 +733,7 @@ function additionsFromYear(searchParams: SearchParams) { "filter": { "bool": { "must": [ - matchingTerms(searchParams), - ...termsForTypeCode(searchParams), - ...termsForOrganization(searchParams), - ...termsForStatusCode(searchParams), - ...termsForLanguageCode(searchParams), - ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams), - ...termsForPeerReviewed(searchParams), - ...termsForParentPublicationType(searchParams), - ...termsForInternationalPublication(searchParams), - ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams), - ...termsForFieldsOfScience(searchParams), - ...termsForPublicationTypeCode(searchParams), + ...additionFilterTerms("year", searchParams), ] } }, @@ -808,20 +773,7 @@ function additionsFromTypeCode(searchParams: SearchParams) { "filter": { "bool": { "must": [ - matchingTerms(searchParams), - ...termsForYear(searchParams), - ...termsForStatusCode(searchParams), - ...termsForOrganization(searchParams), - ...termsForLanguageCode(searchParams), - ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams), - ...termsForPeerReviewed(searchParams), - ...termsForParentPublicationType(searchParams), - ...termsForInternationalPublication(searchParams), - ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams), - ...termsForFieldsOfScience(searchParams), - ...termsForPublicationTypeCode(searchParams), + ...additionFilterTerms("publicationTypeCode", searchParams), ] } }, @@ -847,20 +799,7 @@ function additionsFromStatusCode(searchParams: SearchParams) { "filter": { "bool": { "must": [ - matchingTerms(searchParams), - ...termsForYear(searchParams), - ...termsForTypeCode(searchParams), - ...termsForOrganization(searchParams), - ...termsForLanguageCode(searchParams), - ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams), - ...termsForPeerReviewed(searchParams), - ...termsForParentPublicationType(searchParams), - ...termsForInternationalPublication(searchParams), - ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams), - ...termsForFieldsOfScience(searchParams), - ...termsForPublicationTypeCode(searchParams), + ...additionFilterTerms("statusCode", searchParams), ] } }, @@ -886,20 +825,7 @@ function additionsFromLanguageCode(searchParams: SearchParams) { "filter": { "bool": { "must": [ - matchingTerms(searchParams), - ...termsForYear(searchParams), - ...termsForTypeCode(searchParams), - ...termsForStatusCode(searchParams), - ...termsForOrganization(searchParams), - ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams), - ...termsForPeerReviewed(searchParams), - ...termsForParentPublicationType(searchParams), - ...termsForInternationalPublication(searchParams), - ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams), - ...termsForFieldsOfScience(searchParams), - ...termsForPublicationTypeCode(searchParams), + ...additionFilterTerms("language", searchParams), ] } }, @@ -926,20 +852,7 @@ function additionsFromPublicationFormat(searchParams: SearchParams) { "filter": { "bool": { "must": [ - matchingTerms(searchParams), - ...termsForYear(searchParams), - ...termsForTypeCode(searchParams), - ...termsForStatusCode(searchParams), - ...termsForOrganization(searchParams), - ...termsForLanguageCode(searchParams), - ...termsForPublicationAudience(searchParams), - ...termsForPeerReviewed(searchParams), - ...termsForParentPublicationType(searchParams), - ...termsForInternationalPublication(searchParams), - ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams), - ...termsForFieldsOfScience(searchParams), - ...termsForPublicationTypeCode(searchParams), + ...additionFilterTerms("publicationFormat", searchParams), ] } }, @@ -966,20 +879,7 @@ function additionsFromPublicationAudience(searchParams: SearchParams) { "filter": { "bool": { "must": [ - matchingTerms(searchParams), - ...termsForYear(searchParams), - ...termsForTypeCode(searchParams), - ...termsForStatusCode(searchParams), - ...termsForOrganization(searchParams), - ...termsForLanguageCode(searchParams), - ...termsForPublicationFormat(searchParams), - ...termsForPeerReviewed(searchParams), - ...termsForParentPublicationType(searchParams), - ...termsForInternationalPublication(searchParams), - ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams), - ...termsForFieldsOfScience(searchParams), - ...termsForPublicationTypeCode(searchParams), + ...additionFilterTerms("publicationAudience", searchParams), ] } }, @@ -1005,20 +905,7 @@ function additionsFromPeerReviewed(searchParams: SearchParams) { "filter": { "bool": { "must": [ - matchingTerms(searchParams), - ...termsForYear(searchParams), - ...termsForTypeCode(searchParams), - ...termsForStatusCode(searchParams), - ...termsForOrganization(searchParams), - ...termsForLanguageCode(searchParams), - ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams), - ...termsForParentPublicationType(searchParams), - ...termsForInternationalPublication(searchParams), - ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams), - ...termsForFieldsOfScience(searchParams), - ...termsForPublicationTypeCode(searchParams), + ...additionFilterTerms("peerReviewed", searchParams), ] } }, @@ -1045,20 +932,7 @@ function additionsFromParentPublicationType(searchParams: SearchParams) { "filter": { "bool": { "must": [ - matchingTerms(searchParams), - ...termsForYear(searchParams), - ...termsForTypeCode(searchParams), - ...termsForStatusCode(searchParams), - ...termsForOrganization(searchParams), - ...termsForLanguageCode(searchParams), - ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams), - ...termsForPeerReviewed(searchParams), - ...termsForInternationalPublication(searchParams), - ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams), - ...termsForFieldsOfScience(searchParams), - ...termsForPublicationTypeCode(searchParams), + ...additionFilterTerms("parentPublicationType", searchParams), ] } }, @@ -1085,20 +959,7 @@ function additionsFromInternationalPublication(searchParams: SearchParams) { "filter": { "bool": { "must": [ - matchingTerms(searchParams), - ...termsForYear(searchParams), - ...termsForTypeCode(searchParams), - ...termsForStatusCode(searchParams), - ...termsForOrganization(searchParams), - ...termsForLanguageCode(searchParams), - ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams), - ...termsForPeerReviewed(searchParams), - ...termsForParentPublicationType(searchParams), - ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams), - ...termsForFieldsOfScience(searchParams), - ...termsForPublicationTypeCode(searchParams), + ...additionFilterTerms("internationalPublication", searchParams), ] } }, @@ -1125,20 +986,7 @@ return { "filter": { "bool": { "must": [ - matchingTerms(searchParams), - ...termsForYear(searchParams), - ...termsForTypeCode(searchParams), - ...termsForStatusCode(searchParams), - ...termsForOrganization(searchParams), - ...termsForLanguageCode(searchParams), - ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams), - ...termsForPeerReviewed(searchParams), - ...termsForParentPublicationType(searchParams), - ...termsForInternationalPublication(searchParams), - ...termsForJufoClassCode(searchParams), - ...termsForFieldsOfScience(searchParams), - ...termsForPublicationTypeCode(searchParams), + ...additionFilterTerms("articleTypeCode", searchParams), ] } }, @@ -1165,20 +1013,7 @@ function additionsFromJufoClassCode(searchParams: SearchParams) { "filter": { "bool": { "must": [ - matchingTerms(searchParams), - ...termsForYear(searchParams), - ...termsForTypeCode(searchParams), - ...termsForStatusCode(searchParams), - ...termsForOrganization(searchParams), - ...termsForLanguageCode(searchParams), - ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams), - ...termsForPeerReviewed(searchParams), - ...termsForParentPublicationType(searchParams), - ...termsForInternationalPublication(searchParams), - ...termsForArticleTypeCode(searchParams), - ...termsForFieldsOfScience(searchParams), - ...termsForPublicationTypeCode(searchParams), + ...additionFilterTerms("jufoClassCode", searchParams), ] } }, @@ -1205,20 +1040,7 @@ function additionsFromFieldsOfScience(searchParams: SearchParams) { "filter": { "bool": { "must": [ - matchingTerms(searchParams), - ...termsForYear(searchParams), - ...termsForTypeCode(searchParams), - ...termsForStatusCode(searchParams), - ...termsForOrganization(searchParams), - ...termsForLanguageCode(searchParams), - ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams), - ...termsForPeerReviewed(searchParams), - ...termsForParentPublicationType(searchParams), - ...termsForInternationalPublication(searchParams), - ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams), - ...termsForPublicationTypeCode(searchParams), + ...additionFilterTerms("fieldsOfScience", searchParams), ] } }, @@ -1315,35 +1137,111 @@ function termsForFieldsOfScience(searchParams: SearchParams) { return []; } -/* -GET publication/_search +function termsForPublicationTypeCode(searchParams: SearchParams) { + if (searchParams.publicationTypeCode) { + return [{ + terms: { + "publicationTypeCode.keyword": searchParams.publicationTypeCode + } + }]; + } + return []; +} + +/*GET publication/_search { - "query": { - "match": { - "publicationTypeCode": "A1" - } - } + "size": 0, + "aggs": { + "openAccess": { + "terms": { + "field": "openAccess", + "size": 100 + } + } } +}*/ -GET publication/_search +function termsForOpenAccess(searchParams: SearchParams) { + if (searchParams.openAccess) { + return [{ + terms: { + "openAccess": searchParams.openAccess + } + }]; + } + return []; +} + +function additionsFromOpenAccess(searchParams: SearchParams) { + return { + "all_data_except_openAccess": { + "global": {}, + "aggregations": { + "filtered_except_openAccess": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("openAccess", searchParams), + ] + } + }, + "aggregations": { + "all_openAccess": { + "terms": { + "field": "openAccess", + "size": 100 + } + } + } + } + } + } + }; +} + +/*GET publication/_search { "size": 0, "aggs": { - "publicationTypeCode": { - "terms": { - "field": "publicationTypeCode.keyword", + "openAccess": { + "terms": { + "field": "publisherOpenAccessCode", "size": 100 + } + } +} + +}*/ + +function termsForPublisherOpenAccessCode(searchParams: SearchParams) { + if (searchParams.publisherOpenAccessCode) { + return [{ + terms: { + "publisherOpenAccessCode": searchParams.publisherOpenAccessCode } + }]; + } + return []; +} + +/*GET publication/_search +{ + "size": 0, + "aggs": { + "openAccess": { + "terms": { + "field": "selfArchivedCode.keyword", + "size": 100 } } } -*/ +}*/ -function termsForPublicationTypeCode(searchParams: SearchParams) { - if (searchParams.publicationTypeCode) { +function termsForSelfArchivedCode(searchParams: SearchParams) { + if (searchParams.selfArchivedCode) { return [{ terms: { - "publicationTypeCode.keyword": searchParams.publicationTypeCode + "selfArchivedCode.keyword": searchParams.selfArchivedCode } }]; } @@ -1359,19 +1257,7 @@ function additionsFromPublicationTypeCode(searchParams: SearchParams) { "filter": { "bool": { "must": [ - matchingTerms(searchParams), - ...termsForYear(searchParams), - ...termsForStatusCode(searchParams), - ...termsForOrganization(searchParams), - ...termsForLanguageCode(searchParams), - ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams), - ...termsForPeerReviewed(searchParams), - ...termsForParentPublicationType(searchParams), - ...termsForInternationalPublication(searchParams), - ...termsForJufoClassCode(searchParams), - ...termsForArticleTypeCode(searchParams), - ...termsForFieldsOfScience(searchParams) + ...additionFilterTerms("publicationTypeCode", searchParams), ] } }, @@ -1398,20 +1284,7 @@ function additionsFromOrganization(searchParams: SearchParams) { "filter": { "bool": { "must": [ - matchingTerms(searchParams), - ...termsForYear(searchParams), - ...termsForTypeCode(searchParams), - ...termsForStatusCode(searchParams), - ...termsForLanguageCode(searchParams), - ...termsForPublicationFormat(searchParams), - ...termsForPublicationAudience(searchParams), - ...termsForPeerReviewed(searchParams), - ...termsForParentPublicationType(searchParams), - ...termsForInternationalPublication(searchParams), - ...termsForArticleTypeCode(searchParams), - ...termsForJufoClassCode(searchParams), - ...termsForFieldsOfScience(searchParams), - ...termsForPublicationTypeCode(searchParams), + ...additionFilterTerms("organization", searchParams), ] } }, @@ -1456,10 +1329,8 @@ type OrgsAggsResponse = { }; }; -// helper for getting all terms instead of using a array literal -function allMatchingTerms(searchParams: SearchParams) { +function filteringTerms(searchParams: SearchParams) { return [ - matchingTerms(searchParams), ...termsForYear(searchParams), ...termsForTypeCode(searchParams), ...termsForStatusCode(searchParams), @@ -1473,11 +1344,32 @@ function allMatchingTerms(searchParams: SearchParams) { ...termsForArticleTypeCode(searchParams), ...termsForJufoClassCode(searchParams), ...termsForFieldsOfScience(searchParams), - // ...termsForPublicationTypeCode(searchParams) // TODO ENABLE + ...termsForPublicationTypeCode(searchParams), + ...termsForOpenAccess(searchParams), ]; } -function allMatchingTermsExcept(excluded: string, searchParams: SearchParams) { +function aggregationTerms(searchParams: SearchParams) { + return { + ...additionsFromYear(searchParams), + ...additionsFromTypeCode(searchParams), + ...additionsFromStatusCode(searchParams), + ...additionsFromOrganization(searchParams), + ...additionsFromLanguageCode(searchParams), + ...additionsFromPublicationFormat(searchParams), + ...additionsFromPublicationAudience(searchParams), + ...additionsFromPeerReviewed(searchParams), + ...additionsFromParentPublicationType(searchParams), + ...additionsFromInternationalPublication(searchParams), + ...additionsFromArticleTypeCode(searchParams), + ...additionsFromJufoClassCode(searchParams), + ...additionsFromFieldsOfScience(searchParams), + ...additionsFromPublicationTypeCode(searchParams), + ...additionsFromOpenAccess(searchParams), + }; +} + +function additionFilterTerms(excluded: string, searchParams: SearchParams) { return [ matchingTerms(searchParams), ...(excluded === "year" ? [] : termsForYear(searchParams)), @@ -1493,8 +1385,9 @@ function allMatchingTermsExcept(excluded: string, searchParams: SearchParams) { ...(excluded === "articleTypeCode" ? [] : termsForArticleTypeCode(searchParams)), ...(excluded === "jufoClassCode" ? [] : termsForJufoClassCode(searchParams)), ...(excluded === "fieldsOfScience" ? [] : termsForFieldsOfScience(searchParams)), - // ...(excluded === "publicationTypeCode" ? [] : termsForPublicationTypeCode(searchParams)), // TODO ENABLE - ]; + ...(excluded === "publicationTypeCode" ? [] : termsForPublicationTypeCode(searchParams)), + ...(excluded === "openAccess" ? [] : termsForOpenAccess(searchParams)), + ]; } function toIdNameLookup(data: OrgsAggsResponse): Map { @@ -1557,3 +1450,7 @@ export function getFieldsOfScienceAdditions(aggregations: any) { export function getPublicationTypeCodeAdditions(aggregations: any) { return aggregations.all_data_except_publicationTypeCode?.filtered_except_publicationTypeCode.all_publicationTypeCodes.buckets ?? []; } + +export function getOpenAccessAdditions(aggregations: any) { + return aggregations.all_data_except_openAccess?.filtered_except_openAccess.all_openAccess.buckets ?? []; +} From aedb85aabf12d5e6632ae45773923163c750c9de Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Mon, 22 Jan 2024 16:52:07 +0200 Subject: [PATCH 20/47] Proof of concept without "global" aggregations --- .../publications2.component.html | 39 + .../publications2/publications2.component.ts | 40 +- .../portal/services/publication2.service.ts | 676 ++++++++++++++++-- 3 files changed, 694 insertions(+), 61 deletions(-) diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index 6a93347b6..6eae9cab3 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -442,6 +442,45 @@

Rajaa hakua

+ +
+ +
+ + + TODO + + + + + + + +
+ + +
+ + +
+ + + TODO + + + + + + + +
+
diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 506f59ba8..108b79407 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -15,7 +15,7 @@ import { getPublicationFormatAdditions, getPublicationTypeCodeAdditions, getYearAdditions, HighlightedPublication, - Publication2Service, getOpenAccessAdditions + Publication2Service, getOpenAccessAdditions, getPublisherOpenAccessCodeAdditions, getSelfArchivedCodeAdditions } from '@portal/services/publication2.service'; import { map, take, tap } from 'rxjs/operators'; import { SharedModule } from '@shared/shared.module'; @@ -64,7 +64,11 @@ export class Publications2Component implements OnDestroy { publisherInternationality: $localize`:@@publisherInternationality:Kustantajan kansainvälisyys`, language: $localize`:@@language:Kieli`, jufoLevel: $localize`:@@jufoLevel:Julkaisufoorumitaso`, + + // TODO: tarkista vielä nämä openAccess: $localize`:@@openAccess:Avoin saatavuus`, + publisherOpenAccess: $localize`:@@publisherOpenAccess:Avoin saatavuus kustantajan palvelussa`, + selfArchivedCode: $localize`:@@selfArchivedCode:selfArchivedCode TODO`, } publicationTypeLabels = [ @@ -313,8 +317,6 @@ export class Publications2Component implements OnDestroy { tap(filters => this.updateFilterCount("parentPublicationType", filters.filter(filter => filter.enabled).length)) ); - // TODO: OPEN ACCESS FILTER - additionsFromOpenAccess$ = this.aggregations$.pipe( map(aggs => getOpenAccessAdditions(aggs).map((bucket: any) => ({ id: bucket.key.toString(), count: bucket.doc_count })) ?? []), map(aggs => aggs.sort((a, b) => b.count - a.count)) @@ -324,13 +326,43 @@ export class Publications2Component implements OnDestroy { map(([additionsFromOpenAccess, enabledFilters]) => additionsFromOpenAccess.map(additionFromOpenAccess => ({ id: additionFromOpenAccess.id, count: additionFromOpenAccess.count, - name: additionFromOpenAccess.id, // TODO TODO TODO TODO + name: additionFromOpenAccess.id, // TODO TODO TODO TODO enabled: enabledFilters.includes(additionFromOpenAccess.id) }))), tap(filters => this.updateFilterCount("openAccess", filters.filter(filter => filter.enabled).length)) ); + additionsFromPublisherOpenAccess$ = this.aggregations$.pipe( + map(aggs => getPublisherOpenAccessCodeAdditions(aggs).map((bucket: any) => ({ id: bucket.key.toString(), count: bucket.doc_count })) ?? []), + map(aggs => aggs.sort((a, b) => b.count - a.count)) + ); + + publisherOpenAccessFilters$ = combineLatest([this.additionsFromPublisherOpenAccess$, this.searchParams$.pipe(map(params => params.publisherOpenAccessCode ?? []))]).pipe( + map(([additionsFromPublisherOpenAccess, enabledFilters]) => additionsFromPublisherOpenAccess.map(additionFromPublisherOpenAccess => ({ + id: additionFromPublisherOpenAccess.id, + count: additionFromPublisherOpenAccess.count, + name: additionFromPublisherOpenAccess.id, // TODO TODO TODO TODO + enabled: enabledFilters.includes(additionFromPublisherOpenAccess.id) + }))), + tap(filters => this.updateFilterCount("publisherOpenAccess", filters.filter(filter => filter.enabled).length)) + ); + // TODO selfArchiveCode + + additionsFromSelfArchivedCode$ = this.aggregations$.pipe( + map(aggs => getSelfArchivedCodeAdditions(aggs).map((bucket: any) => ({ id: bucket.key.toString(), count: bucket.doc_count })) ?? []), + map(aggs => aggs.sort((a, b) => b.count - a.count)) + ); + + selfArchivedCodeFilters$ = combineLatest([this.additionsFromSelfArchivedCode$, this.searchParams$.pipe(map(params => params.selfArchiveCode ?? []))]).pipe( + map(([additionsFromSelfArchive, enabledFilters]) => additionsFromSelfArchive.map(additionFromSelfArchive => ({ + id: additionFromSelfArchive.id, + count: additionFromSelfArchive.count, + name: additionFromSelfArchive.id, // TODO TODO TODO TODO + enabled: enabledFilters.includes(additionFromSelfArchive.id) + }))), + tap(filters => this.updateFilterCount("selfArchiveCode", filters.filter(filter => filter.enabled).length)) + ); public mainFieldOfScienceName = { "1": $localize`:@@naturalSciences:Luonnontieteet`, diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 70ec81140..3c6334367 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -724,33 +724,143 @@ function termsForJufoClassCode(searchParams: SearchParams) { return []; } -function additionsFromYear(searchParams: SearchParams) { - return { - "all_data_except_publicationYear": { - "global": {}, - "aggregations": { - "filtered_except_publicationYear": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("year", searchParams), +function generateAggregationStep(name: string, lookup: Record = {}) { + const fieldName = lookup[name] ?? name; + + const topLevelPath = `all_data_except_${fieldName}`; + const middleLevelPath = `filtered_except_${fieldName}`; + const bottomLevelPath = `all_${fieldName}`; + + function getAdditions(searchParams: SearchParams) { + const global = searchParams[name] != null + + if (global) { + return { + [topLevelPath]: { + global: {}, + aggregations: { + [middleLevelPath]: { + filter: { + bool: { + must: [ + ...additionFilterTerms(name, searchParams) + ] + } + }, + aggregations: { + [bottomLevelPath]: { + terms: { + field: fieldName, + size: 100 + } + } + } + } + } + } + }; + } else { + return { + [topLevelPath]: { + filter: { + bool: { + must: [ + ...additionFilterTerms(name, searchParams) ] } }, - "aggregations": { - "all_publicationYears": { - "terms": { - "field": "publicationYear", - "size": 250, + aggregations: { + [bottomLevelPath]: { + terms: { + field: fieldName, + size: 100 } } } } - } + }; } - }; + } + + function getBuckets(aggregations: any) { + // Use the topLevelPath, middleLevelPath and bottomLevelPath to get the buckets + // Detect if global query was made, without explicit "global" parameter + if (aggregations[topLevelPath]?.[middleLevelPath]?.[bottomLevelPath]?.buckets) { + return aggregations[topLevelPath][middleLevelPath][bottomLevelPath].buckets; + } else { + return aggregations[topLevelPath][bottomLevelPath].buckets; + } + } + + return [getAdditions, getBuckets]; +} + +// TODO lookup for urlParam and field name +const lookup = { + "year": "publicationYear", +} + +const [additionsFromYear, getYearAdditions] = generateAggregationStep("year", lookup); +export { getYearAdditions }; + +function NOT_additionsFromYear(searchParams: SearchParams, global = false) { + if (global) + return { + "all_data_except_publicationYear": { + "global": {}, + "aggregations": { + "filtered_except_publicationYear": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("year", searchParams), + ] + } + }, + "aggregations": { + "all_publicationYears": { + "terms": { + "field": "publicationYear", + "size": 250, + } + } + } + } + } + } + }; + + else + return { + "all_data_except_publicationYear": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("year", searchParams), + ] + } + }, + "aggregations": { + "all_publicationYears": { + "terms": { + "field": "publicationYear", + "size": 250, + } + } + } + } + }; } +export function NOT_getYearAdditions(aggregations: /*YearAggregation*/ any, global = false) { + if (global) + return aggregations.all_data_except_publicationYear?.filtered_except_publicationYear.all_publicationYears.buckets ?? []; + else + return aggregations.all_data_except_publicationYear.all_publicationYears.buckets ?? []; +} + + + type YearAggregation = { all_data_except_publicationYear: { filtered_except_publicationYear: { @@ -764,7 +874,9 @@ type YearAggregation = { }; }; -function additionsFromTypeCode(searchParams: SearchParams) { +// TODO IS THIS USED? +function additionsFromTypeCode(searchParams: SearchParams, global = false) { + if (global) return { "all_data_except_publicationTypeCode": { "global": {}, @@ -788,6 +900,26 @@ function additionsFromTypeCode(searchParams: SearchParams) { } } }; + + else + return { + "all_data_except_publicationTypeCode": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("publicationTypeCode", searchParams), + ] + } + }, + "aggregations": { + "all_publicationTypeCodes": { + "terms": { + "field": "publicationTypeCode.keyword" + } + } + } + } + }; } function additionsFromStatusCode(searchParams: SearchParams) { @@ -816,7 +948,8 @@ function additionsFromStatusCode(searchParams: SearchParams) { }; } -function additionsFromLanguageCode(searchParams: SearchParams) { +function additionsFromLanguageCode(searchParams: SearchParams, global = false) { + if (global) return { "all_data_except_languageCode": { "global": {}, @@ -841,9 +974,31 @@ function additionsFromLanguageCode(searchParams: SearchParams) { } } }; + + else + return { + "all_data_except_languageCode": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("language", searchParams), + ] + } + }, + "aggregations": { + "all_languageCodes": { + "terms": { + "field": "languages.languageCode.keyword", + "size": 1000, + } + } + } + } + }; } -function additionsFromPublicationFormat(searchParams: SearchParams) { +function additionsFromPublicationFormat(searchParams: SearchParams, global = false) { + if (global) return { "all_data_except_publicationFormat": { "global": {}, @@ -868,9 +1023,31 @@ function additionsFromPublicationFormat(searchParams: SearchParams) { } } }; + + else + return { + "all_data_except_publicationFormat": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("publicationFormat", searchParams), + ] + } + }, + "aggregations": { + "all_publicationFormats": { + "terms": { + "field": "publicationFormat.id.keyword", + "size": 1000, + } + } + } + } + }; } -function additionsFromPublicationAudience(searchParams: SearchParams) { +function additionsFromPublicationAudience(searchParams: SearchParams, global = false) { + if (global) return { "all_data_except_publicationAudience": { "global": {}, @@ -894,9 +1071,30 @@ function additionsFromPublicationAudience(searchParams: SearchParams) { } } }; + + else + return { + "all_data_except_publicationAudience": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("publicationAudience", searchParams), + ] + } + }, + "aggregations": { + "all_publicationAudiences": { + "terms": { + "field": "publicationAudience.id.keyword" + } + } + } + } + }; } -function additionsFromPeerReviewed(searchParams: SearchParams) { +function additionsFromPeerReviewed(searchParams: SearchParams, global = false) { + if (global) return { "all_data_except_peerReviewed": { "global": {}, @@ -921,9 +1119,31 @@ function additionsFromPeerReviewed(searchParams: SearchParams) { } } }; + + else + return { + "all_data_except_peerReviewed": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("peerReviewed", searchParams), + ] + } + }, + "aggregations": { + "all_peerReviewed": { + "terms": { + "field": "peerReviewed.id.keyword", + "size": 100 + } + } + } + } + }; } -function additionsFromParentPublicationType(searchParams: SearchParams) { +function additionsFromParentPublicationType(searchParams: SearchParams, global = false) { + if (global) return { "all_data_except_parentPublicationType": { "global": {}, @@ -948,9 +1168,31 @@ function additionsFromParentPublicationType(searchParams: SearchParams) { } } } + + else + return { + "all_data_except_parentPublicationType": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("parentPublicationType", searchParams), + ] + } + }, + "aggregations": { + "all_parentPublicationTypes": { + "terms": { + "field": "parentPublicationType.id.keyword", + "size": 100 + } + } + } + } + }; } -function additionsFromInternationalPublication(searchParams: SearchParams) { +function additionsFromInternationalPublication(searchParams: SearchParams, global = false) { + if (global) return { "all_data_except_internationalPublication": { "global": {}, @@ -975,10 +1217,32 @@ function additionsFromInternationalPublication(searchParams: SearchParams) { } } } + + else + return { + "all_data_except_internationalPublication": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("internationalPublication", searchParams), + ] + } + }, + "aggregations": { + "all_internationalPublications": { + "terms": { + "field": "internationalPublication", + "size": 100 + } + } + } + } + }; } -function additionsFromArticleTypeCode(searchParams: SearchParams) { -return { +function additionsFromArticleTypeCode(searchParams: SearchParams, global = false) { + if (global) + return { "all_data_except_articleTypeCode": { "global": {}, "aggregations": { @@ -1002,9 +1266,31 @@ return { } } } + + else + return { + "all_data_except_articleTypeCode": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("articleTypeCode", searchParams), + ] + } + }, + "aggregations": { + "all_articleTypeCodes": { + "terms": { + "field": "articleTypeCode", + "size": 100 + } + } + } + } + }; } -function additionsFromJufoClassCode(searchParams: SearchParams) { +function additionsFromJufoClassCode(searchParams: SearchParams, global = false) { + if (global) return { "all_data_except_jufoClassCode": { "global": {}, @@ -1029,9 +1315,31 @@ function additionsFromJufoClassCode(searchParams: SearchParams) { } } } + + else + return { + "all_data_except_jufoClassCode": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("jufoClassCode", searchParams), + ] + } + }, + "aggregations": { + "all_jufoClassCodes": { + "terms": { + "field": "jufoClassCode.keyword", + "size": 100 + } + } + } + } + }; } -function additionsFromFieldsOfScience(searchParams: SearchParams) { +function additionsFromFieldsOfScience(searchParams: SearchParams, global = false) { + if (global) return { "all_data_except_fieldsOfScience": { "global": {}, @@ -1063,6 +1371,34 @@ function additionsFromFieldsOfScience(searchParams: SearchParams) { } } }; + + else + return { + "all_data_except_fieldsOfScience": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("fieldsOfScience", searchParams), + ] + } + }, + "aggregations": { + "fieldsOfScience_nested": { + "nested": { + "path": "fieldsOfScience" + }, + "aggregations": { + "all_fieldsOfScience": { + "terms": { + "field": "fieldsOfScience.fieldIdScience", + "size": 1000, + } + } + } + } + } + } + }; } type OrganizationAggregation = { @@ -1172,7 +1508,8 @@ function termsForOpenAccess(searchParams: SearchParams) { return []; } -function additionsFromOpenAccess(searchParams: SearchParams) { +function additionsFromOpenAccess(searchParams: SearchParams, global = false) { + if (global) return { "all_data_except_openAccess": { "global": {}, @@ -1197,6 +1534,27 @@ function additionsFromOpenAccess(searchParams: SearchParams) { } } }; + + else + return { + "all_data_except_openAccess": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("openAccess", searchParams), + ] + } + }, + "aggregations": { + "all_openAccess": { + "terms": { + "field": "openAccess", + "size": 100 + } + } + } + } + }; } /*GET publication/_search @@ -1224,6 +1582,55 @@ function termsForPublisherOpenAccessCode(searchParams: SearchParams) { return []; } +function additionsFromPublisherOpenAccessCode(searchParams: SearchParams, global = false) { + if (global) + return { + "all_data_except_publisherOpenAccessCode": { + "global": {}, + "aggregations": { + "filtered_except_publisherOpenAccessCode": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("publisherOpenAccessCode", searchParams), + ] + } + }, + "aggregations": { + "all_publisherOpenAccessCodes": { + "terms": { + "field": "publisherOpenAccessCode", + "size": 100 + } + } + } + } + } + } + }; + + else + return { + "all_data_except_publisherOpenAccessCode": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("publisherOpenAccessCode", searchParams), + ] + } + }, + "aggregations": { + "all_publisherOpenAccessCodes": { + "terms": { + "field": "publisherOpenAccessCode", + "size": 100 + } + } + } + } + }; +} + /*GET publication/_search { "size": 0, @@ -1248,7 +1655,57 @@ function termsForSelfArchivedCode(searchParams: SearchParams) { return []; } -function additionsFromPublicationTypeCode(searchParams: SearchParams) { +function additionsFromSelfArchivedCode(searchParams: SearchParams, global = false) { + if (global) + return { + "all_data_except_selfArchivedCode": { + "global": {}, + "aggregations": { + "filtered_except_selfArchivedCode": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("selfArchivedCode", searchParams), + ] + } + }, + "aggregations": { + "all_selfArchivedCodes": { + "terms": { + "field": "selfArchivedCode.keyword", + "size": 100 + } + } + } + } + } + } + }; + + else + return { + "all_data_except_selfArchivedCode": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("selfArchivedCode", searchParams), + ] + } + }, + "aggregations": { + "all_selfArchivedCodes": { + "terms": { + "field": "selfArchivedCode.keyword", + "size": 100 + } + } + } + } + }; +} + +function additionsFromPublicationTypeCode(searchParams: SearchParams, global = false) { + if (global) return { "all_data_except_publicationTypeCode": { "global": {}, @@ -1273,9 +1730,31 @@ function additionsFromPublicationTypeCode(searchParams: SearchParams) { } } }; + + else + return { + "all_data_except_publicationTypeCode": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("publicationTypeCode", searchParams), + ] + } + }, + "aggregations": { + "all_publicationTypeCodes": { + "terms": { + "field": "publicationTypeCode.keyword", + "size": 100 + } + } + } + } + }; } -function additionsFromOrganization(searchParams: SearchParams) { +function additionsFromOrganization(searchParams: SearchParams, global = false) { + if (global) return { "all_data_except_organizationId": { "global": {}, @@ -1307,6 +1786,34 @@ function additionsFromOrganization(searchParams: SearchParams) { } } }; + + else + return { + "all_data_except_organizationId": { + "filter": { + "bool": { + "must": [ + ...additionFilterTerms("organization", searchParams), + ] + } + }, + "aggregations": { + "organization_nested": { + "nested": { + "path": "author" + }, + "aggregations": { + "all_organizationIds": { + "terms": { + "field": "author.organization.organizationId.keyword", + "size": 250, + } + } + } + } + } + } + }; } type OrgsAggsResponse = { @@ -1346,13 +1853,15 @@ function filteringTerms(searchParams: SearchParams) { ...termsForFieldsOfScience(searchParams), ...termsForPublicationTypeCode(searchParams), ...termsForOpenAccess(searchParams), + ...termsForPublisherOpenAccessCode(searchParams), + ...termsForSelfArchivedCode(searchParams), ]; } function aggregationTerms(searchParams: SearchParams) { return { ...additionsFromYear(searchParams), - ...additionsFromTypeCode(searchParams), + ...additionsFromTypeCode(searchParams), // TODO is this used? ...additionsFromStatusCode(searchParams), ...additionsFromOrganization(searchParams), ...additionsFromLanguageCode(searchParams), @@ -1366,6 +1875,8 @@ function aggregationTerms(searchParams: SearchParams) { ...additionsFromFieldsOfScience(searchParams), ...additionsFromPublicationTypeCode(searchParams), ...additionsFromOpenAccess(searchParams), + ...additionsFromPublisherOpenAccessCode(searchParams), + ...additionsFromSelfArchivedCode(searchParams), }; } @@ -1387,6 +1898,8 @@ function additionFilterTerms(excluded: string, searchParams: SearchParams) { ...(excluded === "fieldsOfScience" ? [] : termsForFieldsOfScience(searchParams)), ...(excluded === "publicationTypeCode" ? [] : termsForPublicationTypeCode(searchParams)), ...(excluded === "openAccess" ? [] : termsForOpenAccess(searchParams)), + ...(excluded === "publisherOpenAccessCode" ? [] : termsForPublisherOpenAccessCode(searchParams)), + ...(excluded === "selfArchivedCode" ? [] : termsForSelfArchivedCode(searchParams)), ]; } @@ -1400,57 +1913,106 @@ function getOrganizationNameBuckets(response: OrgsAggsResponse) { return response.aggregations.filtered_authors.single_sector.organizations.composite_orgs.buckets; } -export function getOrganizationAdditions(aggregations: OrganizationAggregation) { - return aggregations.all_data_except_organizationId?.filtered_except_organizationId.organization_nested.all_organizationIds.buckets ?? []; -} -export function getYearAdditions(aggregations: YearAggregation) { - return aggregations.all_data_except_publicationYear?.filtered_except_publicationYear.all_publicationYears.buckets ?? []; +export function getOrganizationAdditions(aggregations: /*OrganizationAggregation*/ any, global = false) { + if (global) + return aggregations.all_data_except_organizationId?.filtered_except_organizationId.organization_nested.all_organizationIds.buckets ?? []; + else + return aggregations.all_data_except_organizationId.organization_nested.all_organizationIds.buckets ?? []; } -export function getLanguageCodeAdditions(aggregations: any) { - return aggregations.all_data_except_languageCode?.filtered_except_languageCode.all_languageCodes.buckets ?? []; +export function getLanguageCodeAdditions(aggregations: any, global = false) { + if (global) + return aggregations.all_data_except_languageCode?.filtered_except_languageCode.all_languageCodes.buckets ?? []; + else { + return aggregations.all_data_except_languageCode.all_languageCodes.buckets ?? []; + } } -export function getPublicationFormatAdditions(aggregations: any) { - return aggregations.all_data_except_publicationFormat?.filtered_except_publicationFormat.all_publicationFormats.buckets ?? []; +export function getPublicationFormatAdditions(aggregations: any, global = false) { + if (global) + return aggregations.all_data_except_publicationFormat?.filtered_except_publicationFormat.all_publicationFormats.buckets ?? []; + else + return aggregations.all_data_except_publicationFormat.all_publicationFormats.buckets ?? []; } -export function getPublicationAudienceAdditions(aggregations: any) { - return aggregations.all_data_except_publicationAudience?.filtered_except_publicationAudience.all_publicationAudiences.buckets ?? []; +export function getPublicationAudienceAdditions(aggregations: any, global = false) { + if (global) + return aggregations.all_data_except_publicationAudience?.filtered_except_publicationAudience.all_publicationAudiences.buckets ?? []; + else + return aggregations.all_data_except_publicationAudience.all_publicationAudiences.buckets ?? []; } -export function getPeerReviewedAdditions(aggregations: any) { +export function getPeerReviewedAdditions(aggregations: any, global = false) { + if (global) return aggregations.all_data_except_peerReviewed?.filtered_except_peerReviewed.all_peerReviewed.buckets ?? []; + else + return aggregations.all_data_except_peerReviewed.all_peerReviewed.buckets ?? []; } -export function getParentPublicationTypeAdditions(aggregations: any) { - return aggregations.all_data_except_parentPublicationType?.filtered_except_parentPublicationType.all_parentPublicationTypes.buckets ?? []; +export function getParentPublicationTypeAdditions(aggregations: any, global = false) { + if (global) + return aggregations.all_data_except_parentPublicationType?.filtered_except_parentPublicationType.all_parentPublicationTypes.buckets ?? []; + else + return aggregations.all_data_except_parentPublicationType.all_parentPublicationTypes.buckets ?? []; } -export function getPublisherInternationalityAdditions(aggregations: any) { +export function getPublisherInternationalityAdditions(aggregations: any, global = false) { // used to be called "getInternationalPublicationAdditions" // TODO rename all the way to with "PublisherInternationality" + + if (global) return aggregations.all_data_except_internationalPublication?.filtered_except_internationalPublication.all_internationalPublications.buckets ?? []; + else + return aggregations.all_data_except_internationalPublication.all_internationalPublications.buckets ?? []; +} + +export function getArticleTypeCodeAdditions(aggregations: any, global = false) { + if (global) + return aggregations.all_data_except_articleTypeCode?.filtered_except_articleTypeCode.all_articleTypeCodes.buckets ?? []; + else + return aggregations.all_data_except_articleTypeCode.all_articleTypeCodes.buckets ?? []; +} + +export function getJufoClassCodeAdditions(aggregations: any, global = false) { + if (global) + return aggregations.all_data_except_jufoClassCode?.filtered_except_jufoClassCode.all_jufoClassCodes.buckets ?? []; + else + return aggregations.all_data_except_jufoClassCode.all_jufoClassCodes.buckets ?? []; } -export function getArticleTypeCodeAdditions(aggregations: any) { - return aggregations.all_data_except_articleTypeCode?.filtered_except_articleTypeCode.all_articleTypeCodes.buckets ?? []; +export function getFieldsOfScienceAdditions(aggregations: any, global = false) { + if (global) + return aggregations.all_data_except_fieldsOfScience?.filtered_except_fieldsOfScience.fieldsOfScience_nested.all_fieldsOfScience.buckets ?? []; + else + return aggregations.all_data_except_fieldsOfScience.fieldsOfScience_nested.all_fieldsOfScience.buckets ?? []; } -export function getJufoClassCodeAdditions(aggregations: any) { - return aggregations.all_data_except_jufoClassCode?.filtered_except_jufoClassCode.all_jufoClassCodes.buckets ?? []; +export function getPublicationTypeCodeAdditions(aggregations: any, global = false) { + if (global) + return aggregations.all_data_except_publicationTypeCode?.filtered_except_publicationTypeCode.all_publicationTypeCodes.buckets ?? []; + else + return aggregations.all_data_except_publicationTypeCode.all_publicationTypeCodes.buckets ?? []; } -export function getFieldsOfScienceAdditions(aggregations: any) { - return aggregations.all_data_except_fieldsOfScience?.filtered_except_fieldsOfScience.fieldsOfScience_nested.all_fieldsOfScience.buckets ?? []; +export function getOpenAccessAdditions(aggregations: any, global = false) { + if (global) + return aggregations.all_data_except_openAccess?.filtered_except_openAccess.all_openAccess.buckets ?? []; + else + return aggregations.all_data_except_openAccess.all_openAccess.buckets ?? []; } -export function getPublicationTypeCodeAdditions(aggregations: any) { - return aggregations.all_data_except_publicationTypeCode?.filtered_except_publicationTypeCode.all_publicationTypeCodes.buckets ?? []; +export function getPublisherOpenAccessCodeAdditions(aggregations: any, global = false) { + if (global) + return aggregations.all_data_except_publisherOpenAccessCode?.filtered_except_publisherOpenAccessCode.all_publisherOpenAccessCodes.buckets ?? []; + else + return aggregations.all_data_except_publisherOpenAccessCode.all_publisherOpenAccessCodes.buckets ?? []; } -export function getOpenAccessAdditions(aggregations: any) { - return aggregations.all_data_except_openAccess?.filtered_except_openAccess.all_openAccess.buckets ?? []; +export function getSelfArchivedCodeAdditions(aggregations: any, global = false) { + if (global) + return aggregations.all_data_except_selfArchivedCode?.filtered_except_selfArchivedCode.all_selfArchivedCodes.buckets ?? []; + else + return aggregations.all_data_except_selfArchivedCode.all_selfArchivedCodes.buckets ?? []; } From 1fd1521c5c7a97f7de4d9973bfa839d2fc8d91ce Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Wed, 24 Jan 2024 16:42:59 +0200 Subject: [PATCH 21/47] Generalize the building of aggregation steps --- .../portal/services/publication2.service.ts | 1181 ++--------------- 1 file changed, 76 insertions(+), 1105 deletions(-) diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 3c6334367..f8bf5b640 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -9,8 +9,6 @@ import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { locale } from "../../../environments/locale"; const path = suffixer(locale); -console.log("LOOK HERE:", path`fieldsOfScience.nameFiScience.keyword`); - /*const PublicationSearchSchema = object({ publicationId: string(), title: string(), @@ -75,12 +73,8 @@ export class Publication2Service { sanitizer = inject(DomSanitizer); locale = inject(LOCALE_ID); - // path = suffixer(this.locale); - searchUrl = 'https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?' - // organizationNames$ = this.getOrganizationNames(); - searchParams = new BehaviorSubject>({}); searchResults$: Observable = this.searchParams.pipe( @@ -240,13 +234,6 @@ export class Publication2Service { .pipe(shareReplay({ bufferSize: 1, refCount: true })); } -/* -"terms": { - "field": "languages.languageFi.keyword", - "size": 1000 -} -*/ - getLanguageCodeNames(): Observable> { const body = { 'size': 0, @@ -364,11 +351,6 @@ export class Publication2Service { ); } -// getParentPublicationTypeNames -// getInternationalPublicationNames -// getArticleTypeCodeNames - - getParentPublicationTypeNames(): Observable> { const body = { "size": 0, @@ -630,12 +612,6 @@ function termsForPublicationAudience(searchParams: SearchParams) { return []; } -/* Peer reviewed -"terms": { - "field": "peerReviewed.id.keyword", - "size": 100 -} -*/ function termsForPeerReviewed(searchParams: SearchParams) { if (searchParams.peerReviewed) { return [{ @@ -647,19 +623,6 @@ function termsForPeerReviewed(searchParams: SearchParams) { return []; } -/* Parent Publication Type -{ - "size": 0, - "aggs": { - "parentPublicationType": { - "terms": { - "field": "parentPublicationType.id.keyword", - "size": 100 - } - } - } -} -*/ function termsForParentPublicationType(searchParams: SearchParams) { if (searchParams.parentPublicationType) { return [{ @@ -671,12 +634,6 @@ function termsForParentPublicationType(searchParams: SearchParams) { return []; } -/* International Publication -"terms": { - "field": "internationalPublication", - "size": 100 -} -*/ function termsForInternationalPublication(searchParams: SearchParams) { if (searchParams.international) { return [{ @@ -688,12 +645,6 @@ function termsForInternationalPublication(searchParams: SearchParams) { return []; } -/* -"terms": { - "field": "articleTypeCode", - "size": 100 -} -*/ function termsForArticleTypeCode(searchParams: SearchParams) { if (searchParams.articleType) { return [{ @@ -705,14 +656,6 @@ function termsForArticleTypeCode(searchParams: SearchParams) { return []; } -/* -"jufoClassCode": { - "terms": { - "field": "jufoClassCode.keyword", - "size": 100 - } -} -*/ function termsForJufoClassCode(searchParams: SearchParams) { if (searchParams.jufo) { return [{ @@ -724,17 +667,20 @@ function termsForJufoClassCode(searchParams: SearchParams) { return []; } -function generateAggregationStep(name: string, lookup: Record = {}) { - const fieldName = lookup[name] ?? name; +function generateAggregationStep(name: SearchParamKey, lookup: Record) { + const fieldPath = lookup[name].fieldPath; - const topLevelPath = `all_data_except_${fieldName}`; - const middleLevelPath = `filtered_except_${fieldName}`; - const bottomLevelPath = `all_${fieldName}`; + const fieldName = lookup[name].fieldName; // TODO: "fieldName" is just redundant + const topLevelPath = `top_level_${fieldName}`; // TODO: Use the established SearchParamKey and arbitary fieldPath + const middleLevelPath = `filtered_except_${fieldName}`; // + const bottomLevelPath = `all_${fieldName}`; // function getAdditions(searchParams: SearchParams) { const global = searchParams[name] != null if (global) { + console.log("global", name); + return { [topLevelPath]: { global: {}, @@ -750,7 +696,7 @@ function generateAggregationStep(name: string, lookup: Record = aggregations: { [bottomLevelPath]: { terms: { - field: fieldName, + field: fieldPath, size: 100 } } @@ -760,6 +706,8 @@ function generateAggregationStep(name: string, lookup: Record = } }; } else { + console.log("local", name); + return { [topLevelPath]: { filter: { @@ -772,7 +720,7 @@ function generateAggregationStep(name: string, lookup: Record = aggregations: { [bottomLevelPath]: { terms: { - field: fieldName, + field: fieldPath, size: 100 } } @@ -783,8 +731,6 @@ function generateAggregationStep(name: string, lookup: Record = } function getBuckets(aggregations: any) { - // Use the topLevelPath, middleLevelPath and bottomLevelPath to get the buckets - // Detect if global query was made, without explicit "global" parameter if (aggregations[topLevelPath]?.[middleLevelPath]?.[bottomLevelPath]?.buckets) { return aggregations[topLevelPath][middleLevelPath][bottomLevelPath].buckets; } else { @@ -795,639 +741,62 @@ function generateAggregationStep(name: string, lookup: Record = return [getAdditions, getBuckets]; } -// TODO lookup for urlParam and field name -const lookup = { - "year": "publicationYear", +type SearchParamKey = Exclude; + +const lookup: Record = { + year: {fieldName: "publicationYear", fieldPath: "publicationYear"}, + language: {fieldName: "languages", fieldPath: "languages.languageCode.keyword"}, + format: {fieldName: "publicationFormat", fieldPath: "publicationFormat.id.keyword"}, + audience: {fieldName: "publicationAudience", fieldPath: "publicationAudience.id.keyword"}, + peerReviewed: {fieldName: "peerReviewed", fieldPath: "peerReviewed.id.keyword"}, + parentPublicationType: {fieldName: "parentPublicationType", fieldPath: "parentPublicationType.id.keyword"}, + international: {fieldName: "internationalPublication", fieldPath: "internationalPublication"}, + articleType: {fieldName: "articleTypeCode", fieldPath: "articleTypeCode"}, + jufo: {fieldName: "jufoClassCode", fieldPath: "jufoClassCode.keyword"}, + publicationTypeCode: {fieldName: "publicationTypeCode", fieldPath: "publicationTypeCode.keyword"}, + publicationStatusCode: {fieldName: "publicationStatusCode", fieldPath: "publicationStatusCode.keyword"}, // No trace of the previous implementation + fieldsOfScience: {fieldName: "fieldsOfScience", fieldPath: "fieldsOfScience.fieldIdScience"}, + openAccess: {fieldName: "openAccess", fieldPath: "openAccess"}, + publisherOpenAccessCode: {fieldName: "publisherOpenAccessCode", fieldPath: "publisherOpenAccessCode"}, + selfArchivedCode: {fieldName: "selfArchivedCode", fieldPath: "selfArchivedCode.keyword"}, + organization: {fieldName: "organization", fieldPath: "author.organization.organizationId.keyword"}, } const [additionsFromYear, getYearAdditions] = generateAggregationStep("year", lookup); export { getYearAdditions }; -function NOT_additionsFromYear(searchParams: SearchParams, global = false) { - if (global) - return { - "all_data_except_publicationYear": { - "global": {}, - "aggregations": { - "filtered_except_publicationYear": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("year", searchParams), - ] - } - }, - "aggregations": { - "all_publicationYears": { - "terms": { - "field": "publicationYear", - "size": 250, - } - } - } - } - } - } - }; - - else - return { - "all_data_except_publicationYear": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("year", searchParams), - ] - } - }, - "aggregations": { - "all_publicationYears": { - "terms": { - "field": "publicationYear", - "size": 250, - } - } - } - } - }; -} - -export function NOT_getYearAdditions(aggregations: /*YearAggregation*/ any, global = false) { - if (global) - return aggregations.all_data_except_publicationYear?.filtered_except_publicationYear.all_publicationYears.buckets ?? []; - else - return aggregations.all_data_except_publicationYear.all_publicationYears.buckets ?? []; -} - - - -type YearAggregation = { - all_data_except_publicationYear: { - filtered_except_publicationYear: { - all_publicationYears: { - buckets: Array<{ - key: number; - doc_count: number; - }>; - }; - }; - }; -}; - -// TODO IS THIS USED? -function additionsFromTypeCode(searchParams: SearchParams, global = false) { - if (global) - return { - "all_data_except_publicationTypeCode": { - "global": {}, - "aggregations": { - "filtered_except_publicationTypeCode": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("publicationTypeCode", searchParams), - ] - } - }, - "aggregations": { - "all_publicationTypeCodes": { - "terms": { - "field": "publicationTypeCode.keyword" - } - } - } - } - } - } - }; - - else - return { - "all_data_except_publicationTypeCode": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("publicationTypeCode", searchParams), - ] - } - }, - "aggregations": { - "all_publicationTypeCodes": { - "terms": { - "field": "publicationTypeCode.keyword" - } - } - } - } - }; -} - -function additionsFromStatusCode(searchParams: SearchParams) { - return { - "all_data_except_publicationStatusCode": { - "global": {}, - "aggregations": { - "filtered_except_publicationStatusCode": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("statusCode", searchParams), - ] - } - }, - "aggregations": { - "all_publicationStatusCodes": { - "terms": { - "field": "publicationStatusCode.keyword" - } - } - } - } - } - } - }; -} - -function additionsFromLanguageCode(searchParams: SearchParams, global = false) { - if (global) - return { - "all_data_except_languageCode": { - "global": {}, - "aggregations": { - "filtered_except_languageCode": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("language", searchParams), - ] - } - }, - "aggregations": { - "all_languageCodes": { - "terms": { - "field": "languages.languageCode.keyword", - "size": 1000, - } - } - } - } - } - } - }; - - else - return { - "all_data_except_languageCode": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("language", searchParams), - ] - } - }, - "aggregations": { - "all_languageCodes": { - "terms": { - "field": "languages.languageCode.keyword", - "size": 1000, - } - } - } - } - }; -} - -function additionsFromPublicationFormat(searchParams: SearchParams, global = false) { - if (global) - return { - "all_data_except_publicationFormat": { - "global": {}, - "aggregations": { - "filtered_except_publicationFormat": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("publicationFormat", searchParams), - ] - } - }, - "aggregations": { - "all_publicationFormats": { - "terms": { - "field": "publicationFormat.id.keyword", - "size": 1000, - } - } - } - } - } - } - }; - - else - return { - "all_data_except_publicationFormat": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("publicationFormat", searchParams), - ] - } - }, - "aggregations": { - "all_publicationFormats": { - "terms": { - "field": "publicationFormat.id.keyword", - "size": 1000, - } - } - } - } - }; -} +const [additionsFromLanguageCode, getLanguageCodeAdditions] = generateAggregationStep("language", lookup); +export { getLanguageCodeAdditions }; -function additionsFromPublicationAudience(searchParams: SearchParams, global = false) { - if (global) - return { - "all_data_except_publicationAudience": { - "global": {}, - "aggregations": { - "filtered_except_publicationAudience": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("publicationAudience", searchParams), - ] - } - }, - "aggregations": { - "all_publicationAudiences": { - "terms": { - "field": "publicationAudience.id.keyword" - } - } - } - } - } - } - }; +const [additionsFromPublicationFormat, getPublicationFormatAdditions] = generateAggregationStep("format", lookup); +export { getPublicationFormatAdditions }; - else - return { - "all_data_except_publicationAudience": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("publicationAudience", searchParams), - ] - } - }, - "aggregations": { - "all_publicationAudiences": { - "terms": { - "field": "publicationAudience.id.keyword" - } - } - } - } - }; -} +const [additionsFromPublicationAudience, getPublicationAudienceAdditions] = generateAggregationStep("audience", lookup); +export { getPublicationAudienceAdditions }; -function additionsFromPeerReviewed(searchParams: SearchParams, global = false) { - if (global) - return { - "all_data_except_peerReviewed": { - "global": {}, - "aggregations": { - "filtered_except_peerReviewed": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("peerReviewed", searchParams), - ] - } - }, - "aggregations": { - "all_peerReviewed": { - "terms": { - "field": "peerReviewed.id.keyword", - "size": 100 - } - } - } - } - } - } - }; +const [additionsFromPeerReviewed, getPeerReviewedAdditions] = generateAggregationStep("peerReviewed", lookup); +export { getPeerReviewedAdditions }; - else - return { - "all_data_except_peerReviewed": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("peerReviewed", searchParams), - ] - } - }, - "aggregations": { - "all_peerReviewed": { - "terms": { - "field": "peerReviewed.id.keyword", - "size": 100 - } - } - } - } - }; -} +const [additionsFromParentPublicationType, getParentPublicationTypeAdditions] = generateAggregationStep("parentPublicationType", lookup); +export { getParentPublicationTypeAdditions }; -function additionsFromParentPublicationType(searchParams: SearchParams, global = false) { - if (global) - return { - "all_data_except_parentPublicationType": { - "global": {}, - "aggregations": { - "filtered_except_parentPublicationType": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("parentPublicationType", searchParams), - ] - } - }, - "aggregations": { - "all_parentPublicationTypes": { - "terms": { - "field": "parentPublicationType.id.keyword", - "size": 100 - } - } - } - } - } - } - } +const [additionsFromInternationalPublication, getPublisherInternationalityAdditions] = generateAggregationStep("international", lookup); +export { getPublisherInternationalityAdditions }; - else - return { - "all_data_except_parentPublicationType": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("parentPublicationType", searchParams), - ] - } - }, - "aggregations": { - "all_parentPublicationTypes": { - "terms": { - "field": "parentPublicationType.id.keyword", - "size": 100 - } - } - } - } - }; -} - -function additionsFromInternationalPublication(searchParams: SearchParams, global = false) { - if (global) - return { - "all_data_except_internationalPublication": { - "global": {}, - "aggregations": { - "filtered_except_internationalPublication": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("internationalPublication", searchParams), - ] - } - }, - "aggregations": { - "all_internationalPublications": { - "terms": { - "field": "internationalPublication", - "size": 100 - } - } - } - } - } - } - } +const [additionsFromArticleTypeCode, getArticleTypeCodeAdditions] = generateAggregationStep("articleType", lookup); +export { getArticleTypeCodeAdditions }; - else - return { - "all_data_except_internationalPublication": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("internationalPublication", searchParams), - ] - } - }, - "aggregations": { - "all_internationalPublications": { - "terms": { - "field": "internationalPublication", - "size": 100 - } - } - } - } - }; -} +const [additionsFromJufoClassCode, getJufoClassCodeAdditions] = generateAggregationStep("jufo", lookup); +export { getJufoClassCodeAdditions }; -function additionsFromArticleTypeCode(searchParams: SearchParams, global = false) { - if (global) - return { - "all_data_except_articleTypeCode": { - "global": {}, - "aggregations": { - "filtered_except_articleTypeCode": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("articleTypeCode", searchParams), - ] - } - }, - "aggregations": { - "all_articleTypeCodes": { - "terms": { - "field": "articleTypeCode", - "size": 100 - } - } - } - } - } - } - } - - else - return { - "all_data_except_articleTypeCode": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("articleTypeCode", searchParams), - ] - } - }, - "aggregations": { - "all_articleTypeCodes": { - "terms": { - "field": "articleTypeCode", - "size": 100 - } - } - } - } - }; -} - -function additionsFromJufoClassCode(searchParams: SearchParams, global = false) { - if (global) - return { - "all_data_except_jufoClassCode": { - "global": {}, - "aggregations": { - "filtered_except_jufoClassCode": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("jufoClassCode", searchParams), - ] - } - }, - "aggregations": { - "all_jufoClassCodes": { - "terms": { - "field": "jufoClassCode.keyword", - "size": 100 - } - } - } - } - } - } - } - - else - return { - "all_data_except_jufoClassCode": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("jufoClassCode", searchParams), - ] - } - }, - "aggregations": { - "all_jufoClassCodes": { - "terms": { - "field": "jufoClassCode.keyword", - "size": 100 - } - } - } - } - }; -} - -function additionsFromFieldsOfScience(searchParams: SearchParams, global = false) { - if (global) - return { - "all_data_except_fieldsOfScience": { - "global": {}, - "aggregations": { - "filtered_except_fieldsOfScience": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("fieldsOfScience", searchParams), - ] - } - }, - "aggregations": { - "fieldsOfScience_nested": { - "nested": { - "path": "fieldsOfScience" - }, - "aggregations": { - "all_fieldsOfScience": { - "terms": { - "field": "fieldsOfScience.fieldIdScience", - "size": 1000, - } - } - } - } - } - } - } - } - }; - - else - return { - "all_data_except_fieldsOfScience": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("fieldsOfScience", searchParams), - ] - } - }, - "aggregations": { - "fieldsOfScience_nested": { - "nested": { - "path": "fieldsOfScience" - }, - "aggregations": { - "all_fieldsOfScience": { - "terms": { - "field": "fieldsOfScience.fieldIdScience", - "size": 1000, - } - } - } - } - } - } - }; -} - -type OrganizationAggregation = { - all_data_except_organizationId: { - filtered_except_organizationId: { - organization_nested: { - all_organizationIds: { - buckets: Array<{ - key: string; - doc_count: number; - }>; - }; - }; - }; - }; -}; +const [additionsFromFieldsOfScience, getFieldsOfScienceAdditions] = generateAggregationStep("fieldsOfScience", lookup); +export { getFieldsOfScienceAdditions }; function suffixer(locale) { const capitalized = locale.charAt(0).toUpperCase() + locale.slice(1).toLowerCase(); - - - // replace Fi with capitalized from middle or end of the string - // return strings => strings[0].replace(/(Fi|Sv|En)(?=\.|$)/g, capitalized); - - // replace Fi with capitalized if the following character is a dot or a capital letter or end of string return strings => strings[0].replace(/(Fi|Sv|En)(?=[\.A-Z]|$)/g, capitalized); } -// NOTE: nested is needed for array type mapping function termsForOrganization(searchParams: SearchParams) { if (searchParams.organization && searchParams.organization.length > 0) { return [{ @@ -1450,7 +819,6 @@ function termsForOrganization(searchParams: SearchParams) { return []; } -// NOTE: nested is needed for array type mapping function termsForFieldsOfScience(searchParams: SearchParams) { if (searchParams.fieldsOfScience && searchParams.fieldsOfScience.length > 0) { return [{ @@ -1484,19 +852,6 @@ function termsForPublicationTypeCode(searchParams: SearchParams) { return []; } -/*GET publication/_search -{ - "size": 0, - "aggs": { - "openAccess": { - "terms": { - "field": "openAccess", - "size": 100 - } - } -} -}*/ - function termsForOpenAccess(searchParams: SearchParams) { if (searchParams.openAccess) { return [{ @@ -1508,68 +863,8 @@ function termsForOpenAccess(searchParams: SearchParams) { return []; } -function additionsFromOpenAccess(searchParams: SearchParams, global = false) { - if (global) - return { - "all_data_except_openAccess": { - "global": {}, - "aggregations": { - "filtered_except_openAccess": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("openAccess", searchParams), - ] - } - }, - "aggregations": { - "all_openAccess": { - "terms": { - "field": "openAccess", - "size": 100 - } - } - } - } - } - } - }; - - else - return { - "all_data_except_openAccess": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("openAccess", searchParams), - ] - } - }, - "aggregations": { - "all_openAccess": { - "terms": { - "field": "openAccess", - "size": 100 - } - } - } - } - }; -} - -/*GET publication/_search -{ - "size": 0, - "aggs": { - "openAccess": { - "terms": { - "field": "publisherOpenAccessCode", - "size": 100 - } - } -} - -}*/ +const [additionsFromOpenAccess, getOpenAccessAdditions] = generateAggregationStep("openAccess", lookup); +export { getOpenAccessAdditions }; function termsForPublisherOpenAccessCode(searchParams: SearchParams) { if (searchParams.publisherOpenAccessCode) { @@ -1582,67 +877,8 @@ function termsForPublisherOpenAccessCode(searchParams: SearchParams) { return []; } -function additionsFromPublisherOpenAccessCode(searchParams: SearchParams, global = false) { - if (global) - return { - "all_data_except_publisherOpenAccessCode": { - "global": {}, - "aggregations": { - "filtered_except_publisherOpenAccessCode": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("publisherOpenAccessCode", searchParams), - ] - } - }, - "aggregations": { - "all_publisherOpenAccessCodes": { - "terms": { - "field": "publisherOpenAccessCode", - "size": 100 - } - } - } - } - } - } - }; - - else - return { - "all_data_except_publisherOpenAccessCode": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("publisherOpenAccessCode", searchParams), - ] - } - }, - "aggregations": { - "all_publisherOpenAccessCodes": { - "terms": { - "field": "publisherOpenAccessCode", - "size": 100 - } - } - } - } - }; -} - -/*GET publication/_search -{ - "size": 0, - "aggs": { - "openAccess": { - "terms": { - "field": "selfArchivedCode.keyword", - "size": 100 - } - } -} -}*/ +const [additionsFromPublisherOpenAccessCode, getPublisherOpenAccessCodeAdditions] = generateAggregationStep("publisherOpenAccessCode", lookup); +export { getPublisherOpenAccessCodeAdditions }; function termsForSelfArchivedCode(searchParams: SearchParams) { if (searchParams.selfArchivedCode) { @@ -1655,166 +891,14 @@ function termsForSelfArchivedCode(searchParams: SearchParams) { return []; } -function additionsFromSelfArchivedCode(searchParams: SearchParams, global = false) { - if (global) - return { - "all_data_except_selfArchivedCode": { - "global": {}, - "aggregations": { - "filtered_except_selfArchivedCode": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("selfArchivedCode", searchParams), - ] - } - }, - "aggregations": { - "all_selfArchivedCodes": { - "terms": { - "field": "selfArchivedCode.keyword", - "size": 100 - } - } - } - } - } - } - }; - - else - return { - "all_data_except_selfArchivedCode": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("selfArchivedCode", searchParams), - ] - } - }, - "aggregations": { - "all_selfArchivedCodes": { - "terms": { - "field": "selfArchivedCode.keyword", - "size": 100 - } - } - } - } - }; -} - -function additionsFromPublicationTypeCode(searchParams: SearchParams, global = false) { - if (global) - return { - "all_data_except_publicationTypeCode": { - "global": {}, - "aggregations": { - "filtered_except_publicationTypeCode": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("publicationTypeCode", searchParams), - ] - } - }, - "aggregations": { - "all_publicationTypeCodes": { - "terms": { - "field": "publicationTypeCode.keyword", - "size": 100 - } - } - } - } - } - } - }; +const [additionsFromSelfArchivedCode, getSelfArchivedCodeAdditions] = generateAggregationStep("selfArchivedCode", lookup); +export { getSelfArchivedCodeAdditions }; - else - return { - "all_data_except_publicationTypeCode": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("publicationTypeCode", searchParams), - ] - } - }, - "aggregations": { - "all_publicationTypeCodes": { - "terms": { - "field": "publicationTypeCode.keyword", - "size": 100 - } - } - } - } - }; -} - -function additionsFromOrganization(searchParams: SearchParams, global = false) { - if (global) - return { - "all_data_except_organizationId": { - "global": {}, - "aggregations": { - "filtered_except_organizationId": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("organization", searchParams), - ] - } - }, - "aggregations": { - "organization_nested": { - "nested": { - "path": "author" - }, - "aggregations": { - "all_organizationIds": { - "terms": { - "field": "author.organization.organizationId.keyword", - "size": 250, - } - } - } - } - } - } - } - } - }; +const [additionsFromPublicationTypeCode, getPublicationTypeCodeAdditions] = generateAggregationStep("publicationTypeCode", lookup); +export { getPublicationTypeCodeAdditions }; - else - return { - "all_data_except_organizationId": { - "filter": { - "bool": { - "must": [ - ...additionFilterTerms("organization", searchParams), - ] - } - }, - "aggregations": { - "organization_nested": { - "nested": { - "path": "author" - }, - "aggregations": { - "all_organizationIds": { - "terms": { - "field": "author.organization.organizationId.keyword", - "size": 250, - } - } - } - } - } - } - }; -} +const [additionsFromOrganization, getOrganizationAdditions] = generateAggregationStep("organization", lookup); +export { getOrganizationAdditions }; type OrgsAggsResponse = { aggregations: { @@ -1861,8 +945,6 @@ function filteringTerms(searchParams: SearchParams) { function aggregationTerms(searchParams: SearchParams) { return { ...additionsFromYear(searchParams), - ...additionsFromTypeCode(searchParams), // TODO is this used? - ...additionsFromStatusCode(searchParams), ...additionsFromOrganization(searchParams), ...additionsFromLanguageCode(searchParams), ...additionsFromPublicationFormat(searchParams), @@ -1880,139 +962,28 @@ function aggregationTerms(searchParams: SearchParams) { }; } -function additionFilterTerms(excluded: string, searchParams: SearchParams) { +function additionFilterTerms(excluded: SearchParamKey, searchParams: SearchParams) { return [ matchingTerms(searchParams), - ...(excluded === "year" ? [] : termsForYear(searchParams)), - ...(excluded === "typeCode" ? [] : termsForTypeCode(searchParams)), - ...(excluded === "statusCode" ? [] : termsForStatusCode(searchParams)), - ...(excluded === "organization" ? [] : termsForOrganization(searchParams)), - ...(excluded === "languageCode" ? [] : termsForLanguageCode(searchParams)), - ...(excluded === "publicationFormat" ? [] : termsForPublicationFormat(searchParams)), - ...(excluded === "publicationAudience" ? [] : termsForPublicationAudience(searchParams)), - ...(excluded === "peerReviewed" ? [] : termsForPeerReviewed(searchParams)), - ...(excluded === "parentPublicationType" ? [] : termsForParentPublicationType(searchParams)), - ...(excluded === "internationalPublication" ? [] : termsForInternationalPublication(searchParams)), - ...(excluded === "articleTypeCode" ? [] : termsForArticleTypeCode(searchParams)), - ...(excluded === "jufoClassCode" ? [] : termsForJufoClassCode(searchParams)), - ...(excluded === "fieldsOfScience" ? [] : termsForFieldsOfScience(searchParams)), - ...(excluded === "publicationTypeCode" ? [] : termsForPublicationTypeCode(searchParams)), - ...(excluded === "openAccess" ? [] : termsForOpenAccess(searchParams)), - ...(excluded === "publisherOpenAccessCode" ? [] : termsForPublisherOpenAccessCode(searchParams)), - ...(excluded === "selfArchivedCode" ? [] : termsForSelfArchivedCode(searchParams)), + ...(excluded === "year" ? [] : termsForYear(searchParams)), + ...(excluded === "publicationStatusCode" ? [] : termsForStatusCode(searchParams)), + ...(excluded === "organization" ? [] : termsForOrganization(searchParams)), + ...(excluded === "language" ? [] : termsForLanguageCode(searchParams)), + ...(excluded === "format" ? [] : termsForPublicationFormat(searchParams)), + ...(excluded === "audience" ? [] : termsForPublicationAudience(searchParams)), + ...(excluded === "peerReviewed" ? [] : termsForPeerReviewed(searchParams)), + ...(excluded === "parentPublicationType" ? [] : termsForParentPublicationType(searchParams)), + ...(excluded === "international" ? [] : termsForInternationalPublication(searchParams)), + ...(excluded === "articleType" ? [] : termsForArticleTypeCode(searchParams)), + ...(excluded === "jufo" ? [] : termsForJufoClassCode(searchParams)), + ...(excluded === "fieldsOfScience" ? [] : termsForFieldsOfScience(searchParams)), + ...(excluded === "publicationTypeCode" ? [] : termsForPublicationTypeCode(searchParams)), + ...(excluded === "openAccess" ? [] : termsForOpenAccess(searchParams)), + ...(excluded === "publisherOpenAccessCode" ? [] : termsForPublisherOpenAccessCode(searchParams)), + ...(excluded === "selfArchivedCode" ? [] : termsForSelfArchivedCode(searchParams)), ]; } -function toIdNameLookup(data: OrgsAggsResponse): Map { - const pairs = getOrganizationNameBuckets(data).map((bucket) => [bucket.key.id, bucket.key.name]); - - return Object.fromEntries(pairs); -} - function getOrganizationNameBuckets(response: OrgsAggsResponse) { return response.aggregations.filtered_authors.single_sector.organizations.composite_orgs.buckets; } - - -export function getOrganizationAdditions(aggregations: /*OrganizationAggregation*/ any, global = false) { - if (global) - return aggregations.all_data_except_organizationId?.filtered_except_organizationId.organization_nested.all_organizationIds.buckets ?? []; - else - return aggregations.all_data_except_organizationId.organization_nested.all_organizationIds.buckets ?? []; -} - -export function getLanguageCodeAdditions(aggregations: any, global = false) { - if (global) - return aggregations.all_data_except_languageCode?.filtered_except_languageCode.all_languageCodes.buckets ?? []; - else { - return aggregations.all_data_except_languageCode.all_languageCodes.buckets ?? []; - } -} - -export function getPublicationFormatAdditions(aggregations: any, global = false) { - if (global) - return aggregations.all_data_except_publicationFormat?.filtered_except_publicationFormat.all_publicationFormats.buckets ?? []; - else - return aggregations.all_data_except_publicationFormat.all_publicationFormats.buckets ?? []; -} - -export function getPublicationAudienceAdditions(aggregations: any, global = false) { - if (global) - return aggregations.all_data_except_publicationAudience?.filtered_except_publicationAudience.all_publicationAudiences.buckets ?? []; - else - return aggregations.all_data_except_publicationAudience.all_publicationAudiences.buckets ?? []; -} - -export function getPeerReviewedAdditions(aggregations: any, global = false) { - if (global) - return aggregations.all_data_except_peerReviewed?.filtered_except_peerReviewed.all_peerReviewed.buckets ?? []; - else - return aggregations.all_data_except_peerReviewed.all_peerReviewed.buckets ?? []; -} - -export function getParentPublicationTypeAdditions(aggregations: any, global = false) { - if (global) - return aggregations.all_data_except_parentPublicationType?.filtered_except_parentPublicationType.all_parentPublicationTypes.buckets ?? []; - else - return aggregations.all_data_except_parentPublicationType.all_parentPublicationTypes.buckets ?? []; -} - -export function getPublisherInternationalityAdditions(aggregations: any, global = false) { - // used to be called "getInternationalPublicationAdditions" - - // TODO rename all the way to with "PublisherInternationality" - - if (global) - return aggregations.all_data_except_internationalPublication?.filtered_except_internationalPublication.all_internationalPublications.buckets ?? []; - else - return aggregations.all_data_except_internationalPublication.all_internationalPublications.buckets ?? []; -} - -export function getArticleTypeCodeAdditions(aggregations: any, global = false) { - if (global) - return aggregations.all_data_except_articleTypeCode?.filtered_except_articleTypeCode.all_articleTypeCodes.buckets ?? []; - else - return aggregations.all_data_except_articleTypeCode.all_articleTypeCodes.buckets ?? []; -} - -export function getJufoClassCodeAdditions(aggregations: any, global = false) { - if (global) - return aggregations.all_data_except_jufoClassCode?.filtered_except_jufoClassCode.all_jufoClassCodes.buckets ?? []; - else - return aggregations.all_data_except_jufoClassCode.all_jufoClassCodes.buckets ?? []; -} - -export function getFieldsOfScienceAdditions(aggregations: any, global = false) { - if (global) - return aggregations.all_data_except_fieldsOfScience?.filtered_except_fieldsOfScience.fieldsOfScience_nested.all_fieldsOfScience.buckets ?? []; - else - return aggregations.all_data_except_fieldsOfScience.fieldsOfScience_nested.all_fieldsOfScience.buckets ?? []; -} - -export function getPublicationTypeCodeAdditions(aggregations: any, global = false) { - if (global) - return aggregations.all_data_except_publicationTypeCode?.filtered_except_publicationTypeCode.all_publicationTypeCodes.buckets ?? []; - else - return aggregations.all_data_except_publicationTypeCode.all_publicationTypeCodes.buckets ?? []; -} - -export function getOpenAccessAdditions(aggregations: any, global = false) { - if (global) - return aggregations.all_data_except_openAccess?.filtered_except_openAccess.all_openAccess.buckets ?? []; - else - return aggregations.all_data_except_openAccess.all_openAccess.buckets ?? []; -} - -export function getPublisherOpenAccessCodeAdditions(aggregations: any, global = false) { - if (global) - return aggregations.all_data_except_publisherOpenAccessCode?.filtered_except_publisherOpenAccessCode.all_publisherOpenAccessCodes.buckets ?? []; - else - return aggregations.all_data_except_publisherOpenAccessCode.all_publisherOpenAccessCodes.buckets ?? []; -} - -export function getSelfArchivedCodeAdditions(aggregations: any, global = false) { - if (global) - return aggregations.all_data_except_selfArchivedCode?.filtered_except_selfArchivedCode.all_selfArchivedCodes.buckets ?? []; - else - return aggregations.all_data_except_selfArchivedCode.all_selfArchivedCodes.buckets ?? []; -} From 678f438ff5b9d670e94a4058ca58879a626a9685 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Mon, 5 Feb 2024 12:49:35 +0200 Subject: [PATCH 22/47] Fix the tooltips in openAccess type filters --- .../publications2.component.html | 56 +++++---- .../publications2/publications2.component.ts | 4 +- src/app/portal/portal-routing.module.ts | 3 - .../portal/services/publication2.service.ts | 4 +- src/i18n/messages.en.xlf | 109 ++++++++++++++++++ src/i18n/messages.sv.xlf | 95 +++++++++++++++ src/i18n/messages.xlf | 77 +++++++++++++ 7 files changed, 320 insertions(+), 28 deletions(-) diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index 6eae9cab3..6541d5773 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -401,35 +401,22 @@

Rajaa hakua

-

- Open access -julkaisukanava: - Julkaisu on ilmestynyt julkaisukanavassa, jonka kaikki julkaisut ovat avoimesti saatavilla. -

- -

- Viivästetty avoin saatavuus: - Julkaisukanavan tieteelliset artikkelit avataan kustantajan määrittelemän viiveajan jälkeen. Sisältää sekä jo avoimesti saatavilla että yhä maksumuurin takana olevia julkaisuja. -

- Rinnakkaistallennettu: - Julkaisu on tallennettu organisaatio- tai tieteenalakohtaiseen julkaisuarkistoon joko välittömästi tai kustantajan määrittämän kohtuullisen embargoajan jälkeen. + Avoimesti saatavilla: + Julkaisu on avoimesti saatavilla kustantajan palvelussa.

- Muu avoin saatavuus: - Julkaisu on avoimesti saatavilla, mutta se on ilmestynyt ns. hybridijulkaisukanavassa, jossa kaikki muut julkaisut eivät ole avoimesti saatavilla. + Ei avoin: + Julkaisu ei ole avoimesti saatavilla kustantajan palvelussa.

- Ei avoin: - Julkaisu ei ole ilmestynyt avoimessa julkaisukanavassa, eikä julkaisua ole saatavilla avoimena rinnakkaistallenteena. Huom. myös julkaisut jotka ovat tilapäisesti avoimesti saatavilla, esimerkiksi ajankohtaisen yhteiskunnallisesti merkittävän aiheen tai kustantajan markkinointikampanjan takia, tulevat näkyville tämän kategorian kautta. + Ei tietoa: + Tietoa ei ole raportoitu.

-

- Ei tietoa: - Julkaisun avoimen saatavuuden tilaa ei ole raportoitu. -

@@ -448,7 +435,31 @@

Rajaa hakua

- TODO +

+ Kokonaan avoin julkaisukanava: + Julkaisukanavan kaikki julkaisut ovat pysyvästi avoimesti saatavilla. +

+ +

+ Osittain avoin julkaisukanava: + Julkaisukanavassa vain osa julkaisuista on pysyvästi avoimesti saatavilla. +

+ +

+ Viivästetysti avoin julkaisukanava: + Julkaisukanavan tieteelliset artikkelit avataan kustantajan määrittelemän viiveajan jälkeen. +

+ +

+ Ei vastausta: + Julkaisukanava ei ole kokonaan, osittain tai viivästetysti avoin. +

+ +

+ Ei tietoa: + Tietoa ei ole raportoitu. +

+
@@ -468,7 +479,10 @@

Rajaa hakua

- TODO +

+ Rinnakkaistallennettu: + Julkaisu on tallennettu organisaatio- tai tieteenalakohtaiseen julkaisuarkistoon joko välittömästi tai kustantajan määrittämän kohtuullisen embargoajan jälkeen. +

diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 108b79407..44d5fd5ff 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -67,8 +67,8 @@ export class Publications2Component implements OnDestroy { // TODO: tarkista vielä nämä openAccess: $localize`:@@openAccess:Avoin saatavuus`, - publisherOpenAccess: $localize`:@@publisherOpenAccess:Avoin saatavuus kustantajan palvelussa`, - selfArchivedCode: $localize`:@@selfArchivedCode:selfArchivedCode TODO`, + publisherOpenAccess: $localize`:@@publisherOpenAccess:Julkaisukanavan avoin saatavuus`, + selfArchivedCode: $localize`:@@selfArchivedCode:Rinnakkaistallennettu`, } publicationTypeLabels = [ diff --git a/src/app/portal/portal-routing.module.ts b/src/app/portal/portal-routing.module.ts index 6e05f0a52..ec32aebdd 100644 --- a/src/app/portal/portal-routing.module.ts +++ b/src/app/portal/portal-routing.module.ts @@ -102,9 +102,6 @@ const routes: Routes = [ { path: 'results/publications2', component: Publications2Component, - /*resolve: { // TODO Delete - publications: PublicationsResolver - }*/ }, { path: 'results/:tab', diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index f8bf5b640..d45605830 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -679,7 +679,7 @@ function generateAggregationStep(name: SearchParamKey, lookup: RecordInternational + + + + Avoimesti saatavilla: + Open access: + + + + Julkaisu on avoimesti saatavilla kustantajan palvelussa. + The publication is openly available in the publisher's service. + + + + Ei avoin: + Not open: + + + + Julkaisu ei ole avoimesti saatavilla kustantajan palvelussa. + The publication is not openly available in the publisher's service. + + + + Ei tietoa: + Unknown: + + + + Tietoa ei ole raportoitu. + No information has been reported. + + + + + + Kokonaan avoin julkaisukanava: + Fully open publication channel: + + + + Julkaisukanavan kaikki julkaisut ovat pysyvästi avoimesti saatavilla. + All publications are permanently openly available. + + + + Osittain avoin julkaisukanava: + Partially open publication channel: + + + + Julkaisukanavassa vain osa julkaisuista on pysyvästi avoimesti saatavilla. + Only some of the publications are permanently openly available. + + + + Viivästetysti avoin julkaisukanava: + Delayed open publication channel: + + + + Julkaisukanavan tieteelliset artikkelit avataan kustantajan määrittelemän viiveajan jälkeen. + Scientific articles are opened after a delay time defined by the publisher. + + + + Ei vastausta: + No response: + + + + Julkaisukanava ei ole kokonaan, osittain tai viivästetysti avoin. + The publication channel is not fully, partially, or delayed open. + + + + Ei tietoa: + Unknown: + + + + Tietoa ei ole raportoitu. + No information has been reported. + + + + + + Rinnakkaistallennettu: + Self-archived: + + + + Julkaisu on tallennettu organisaatio- tai tieteenalakohtaiseen julkaisuarkistoon joko välittömästi tai kustantajan määrittämän kohtuullisen embargoajan jälkeen. + The publication has been published in a publication archive specific to an organization or field of science either immediately or after a reasonable embargo period set by the publisher. + + + diff --git a/src/i18n/messages.sv.xlf b/src/i18n/messages.sv.xlf index d4c1114d8..370aae3e3 100644 --- a/src/i18n/messages.sv.xlf +++ b/src/i18n/messages.sv.xlf @@ -6519,6 +6519,101 @@ uppgifter till datalagret för forskningsinformation. De gemensamma personuppgif Internationell + + + + Avoimesti saatavilla: + Öppet tillgänglig: + + + + Julkaisu on avoimesti saatavilla kustantajan palvelussa. + Publikationen är öppet tillgänglig i förlagets tjänst. + + + + Ei avoin: + Inte öppen: + + + + Julkaisu ei ole avoimesti saatavilla kustantajan palvelussa. + Publikationen är inte öppet tillgänglig i förlagets tjänst. + + + + Ei tietoa: + Okänd: + + + + Tietoa ei ole raportoitu. + Ingen information har rapporterats. + + + + + + Kokonaan avoin julkaisukanava: + Helt öppen publikationskanal: + + + + Julkaisukanavan kaikki julkaisut ovat pysyvästi avoimesti saatavilla. + Alla publikationer är permanent öppet tillgängliga. + + + + Osittain avoin julkaisukanava: + Delvis öppen publikationskanal: + + + + Julkaisukanavassa vain osa julkaisuista on pysyvästi avoimesti saatavilla. + Endast en del av publikationerna är permanent öppet tillgängliga. + + + + Viivästetysti avoin julkaisukanava: + Fördröjd öppen publiceringskanal: + + + + Julkaisukanavan tieteelliset artikkelit avataan kustantajan määrittelemän viiveajan jälkeen. + Vetenskapliga artiklar öppnas efter en fördröjningstid som definieras av förlaget. + + + + Ei vastausta: + Inget svar: + + + + Julkaisukanava ei ole kokonaan, osittain tai viivästetysti avoin. + Publikationskanalen är inte helt, delvis eller fördröjd öppen. + + + + Ei tietoa: + Okänd: + + + + Tietoa ei ole raportoitu. + Ingen information har rapporterats. + + + + + + Rinnakkaistallennettu: + Parallellsparad: + + + + Julkaisu on tallennettu organisaatio- tai tieteenalakohtaiseen julkaisuarkistoon joko välittömästi tai kustantajan määrittämän kohtuullisen embargoajan jälkeen. + Publikationen har publicerats i ett organisations- eller vetenskapsområdesspecifikt publikationsarkiv antingen omedelbart eller efter en av förläggaren fastställd skälig embargotid. + diff --git a/src/i18n/messages.xlf b/src/i18n/messages.xlf index 16b43819b..ddfced27a 100644 --- a/src/i18n/messages.xlf +++ b/src/i18n/messages.xlf @@ -2552,6 +2552,83 @@ Kansainvälinen + + + + Avoimesti saatavilla: + + + + Julkaisu on avoimesti saatavilla kustantajan palvelussa. + + + + Ei avoin: + + + + Julkaisu ei ole avoimesti saatavilla kustantajan palvelussa. + + + + Ei tietoa: + + + + Tietoa ei ole raportoitu. + + + + + + Kokonaan avoin julkaisukanava: + + + + Julkaisukanavan kaikki julkaisut ovat pysyvästi avoimesti saatavilla. + + + + Osittain avoin julkaisukanava: + + + + Julkaisukanavassa vain osa julkaisuista on pysyvästi avoimesti saatavilla. + + + + Viivästetysti avoin julkaisukanava: + + + + Julkaisukanavan tieteelliset artikkelit avataan kustantajan määrittelemän viiveajan jälkeen. + + + + Ei vastausta: + + + + Julkaisukanava ei ole kokonaan, osittain tai viivästetysti avoin. + + + + Ei tietoa: + + + + Tietoa ei ole raportoitu. + + + + + + Rinnakkaistallennettu: + + + + Julkaisu on tallennettu organisaatio- tai tieteenalakohtaiseen julkaisuarkistoon joko välittömästi tai kustantajan määrittämän kohtuullisen embargoajan jälkeen. + From 1a853892d92395dd9f45fbdb5e460c8933710059 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Tue, 6 Feb 2024 17:16:41 +0200 Subject: [PATCH 23/47] Grid based layout and sortable columns --- .../publications2.component.html | 1101 +++++++++-------- .../publications2.component.scss | 103 +- .../publications2/publications2.component.ts | 85 +- .../portal/services/publication2.service.ts | 52 +- .../column-sorter.component.html | 13 + .../column-sorter.component.scss | 0 .../column-sorter.component.spec.ts | 23 + .../column-sorter/column-sorter.component.ts | 29 + 8 files changed, 845 insertions(+), 561 deletions(-) create mode 100644 src/app/shared/components/column-sorter/column-sorter.component.html create mode 100644 src/app/shared/components/column-sorter/column-sorter.component.scss create mode 100644 src/app/shared/components/column-sorter/column-sorter.component.spec.ts create mode 100644 src/app/shared/components/column-sorter/column-sorter.component.ts diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index 6541d5773..c74382784 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -1,683 +1,744 @@ -
- -
+
-
+ - -
- - -
- {{tab}} -
-
- - -
- {{tab}} -
-
-
+ - -
-
-

Julkaisut - {{total$ | async}}

-
- -
- Näytetään tulokset {{(page - 1) * size + 1}} - {{page * size}} / {{total$ | async}} - - - - tulosta / sivu - - Mitä julkaisutietoja palvelu sisältää? -
-
- -
-
-
- -
- -

Rajaa hakua

-
+
+
- -
- -
- - - - - - +
+ +

Rajaa hakua

+
-
- - Näytä enemmän - + +
- - Näytä vähemmän - -
+
+ + + + + - -
+
+ + Näytä enemmän + - -
+ + Näytä vähemmän + +
+
- +
+
- -
+ +
-
- - - Tilastokeskuksen tieteenalaluokitus. Taiteenalat OKM:n luokituksen mukaisesti. Julkaisulla voi olla 1-6 tieteen- tai taiteenalaa. - + - + +
- - +
+ + + Tilastokeskuksen tieteenalaluokitus. Taiteenalat OKM:n luokituksen mukaisesti. Julkaisulla voi olla 1-6 tieteen- tai taiteenalaa. + - - - - + + + + + + + - - - -
+
+
- -
+
+
+
-
- - - OKM:n julkaisutiedonkeruun mukainen julkaisutyyppi A–G. - + +
- +
+ + + OKM:n julkaisutiedonkeruun mukainen julkaisutyyppi A–G. + - - + - - - - + + + + + - - - - -
- -
- -
-
- - -

- Artikkeli: - Sisältää alkuperäis- ja katsausartikkelit, kirjan tai lehden johdannot ja esipuheet, lyhyet tutkimusselostukset, pääkirjoitukset, keskustelupuheenvuorot ja kommentit. -

- -

- Erillisteos: - Sisältää monografiat/kirjat, tutkimus- ja kehitystyöhön perustuva kehittämis- tai tutkimusraportti, selvitykset, ns. white paperit sekä working papers ja discussion papers -tyyppiset julkaisut. -

- - -

- Toimitustyö: - Sisältää useista eri kirjoittajien artikkeleista koostuvan tieteellisen kirjan tai lehden erikoisnumeron toimitustyöt. -

- -

- Abstrakti: - Sisältää konferenssiesitelmien abstraktit sekä laajennetut abstraktit. -

- -

- Posteri: - Sisältää konferenssiesitelmien posterit. -

- -

- Blogikirjoitus: - Sisältää blogimuotoiset julkaisut, joiden julkaisemisesta on päättänyt riippumaton toimituskunta tai joiden julkaisualustalla on ISSN-tunnus. -

-
- - - -
-
-
- -
- -
- - -

- Julkaisukanavan kohdeyleisö -

+
+
+
-

- Tieteellinen julkaisu: - Julkaisut, jotka on tarkoitettu edistämään tiedettä sekä tuottamaan uutta tietoa. -

+ +
+
+
+ +

- Ammatillinen julkaisu: - Julkaisut, jotka levittävät tutkimukseen ja kehitystyöhön perustuvaa tietoa ammattiyhteisön käyttöön. + Artikkeli: + Sisältää alkuperäis- ja katsausartikkelit, kirjan tai lehden johdannot ja esipuheet, lyhyet tutkimusselostukset, pääkirjoitukset, keskustelupuheenvuorot ja kommentit.

- Yleistajuinen julkaisu: - Julkaisut, jotka levittävät tutkimus- ja kehitystyöhön perustuvaa tietoa suurelle yleisölle ja joiden sisällön ymmärtäminen ei edellytä erityistä perehtyneisyyttä alaan. + Erillisteos: + Sisältää monografiat/kirjat, tutkimus- ja kehitystyöhön perustuva kehittämis- tai tutkimusraportti, selvitykset, ns. white paperit sekä working papers ja discussion papers -tyyppiset julkaisut.

-
- - - - -
-
- - - -
-
- -

- Lehti: - sisältää tieteelliset aikakauslehdet ja ammattilehdet. + Toimitustyö: + Sisältää useista eri kirjoittajien artikkeleista koostuvan tieteellisen kirjan tai lehden erikoisnumeron toimitustyöt.

- Kokoomateos: - Sisältää tieteelliset kokoomateokset, tieteelliset vuosikirjat ja vastaavat, ammatilliset käsi- tai opaskirjat, ammatilliset tietojärjestelmät tai kokoomateokset, oppikirja-aineistot sekä lyhyet ensyklopediatekstit. + Abstrakti: + Sisältää konferenssiesitelmien abstraktit sekä laajennetut abstraktit.

- Konferenssi: - Sisältää konferenssin painetut tai julkisesti saatavilla olevat julkaisut, ns. proceedings-julkaisut. + Posteri: + Sisältää konferenssiesitelmien posterit.

- Verkkoalusta: - Sisältää muilla sähköisillä alustoilla julkaistut julkaisut. + Blogikirjoitus: + Sisältää blogimuotoiset julkaisut, joiden julkaisemisesta on päättänyt riippumaton toimituskunta tai joiden julkaisualustalla on ISSN-tunnus.

- - + +
+
- -
+ +
-
- - -

- Alkuperäisartikkeli: +

+ + +

+ Julkaisukanavan kohdeyleisö +

+ +

+ Tieteellinen julkaisu: + Julkaisut, jotka on tarkoitettu edistämään tiedettä sekä tuottamaan uutta tietoa. +

+ +

+ Ammatillinen julkaisu: + Julkaisut, jotka levittävät tutkimukseen ja kehitystyöhön perustuvaa tietoa ammattiyhteisön käyttöön. +

+ +

+ Yleistajuinen julkaisu: + Julkaisut, jotka levittävät tutkimus- ja kehitystyöhön perustuvaa tietoa suurelle yleisölle ja joiden sisällön ymmärtäminen ei edellytä erityistä perehtyneisyyttä alaan. +

+
- on pääosin aiemmin julkaisemattomasta materiaalista koostuva tieteellinen artikkeli. -

+ + + + +
+
-

- Katsausartikkeli: - perustuu aikaisempiin samasta aihepiiristä tehtyihin julkaisuihin. -

+ +
-

- Data-artikkeli: +

+ + +

+ Lehti: + sisältää tieteelliset aikakauslehdet ja ammattilehdet. +

+ +

+ Kokoomateos: + Sisältää tieteelliset kokoomateokset, tieteelliset vuosikirjat ja vastaavat, ammatilliset käsi- tai opaskirjat, ammatilliset tietojärjestelmät tai kokoomateokset, oppikirja-aineistot sekä lyhyet ensyklopediatekstit. +

+ +

+ Konferenssi: + Sisältää konferenssin painetut tai julkisesti saatavilla olevat julkaisut, ns. proceedings-julkaisut. +

+ +

+ Verkkoalusta: + Sisältää muilla sähköisillä alustoilla julkaistut julkaisut. +

+
- sisältää ns. data journals -julkaisuissa ilmestyneet, tutkimusaineistoja kuvailevat artikkelit. -

+ + + + +
+
-

- Muu artikkeli: + +

- sisältää muihin luokkiin kuulumattomat artikkelit. -

-
+
+ + +

+ Alkuperäisartikkeli: + on pääosin aiemmin julkaisemattomasta materiaalista koostuva tieteellinen artikkeli. +

- - - - -
-
+

+ Katsausartikkeli: - -

+ perustuu aikaisempiin samasta aihepiiristä tehtyihin julkaisuihin. +

-
- - Tieteellisten julkaisujen vertaisarvioinnilla tarkoitetaan menettelyä, jossa tutkimustuloksia julkaiseva lehti, konferenssi tai kirjakustantaja pyytää tieteenalan asiantuntijoita suorittamaan ennakkoarvion julkaistavaksi tarjottujen käsikirjoitusten tieteellisestä julkaisukelpoisuudesta. +

+ Data-artikkeli: - - - - - -

+ sisältää ns. data journals -julkaisuissa ilmestyneet, tutkimusaineistoja kuvailevat artikkelit. +

- -
+

+ Muu artikkeli: -

- - - Kotimaisen julkaisun kustantaja on suomalainen tai se on ensisijaisesti julkaistu Suomessa. Kansainvälisen julkaisun kustantaja ei ole suomalainen tai se on ensisijaisesti julkaistu muualla kuin Suomessa. - + sisältää muihin luokkiin kuulumattomat artikkelit. +

+ - - - - -
-
- -
+ + + + +
+
-
- - - Kieli, jolla julkaisu on kirjoitettu. - + +
- - - - - +
+ + Tieteellisten julkaisujen vertaisarvioinnilla tarkoitetaan menettelyä, jossa tutkimustuloksia julkaiseva lehti, konferenssi tai kirjakustantaja pyytää tieteenalan asiantuntijoita suorittamaan ennakkoarvion julkaistavaksi tarjottujen käsikirjoitusten tieteellisestä julkaisukelpoisuudesta. -
- - Näytä enemmän - + + + + + +
- - Näytä vähemmän - -
-
+ +
+
+ + + Kotimaisen julkaisun kustantaja on suomalainen tai se on ensisijaisesti julkaistu Suomessa. Kansainvälisen julkaisun kustantaja ei ole suomalainen tai se on ensisijaisesti julkaistu muualla kuin Suomessa. + - -
+ + + + +
+
- -
+ +
-
- - - Julkaisufoorumin (www.julkaisufoorumi.fi) mukainen julkaisukanavan (kirjakustantaja, konferenssi tai julkaisusarja) tasoluokitus: 1 = perustaso, 2 = johtava taso, 3 = korkein taso. Tasolla 0 ovat kanavat, jotka eivät joltain osin täytä tason 1 vaatimuksia tai ovat uusia. Julkaisufoorumitaso määräytyy julkaisun julkaisuvuoden mukaan. - +
+ + + Kieli, jolla julkaisu on kirjoitettu. + - - + + + - -
- -
+
+ + Näytä enemmän + -
- - - -

- Avoimesti saatavilla: - Julkaisu on avoimesti saatavilla kustantajan palvelussa. -

+ + Näytä vähemmän + +
+ -

- Ei avoin: - Julkaisu ei ole avoimesti saatavilla kustantajan palvelussa. -

-

- Ei tietoa: - Tietoa ei ole raportoitu. -

+ +
- + +
- - - - -
-
+
+ + + Julkaisufoorumin (www.julkaisufoorumi.fi) mukainen julkaisukanavan (kirjakustantaja, konferenssi tai julkaisusarja) tasoluokitus: 1 = perustaso, 2 = johtava taso, 3 = korkein taso. Tasolla 0 ovat kanavat, jotka eivät joltain osin täytä tason 1 vaatimuksia tai ovat uusia. Julkaisufoorumitaso määräytyy julkaisun julkaisuvuoden mukaan. + - -
+ + + + +
+
-
- - -

- Kokonaan avoin julkaisukanava: - Julkaisukanavan kaikki julkaisut ovat pysyvästi avoimesti saatavilla. -

+ +
-

- Osittain avoin julkaisukanava: - Julkaisukanavassa vain osa julkaisuista on pysyvästi avoimesti saatavilla. -

+
+ + -

- Viivästetysti avoin julkaisukanava: - Julkaisukanavan tieteelliset artikkelit avataan kustantajan määrittelemän viiveajan jälkeen. -

+

+ Avoimesti saatavilla: + Julkaisu on avoimesti saatavilla kustantajan palvelussa. +

-

- Ei vastausta: - Julkaisukanava ei ole kokonaan, osittain tai viivästetysti avoin. -

+

+ Ei avoin: + Julkaisu ei ole avoimesti saatavilla kustantajan palvelussa. +

-

- Ei tietoa: - Tietoa ei ole raportoitu. -

+

+ Ei tietoa: + Tietoa ei ole raportoitu. +

-
+ - - - - -
-
+ + + + +
+
- -
+ +
- -
- - -

- Rinnakkaistallennettu: - Julkaisu on tallennettu organisaatio- tai tieteenalakohtaiseen julkaisuarkistoon joko välittömästi tai kustantajan määrittämän kohtuullisen embargoajan jälkeen. -

-
+
+ + +

+ Kokonaan avoin julkaisukanava: + Julkaisukanavan kaikki julkaisut ovat pysyvästi avoimesti saatavilla. +

+ +

+ Osittain avoin julkaisukanava: + Julkaisukanavassa vain osa julkaisuista on pysyvästi avoimesti saatavilla. +

+ +

+ Viivästetysti avoin julkaisukanava: + Julkaisukanavan tieteelliset artikkelit avataan kustantajan määrittelemän viiveajan jälkeen. +

+ +

+ Ei vastausta: + Julkaisukanava ei ole kokonaan, osittain tai viivästetysti avoin. +

+ +

+ Ei tietoa: + Tietoa ei ole raportoitu. +

- - - - -
-
+ + + + + +
-
-
+ +
+ +
+ + +

+ Rinnakkaistallennettu: + Julkaisu on tallennettu organisaatio- tai tieteenalakohtaiseen julkaisuarkistoon joko välittömästi tai kustantajan määrittämän kohtuullisen embargoajan jälkeen. +

+
- -
-
Rajaukset ({{filterCount$ | async}}):
+ + + + + +
- - - +
+
-
-
{{yearFilter.year}}
- -
+
+ +
+
Rajaukset ({{filterCount$ | async}}):
+ + + + + +
+
{{yearFilter.year}}
+ +
-
+ - - - + + + -
-
{{organizationFilter.name}}
- -
+
+
{{organizationFilter.name}}
+ +
-
+
- - - + + + -
-
{{languageCodeFilter.name}}
- -
+
+
{{languageCodeFilter.name}}
+ +
-
+
- - - + + + -
-
{{publicationFormatFilter.name}}
- -
+
+
{{publicationFormatFilter.name}}
+ +
-
+
- - - + + + -
-
{{publicationAudienceFilter.name}}
- -
+
+
{{publicationAudienceFilter.name}}
+ +
-
+
- - - + + + -
-
{{peerReviewedFilter.name}}
- -
+
+
{{peerReviewedFilter.name}}
+ +
-
+
- - - + + + -
-
{{parentPublicationTypeFilter.name}}
- -
+
+
{{parentPublicationTypeFilter.name}}
+ +
-
+
- - - + + + -
-
{{internationalPublicationFilter.name}}
- -
+
+
{{internationalPublicationFilter.name}}
+ +
-
+
- - - + + + -
-
{{articleTypeCodeFilter.name}}
- -
+
+
{{articleTypeCodeFilter.name}}
+ +
-
+
- - - + + + -
-
{{jufoClassCodeFilter.id}}
- -
+
+
{{jufoClassCodeFilter.id}}
+ +
-
-
+
+
+ +
+ +
+
+ + + + + + + -
- - -
Julkaisun nimi
-
- - - -
-
-
-
+ + +
+
+ Julkaisun nimi +
- - - Tekijät - + +
+
- -
-
-
+ + +
+
+
+ - - - Julkaisukanava - + + - -
-
-
+
+
Tekijät
+ + +
+ + + + +
+
+
+ + + + +
+
+ Julkaisukanava +
+ + +
+ +
+ + +
+
+
+ + + + +
+
+ Julkaisuvuosi +
+ + +
- - - Julkaisuvuosi - +
- -
-
+ +
+
+
+ + + +
+
+ +
+
+ +
+
+ +
+ + + + + + +
+ + +
+ + diff --git a/src/app/portal/components/results/publications2/publications2.component.scss b/src/app/portal/components/results/publications2/publications2.component.scss index 99a7bdac8..40171bc50 100644 --- a/src/app/portal/components/results/publications2/publications2.component.scss +++ b/src/app/portal/components/results/publications2/publications2.component.scss @@ -1,13 +1,76 @@ +.search-container { + display: grid; + + grid-template-rows: + auto + minmax(0, auto) + // minmax(0, auto) + auto; + + // grid-template-columns: 0px 350px 1fr 100px 0px; + // grid-template-columns: 100px 350px auto auto 100px; + // grid-template-columns: 1fr 350px 4fr 4fr 1fr; + // grid-template-columns: 1fr 350px minmax(0, 1fr) minmax(0, 1fr) 1fr; + // grid-template-columns: minmax(0, 1fr) 350px minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr); + // grid-template-columns: minmax(0, 1fr) 350px minmax(0, 2fr) minmax(0, 2fr) minmax(0, 1fr); + + grid-template-columns: 1fr 350px 1100px 1fr; + + grid-template-areas: + "search search search search" + ". filters summary ." + ". filters results ." + ". filters pagination ."; +} + +@media (max-width: 1449px) { + .search-container { + grid-template-columns: 350px 1fr; + + grid-template-areas: + "search search" + "filters summary" + "filters results" + "filters pagination"; + } +} + +.search-bar { + grid-area: search; +} + +.search-filters { + grid-area: filters; +} + +.search-summary { + grid-area: summary; +} + +.search-results { + grid-area: results; +} + +.search-pagination { + grid-area: pagination; + + display: flex; + justify-content: center; +} + /* Ensure the table takes the full width */ cdk-table { - width: 100%; + // width: 100%; } /* Add some spacing and border to the cells */ cdk-header-cell, cdk-cell { - padding: 16px; - border-bottom: 1px solid #e0e0e0; + // padding: 16px; + padding: 0.5rem; + // border-bottom: 1px solid #e0e0e0; display: table-cell; /* This ensures they behave like traditional table cells */ + + text-align: start; } /* Define the header row and data row to behave like traditional table rows */ @@ -16,27 +79,27 @@ cdk-header-row, cdk-row { } /* Define columns to ensure they take equal widths - adjust as required */ -[cdkColumnDef="title"] { - width: 25%; -} +/*[cdkColumnDef="title"] { + // width: 25%; +}*/ -[cdkColumnDef="authors"] { - width: 25%; -} +/*[cdkColumnDef="authors"] { + // width: 25%; +}*/ -[cdkColumnDef="journalName"] { - width: 25%; -} +/*[cdkColumnDef="journalName"] { + // width: 25%; +}*/ -[cdkColumnDef="publicationYear"] { - width: 25%; -} +/*[cdkColumnDef="publicationYear"] { + // width: 25%; +}*/ -em { - background-color: #FFFF00; - font-style: normal; - padding: 0.1em 0; -} +/*em { + background-color: #FFFF00; + font-style: normal; + padding: 0.1em 0; +}*/ .filters-summary { background-color: #e8e8f5; diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 44d5fd5ff..7dd6807b3 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -1,21 +1,27 @@ -import { Component, inject, OnDestroy, Pipe, PipeTransform } from '@angular/core'; +import { Component, inject, OnDestroy } from '@angular/core'; import { CdkTableModule, DataSource } from '@angular/cdk/table'; -import { BehaviorSubject, combineLatest, merge, Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { ActivatedRoute, Router, RouterLink, RouterModule } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { AsyncPipe, JsonPipe, NgForOf, NgIf, NgStyle } from '@angular/common'; import { - getArticleTypeCodeAdditions, getFieldsOfScienceAdditions, - getPublisherInternationalityAdditions, getJufoClassCodeAdditions, + getArticleTypeCodeAdditions, + getFieldsOfScienceAdditions, + getJufoClassCodeAdditions, getLanguageCodeAdditions, + getOpenAccessAdditions, getOrganizationAdditions, getParentPublicationTypeAdditions, getPeerReviewedAdditions, getPublicationAudienceAdditions, - getPublicationFormatAdditions, getPublicationTypeCodeAdditions, + getPublicationFormatAdditions, + getPublicationTypeCodeAdditions, + getPublisherInternationalityAdditions, + getPublisherOpenAccessCodeAdditions, + getSelfArchivedCodeAdditions, getYearAdditions, HighlightedPublication, - Publication2Service, getOpenAccessAdditions, getPublisherOpenAccessCodeAdditions, getSelfArchivedCodeAdditions + Publication2Service, SearchParams } from '@portal/services/publication2.service'; import { map, take, tap } from 'rxjs/operators'; import { SharedModule } from '@shared/shared.module'; @@ -29,6 +35,8 @@ import { MatButtonModule } from '@angular/material/button'; import { FilterLimitButtonComponent } from '@portal/components/filter-limit-button/filter-limit-button.component'; import { FirstDigitPipe } from '@shared/pipes/first-digit.pipe'; import { FirstLetterPipe } from '@shared/pipes/first-letter.pipe'; +import { BreakpointObserver, LayoutModule } from '@angular/cdk/layout'; +import { ColumnSorterComponent } from '@shared/components/column-sorter/column-sorter.component'; @Component({ selector: 'app-publications2', @@ -38,7 +46,8 @@ import { FirstLetterPipe } from '@shared/pipes/first-letter.pipe'; SharedModule, //TODO not good? FormsModule, RouterModule, - SearchBar2Component, OrganizationFilterComponent, FilterOptionComponent, CollapsibleComponent, MatButtonModule, NgStyle, FilterLimitButtonComponent, FirstDigitPipe, FirstLetterPipe, RouterLink + SearchBar2Component, OrganizationFilterComponent, FilterOptionComponent, CollapsibleComponent, MatButtonModule, NgStyle, FilterLimitButtonComponent, FirstDigitPipe, FirstLetterPipe, RouterLink, + LayoutModule, ColumnSorterComponent ], standalone: true }) @@ -47,10 +56,14 @@ export class Publications2Component implements OnDestroy { router = inject(Router); publications2Service = inject(Publication2Service); + breakpointObserver = inject(BreakpointObserver); + keywords = ""; page = 1; size = 10; + sort = ""; + labelText = { yearOfPublication: $localize`:@@yearOfPublication:Julkaisuvuosi`, organization: $localize`:@@organization:Organisaatio`, @@ -64,8 +77,6 @@ export class Publications2Component implements OnDestroy { publisherInternationality: $localize`:@@publisherInternationality:Kustantajan kansainvälisyys`, language: $localize`:@@language:Kieli`, jufoLevel: $localize`:@@jufoLevel:Julkaisufoorumitaso`, - - // TODO: tarkista vielä nämä openAccess: $localize`:@@openAccess:Avoin saatavuus`, publisherOpenAccess: $localize`:@@publisherOpenAccess:Julkaisukanavan avoin saatavuus`, selfArchivedCode: $localize`:@@selfArchivedCode:Rinnakkaistallennettu`, @@ -93,7 +104,7 @@ export class Publications2Component implements OnDestroy { this.filterCount$.next(count); } - displayedColumns: string[] = ['publicationName', 'authorsText', 'publisherName', 'publicationYear']; + displayedColumns: string[] = ['icon', 'publicationName', 'authorsText', 'publisherName', 'publicationYear']; highlights$ = this.publications2Service.getSearch(); // TODO: /*: Observable*/ dataSource = new PublicationDataSource(this.highlights$); @@ -102,7 +113,11 @@ export class Publications2Component implements OnDestroy { aggregations$ = this.publications2Service.getAggregations(); yearAdditions$ = this.aggregations$.pipe( - map(aggs => getYearAdditions(aggs).map((bucket: any) => ({ year: bucket.key.toString(), count: bucket.doc_count })) ?? []), + map(aggs => + getYearAdditions(aggs).map( + (bucket: any) => ({ year: bucket.key.toString(), count: bucket.doc_count }) + ) ?? [] + ), map(aggs => aggs.sort((a, b) => b.year - a.year)) ); @@ -384,6 +399,13 @@ export class Publications2Component implements OnDestroy { language: 10, }; + breakpointSubscription = this.breakpointObserver.observe('(max-width: 1199px)').subscribe(result => { + if (result.matches) { + this.displayedColumns = ['icon', 'publicationName', 'authorsText', 'publicationYear']; + } else { + this.displayedColumns = ['icon', 'publicationName', 'authorsText', 'publisherName', 'publicationYear']; + } + }); searchParamsSubscription = this.searchParams$.subscribe(searchParams => { this.publications2Service.updateSearchTerms(searchParams); @@ -391,12 +413,14 @@ export class Publications2Component implements OnDestroy { this.keywords = searchParams.q?.[0] ?? ""; this.page = parseInt(searchParams.page?.[0] ?? "1"); this.size = parseInt(searchParams.size?.[0] ?? "10"); + this.sort = searchParams.sort?.[0] ?? ""; }); total$ = this.publications2Service.getTotal(); ngOnDestroy() { this.searchParamsSubscription.unsubscribe(); + this.breakpointSubscription.unsubscribe(); } toggleParam(key: string, value: string) { @@ -426,6 +450,45 @@ export class Publications2Component implements OnDestroy { }); } + setParam(key: string, value: string) { + this.searchParams$.pipe(take(1)).subscribe(filterParams => { + const queryParams = { ...filterParams }; + + queryParams[key] = [value]; + + this.router.navigate([], { + relativeTo: this.route, + queryParams: concatFields(queryParams) + }); + }); + } + + clearParam(key: string) { + this.searchParams$.pipe(take(1)).subscribe(filterParams => { + const queryParams = { ...filterParams }; + delete queryParams[key]; + + this.router.navigate([], { + relativeTo: this.route, + queryParams: concatFields(queryParams) + }); + }); + } + + toggleSort(field: string) { + this.searchParams$.pipe(take(1)).subscribe(params => { + const existingSort = params.sort?.pop() ?? ""; + + if (existingSort === field + "Desc") { + this.clearParam("sort"); + } else if (existingSort === field) { + this.setParam("sort", field + "Desc"); + } else { + this.setParam("sort", field); + } + }); + } + searchKeywords(keywords: string) { this.router.navigate([], { relativeTo: this.route, diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index d45605830..7b2fa84cb 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -36,10 +36,12 @@ type CachedPublicationSearch = { publicationSearch: PublicationSearch, } -type SearchParams = { +export type SearchParams = { q?: string[], page?: string[], size?: string[], + sort?: string[], + year?: string[], organization?: string[], @@ -75,7 +77,7 @@ export class Publication2Service { searchUrl = 'https://researchfi-api-qa.rahtiapp.fi/portalapi/publication/_search?' - searchParams = new BehaviorSubject>({}); + searchParams = new BehaviorSubject({}); searchResults$: Observable = this.searchParams.pipe( switchMap(searchParams => this.searchPublications(searchParams)), @@ -108,25 +110,26 @@ export class Publication2Service { return this.publicationAggregations$; } - updateSearchTerms(searchParams: Record): void { + updateSearchTerms(searchParams: SearchParams): void { this.searchParams.next(searchParams); } - private searchPublications(searchParams: Record): Observable { + private searchPublications(searchParams: SearchParams): Observable { const q = searchParams.q?.[0] ?? ""; const page = parseInt(searchParams.page?.[0] ?? "1"); const size = parseInt(searchParams.size?.[0] ?? "10"); const from = (page - 1) * size; - searchParams = searchParams as SearchParams; // TODO no effect? + // searchParams = searchParams as SearchParams; // TODO no effect? return this.http.post(this.searchUrl, { from: from, size: size, track_total_hits: true, sort: [ - "_score", - { "publicationYear": { "order": "desc" } } + // "_score", + // { "publicationYear": { "order": "desc" } } + ...sortingTerms(searchParams) ], query: { bool: { @@ -153,11 +156,14 @@ export class Publication2Service { post_tags: [""] }, aggregations: { - ... aggregationTerms(searchParams), + ...aggregationTerms(searchParams), } }); } + // sort column? + // sort=foo OR fooDesc + createHighlightedPublications(searchData: PublicationSearch): HighlightedPublication[] { return searchData.hits.hits.map((hit: any/*ES doc for Publication*/) => this.createHighlightedPublication(hit)); } @@ -731,17 +737,24 @@ function generateAggregationStep(name: SearchParamKey, lookup: Record; +type SearchParamKey = Exclude; const lookup: Record = { year: {fieldName: "publicationYear", fieldPath: "publicationYear"}, @@ -984,6 +997,25 @@ function additionFilterTerms(excluded: SearchParamKey, searchParams: SearchParam ]; } +function sortingTerms(searchParams: SearchParams) { + let sortedField = "publicationYear"; + let direction = "desc"; + + if (searchParams.sort != null) { + const value = searchParams.sort.pop(); + + sortedField = value.replace(/Desc$/, ""); + direction = value.endsWith("Desc") ? "desc" : "asc"; + + sortedField += ".keyword"; + } + + return [ + "_score", + { [sortedField]: { "order": direction } } + ]; +} + function getOrganizationNameBuckets(response: OrgsAggsResponse) { return response.aggregations.filtered_authors.single_sector.organizations.composite_orgs.buckets; } diff --git a/src/app/shared/components/column-sorter/column-sorter.component.html b/src/app/shared/components/column-sorter/column-sorter.component.html new file mode 100644 index 000000000..793fea2d0 --- /dev/null +++ b/src/app/shared/components/column-sorter/column-sorter.component.html @@ -0,0 +1,13 @@ +
+ + + + + + + + + + + +
diff --git a/src/app/shared/components/column-sorter/column-sorter.component.scss b/src/app/shared/components/column-sorter/column-sorter.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/shared/components/column-sorter/column-sorter.component.spec.ts b/src/app/shared/components/column-sorter/column-sorter.component.spec.ts new file mode 100644 index 000000000..8c8550c48 --- /dev/null +++ b/src/app/shared/components/column-sorter/column-sorter.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ColumnSorterComponent } from './column-sorter.component'; + +describe('ColumnSorterComponent', () => { + let component: ColumnSorterComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ColumnSorterComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ColumnSorterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/column-sorter/column-sorter.component.ts b/src/app/shared/components/column-sorter/column-sorter.component.ts new file mode 100644 index 000000000..5ab1ba7c4 --- /dev/null +++ b/src/app/shared/components/column-sorter/column-sorter.component.ts @@ -0,0 +1,29 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { NgIf, NgSwitch, NgSwitchCase } from '@angular/common'; + +@Component({ + selector: 'app-column-sorter', + templateUrl: './column-sorter.component.html', + styleUrls: ['./column-sorter.component.scss'], + imports: [ + NgSwitch, NgSwitchCase, NgIf + ], + standalone: true +}) +export class ColumnSorterComponent { + @Input() name: string; + @Input() value: string; + + /*@Input() direction: number; + @Output() directionChange = new EventEmitter(); + + toggleSort() { + if (this.direction === 0) { + this.directionChange.emit(1); + } else if (this.direction === 1) { + this.directionChange.emit(-1); + } else { + this.directionChange.emit(0); + } + }*/ +} From 0176ab043859338e31759bf0539cdb72ed66f587 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Thu, 8 Feb 2024 12:14:45 +0200 Subject: [PATCH 24/47] Adjust breakpoints and add badges to the search listing --- .../publications2.component.html | 135 ++++++++++++------ .../publications2.component.scss | 54 +++++-- .../publications2/publications2.component.ts | 10 +- .../portal/services/publication2.service.ts | 62 +++++--- 4 files changed, 183 insertions(+), 78 deletions(-) diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index c74382784..8f8bda828 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -6,6 +6,46 @@
+
+ +
+ + +
+ {{tab}} +
+
+ + +
+ {{tab}} +
+
+
+
+
+ +
+
+
+

Julkaisut - {{total$ | async}}

+
+ +
+ Näytetään tulokset {{(page - 1) * size + 1}} - {{page * size}} / {{total$ | async}} + + + + tulosta / sivu + + Mitä julkaisutietoja palvelu sisältää? +
+
+
+
@@ -598,12 +638,15 @@

Rajaa hakua

- + + + + - +
Julkaisun nimi @@ -614,9 +657,53 @@

Rajaa hakua

+ +
+ + + + + + + + +
+ +
+
+ + +
+ +
+
+ + +
+ +
+
+
@@ -678,61 +765,19 @@

Rajaa hakua

-
+
- - - -
diff --git a/src/app/portal/components/results/publications2/publications2.component.scss b/src/app/portal/components/results/publications2/publications2.component.scss index 40171bc50..2735037c9 100644 --- a/src/app/portal/components/results/publications2/publications2.component.scss +++ b/src/app/portal/components/results/publications2/publications2.component.scss @@ -4,7 +4,7 @@ grid-template-rows: auto minmax(0, auto) - // minmax(0, auto) + minmax(0, auto) auto; // grid-template-columns: 0px 350px 1fr 100px 0px; @@ -18,27 +18,54 @@ grid-template-areas: "search search search search" + ". tabs tabs ." + ". info info ." ". filters summary ." ". filters results ." ". filters pagination ."; } -@media (max-width: 1449px) { +@media (max-width: 1200px) { .search-container { grid-template-columns: 350px 1fr; grid-template-areas: "search search" + "tabs tabs" + "info info" "filters summary" "filters results" "filters pagination"; } } +@media (max-width: 990px) { + .search-container { + grid-template-columns: 1fr; + + grid-template-areas: + "search" + "tabs" + "info" + "summary" + "filters" + "results" + "pagination"; + } +} + .search-bar { grid-area: search; } +.search-tabs { + grid-area: tabs; +} + +.search-info { + grid-area: info; +} + .search-filters { grid-area: filters; } @@ -79,20 +106,21 @@ cdk-header-row, cdk-row { } /* Define columns to ensure they take equal widths - adjust as required */ -/*[cdkColumnDef="title"] { - // width: 25%; -}*/ +/*[cdkColumnDef="publicationName"] { + width: 35%; + background-color: red; +} -/*[cdkColumnDef="authors"] { - // width: 25%; -}*/ +[cdkColumnDef="authorsText"] { + width: 25%; +} -/*[cdkColumnDef="journalName"] { - // width: 25%; -}*/ +[cdkColumnDef="publisherName"] { + width: 20%; +} -/*[cdkColumnDef="publicationYear"] { - // width: 25%; +[cdkColumnDef="publicationYear"] { + width: 20%; }*/ /*em { diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 7dd6807b3..c6148d37d 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -399,11 +399,13 @@ export class Publications2Component implements OnDestroy { language: 10, }; - breakpointSubscription = this.breakpointObserver.observe('(max-width: 1199px)').subscribe(result => { - if (result.matches) { + breakpointSubscription = this.breakpointObserver.observe(['(max-width: 1200px)', '(max-width: 990px)']).subscribe(result => { + this.displayedColumns = ['icon', 'publicationName', 'authorsText', 'publisherName', 'publicationYear']; + + if (result.breakpoints['(max-width: 990px)']) { + this.displayedColumns = ['publicationName', 'publicationYear']; + } else if (result.breakpoints['(max-width: 1200px)']) { this.displayedColumns = ['icon', 'publicationName', 'authorsText', 'publicationYear']; - } else { - this.displayedColumns = ['icon', 'publicationName', 'authorsText', 'publisherName', 'publicationYear']; } }); diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 7b2fa84cb..75a205977 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -14,21 +14,29 @@ const path = suffixer(locale); title: string(), });*/ -type PublicationSearch = any; // Output; +type TODO = any; +type PublicationSearch = TODO; // Output; // function that validates API response into a PublicationSearch -function parsePublicationSearch(data: any): PublicationSearch { +function parsePublicationSearch(data: TODO): PublicationSearch { return data; // return parse(PublicationSearchSchema, data); } -export interface HighlightedPublication { +// export interface HighlightedPublication { +export type HighlightedPublication = { id: string; publicationName: SafeHtml; authorsText: SafeHtml; authorsTextSplitted: SafeHtml; publisherName: SafeHtml; publicationYear: SafeHtml; + + badges: { + doi?: string; + peerReviewed?: boolean; + openAccess?: string; + }; } type CachedPublicationSearch = { @@ -91,7 +99,7 @@ export class Publication2Service { publicationSearch$: Observable = this.searchResults$.pipe( map((data) => parsePublicationSearch(data)), - map((publicationSearch: PublicationSearch) => this.createHighlightedPublications(publicationSearch)) + map((publicationSearch: PublicationSearch) => this.createHighlightedPublications(publicationSearch)) ); publicationAggregations$ = this.searchResults$.pipe( @@ -169,21 +177,43 @@ export class Publication2Service { } createHighlightedPublication(searchData: any/*ES doc for Publication*/): HighlightedPublication { - const values = { - publicationName: searchData.highlight?.publicationName?.[0] ?? searchData._source.publicationName ?? '', - authorsText: searchData.highlight?.authorsText?.[0] ?? searchData._source.authorsText ?? '', - authorsTextSplitted: searchData.highlight?.authorsText?.[0] ?? searchData._source.authorsText ?? '', - publisherName: searchData.highlight?.publisherName?.[0] ?? searchData._source.publisherName ?? '', - publicationYear: searchData.highlight?.publicationYear?.[0] ?? searchData._source.publicationYear ?? '' - }; + const source = searchData._source; + const highlight = searchData.highlight; + + /*const badgesExist = { + doi: source.doi != null || source.doiHandle != null, + peerReviewed: source.peerReviewed?.id === 1 ?? false, + openAccess: source.openAccess === 1 + }*/ + + // badges are keys values with strings to strings + + + const badges: { + doi?: string; + peerReviewed?: boolean; + openAccess?: string; + } = {}; + + badges.doi = source.doi; + badges.openAccess = source.doiHandle; + badges.peerReviewed = source.peerReviewed[0].id === "1"; + + const publicationName = highlight?.publicationName?.[0] ?? source.publicationName ?? ''; + const authorsText = highlight?.authorsText?.[0] ?? source.authorsText ?? ''; + const authorsTextSplitted = highlight?.authorsText?.[0] ?? source.authorsText ?? ''; + const publisherName = highlight?.publisherName?.[0] ?? source.publisherName ?? ''; + const publicationYear = highlight?.publicationYear?.[0] ?? source.publicationYear ?? ''; return { id: searchData._id, - publicationName: this.sanitizer.sanitize(SecurityContext.HTML, values.publicationName), - authorsText: this.sanitizer.sanitize(SecurityContext.HTML, values.authorsText), - authorsTextSplitted: this.sanitizer.sanitize(SecurityContext.HTML, values.authorsTextSplitted), - publisherName: this.sanitizer.sanitize(SecurityContext.HTML, values.publisherName), - publicationYear: this.sanitizer.sanitize(SecurityContext.HTML, values.publicationYear) + publicationName: this.sanitizer.sanitize(SecurityContext.HTML, publicationName), + authorsText: this.sanitizer.sanitize(SecurityContext.HTML, authorsText), + authorsTextSplitted: this.sanitizer.sanitize(SecurityContext.HTML, authorsTextSplitted), + publisherName: this.sanitizer.sanitize(SecurityContext.HTML, publisherName), + publicationYear: this.sanitizer.sanitize(SecurityContext.HTML, publicationYear), + + badges: badges }; } From f98dee5ca2e08f37da664cd641114831e43f4d22 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Mon, 12 Feb 2024 14:09:08 +0200 Subject: [PATCH 25/47] Adjust grid cell heights and cell padding --- .../publications2.component.html | 34 ++++++++++--------- .../publications2.component.scss | 5 ++- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index 8f8bda828..2592d5465 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -641,7 +641,7 @@

Rajaa hakua

- + @@ -686,23 +686,25 @@

Rajaa hakua

{{ element.badges | json }}
--> - -
- -
-
+
+ +
+ +
+
- -
- -
-
+ +
+ +
+
- -
- -
-
+ +
+ +
+
+
diff --git a/src/app/portal/components/results/publications2/publications2.component.scss b/src/app/portal/components/results/publications2/publications2.component.scss index 2735037c9..e121b0ff4 100644 --- a/src/app/portal/components/results/publications2/publications2.component.scss +++ b/src/app/portal/components/results/publications2/publications2.component.scss @@ -5,6 +5,8 @@ auto minmax(0, auto) minmax(0, auto) + minmax(0, auto) + auto auto; // grid-template-columns: 0px 350px 1fr 100px 0px; @@ -93,7 +95,8 @@ cdk-table { /* Add some spacing and border to the cells */ cdk-header-cell, cdk-cell { // padding: 16px; - padding: 0.5rem; + padding: 1rem 0.5rem; + // border-bottom: 1px solid #e0e0e0; display: table-cell; /* This ensures they behave like traditional table cells */ From 514bcd198883832b1b99528fe62f11e1b511e739 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Tue, 13 Feb 2024 16:42:55 +0200 Subject: [PATCH 26/47] Fix translations for split open access filters --- .../publications2/publications2.component.ts | 36 +++++++--- .../portal/services/publication2.service.ts | 38 +++++++++++ src/i18n/messages.en.xlf | 64 ++++++++++++++++++ src/i18n/messages.sv.xlf | 65 +++++++++++++++++++ src/i18n/messages.xlf | 54 ++++++++++++++- 5 files changed, 248 insertions(+), 9 deletions(-) diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index c6148d37d..ea1d3834a 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -79,7 +79,7 @@ export class Publications2Component implements OnDestroy { jufoLevel: $localize`:@@jufoLevel:Julkaisufoorumitaso`, openAccess: $localize`:@@openAccess:Avoin saatavuus`, publisherOpenAccess: $localize`:@@publisherOpenAccess:Julkaisukanavan avoin saatavuus`, - selfArchivedCode: $localize`:@@selfArchivedCode:Rinnakkaistallennettu`, + selfArchivedCode: $localize`:@@selfArchivedCode:Rinnakkaistallenne`, } publicationTypeLabels = [ @@ -337,11 +337,14 @@ export class Publications2Component implements OnDestroy { map(aggs => aggs.sort((a, b) => b.count - a.count)) ); - openAccessFilters$ = combineLatest([this.additionsFromOpenAccess$, this.searchParams$.pipe(map(params => params.openAccess ?? []))]).pipe( - map(([additionsFromOpenAccess, enabledFilters]) => additionsFromOpenAccess.map(additionFromOpenAccess => ({ + // names + openAccessNames$ = this.publications2Service.getOpenAccessNames(); + + openAccessFilters$ = combineLatest([this.additionsFromOpenAccess$, this.openAccessNames$, this.searchParams$.pipe(map(params => params.openAccess ?? []))]).pipe( + map(([additionsFromOpenAccess, openAccessNames, enabledFilters]) => additionsFromOpenAccess.map(additionFromOpenAccess => ({ id: additionFromOpenAccess.id, count: additionFromOpenAccess.count, - name: additionFromOpenAccess.id, // TODO TODO TODO TODO + name: openAccessNames[additionFromOpenAccess.id], enabled: enabledFilters.includes(additionFromOpenAccess.id) }))), tap(filters => this.updateFilterCount("openAccess", filters.filter(filter => filter.enabled).length)) @@ -352,11 +355,13 @@ export class Publications2Component implements OnDestroy { map(aggs => aggs.sort((a, b) => b.count - a.count)) ); - publisherOpenAccessFilters$ = combineLatest([this.additionsFromPublisherOpenAccess$, this.searchParams$.pipe(map(params => params.publisherOpenAccessCode ?? []))]).pipe( - map(([additionsFromPublisherOpenAccess, enabledFilters]) => additionsFromPublisherOpenAccess.map(additionFromPublisherOpenAccess => ({ + publisherOpenAccessNames$ = this.publications2Service.getPublisherOpenAccessNames(); + + publisherOpenAccessFilters$ = combineLatest([this.additionsFromPublisherOpenAccess$, this.publisherOpenAccessNames$, this.searchParams$.pipe(map(params => params.publisherOpenAccessCode ?? []))]).pipe( + map(([additionsFromPublisherOpenAccess, publisherOpenAccessNames, enabledFilters]) => additionsFromPublisherOpenAccess.map(additionFromPublisherOpenAccess => ({ id: additionFromPublisherOpenAccess.id, count: additionFromPublisherOpenAccess.count, - name: additionFromPublisherOpenAccess.id, // TODO TODO TODO TODO + name: publisherOpenAccessNames[additionFromPublisherOpenAccess.id], enabled: enabledFilters.includes(additionFromPublisherOpenAccess.id) }))), tap(filters => this.updateFilterCount("publisherOpenAccess", filters.filter(filter => filter.enabled).length)) @@ -369,7 +374,11 @@ export class Publications2Component implements OnDestroy { map(aggs => aggs.sort((a, b) => b.count - a.count)) ); - selfArchivedCodeFilters$ = combineLatest([this.additionsFromSelfArchivedCode$, this.searchParams$.pipe(map(params => params.selfArchiveCode ?? []))]).pipe( + // names + selfArchivedCodeNames$ = this.publications2Service.getSelfArchivedCodeNames(); + + // OLD + /*selfArchivedCodeFilters$ = combineLatest([this.additionsFromSelfArchivedCode$, this.searchParams$.pipe(map(params => params.selfArchiveCode ?? []))]).pipe( map(([additionsFromSelfArchive, enabledFilters]) => additionsFromSelfArchive.map(additionFromSelfArchive => ({ id: additionFromSelfArchive.id, count: additionFromSelfArchive.count, @@ -377,6 +386,17 @@ export class Publications2Component implements OnDestroy { enabled: enabledFilters.includes(additionFromSelfArchive.id) }))), tap(filters => this.updateFilterCount("selfArchiveCode", filters.filter(filter => filter.enabled).length)) + );*/ + + // NEW + selfArchivedCodeFilters$ = combineLatest([this.additionsFromSelfArchivedCode$, this.selfArchivedCodeNames$, this.searchParams$.pipe(map(params => params.selfArchiveCode ?? []))]).pipe( + map(([additionsFromSelfArchivedCode, selfArchivedCodeNames, enabledFilters]) => additionsFromSelfArchivedCode.map(additionFromSelfArchivedCode => ({ + id: additionFromSelfArchivedCode.id, + count: additionFromSelfArchivedCode.count, + name: selfArchivedCodeNames[additionFromSelfArchivedCode.id], + enabled: enabledFilters.includes(additionFromSelfArchivedCode.id) + }))), + tap(filters => this.updateFilterCount("selfArchiveCode", filters.filter(filter => filter.enabled).length)) ); public mainFieldOfScienceName = { diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 75a205977..d81105c6f 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -562,6 +562,44 @@ export class Publication2Service { ...lookup }); } + + getOpenAccessNames(): Observable> { + return of({ + "0": $localize`:@@publication2.openAccess.No:Ei avoin`, + "1": $localize`:@@publication2.openAccess.Yes:Avoimesti saatavilla`, + "9": $localize`:@@publication2.openAccess.Unknown:Ei tietoa` + }); + } + + getPublisherOpenAccessNames(): Observable> { + return of({ + "1": $localize`:@@publication2.publisherOpenAccess.fullyOpen:Kokonaan avoin julkaisukanava`, + "2": $localize`:@@publication2.publisherOpenAccess.partiallyOpen:Osittain avoin julkaisukanava`, + "3": $localize`:@@publication2.publisherOpenAccess.delayedOpen:Viivästetysti avoin julkaisukanava`, + "0": $localize`:@@publication2.publisherOpenAccess.Unspecified:Ei vastausta`, + // "9": $localize`:@@publication2.publisherOpenAccess.Unknown:Ei tietoa` + }); + } + /* + Rinnakkaistallennettu + + + + Ei rinnakkaistallennettu + + + + Ei tietoa + */ + + getSelfArchivedCodeNames(): Observable> { + return of({ + "1": $localize`:@@publication2.selfArchived.Yes:Rinnakkaistallennettu`, + "0": $localize`:@@publication2.selfArchived.No:Ei rinnakkaistallennettu`, + " ": $localize`:@@publication2.selfArchived.Unknown:Ei tietoa` + }); + + } } function matchingTerms(searchParams: SearchParams) { diff --git a/src/i18n/messages.en.xlf b/src/i18n/messages.en.xlf index d6aab7834..a85d43c1c 100644 --- a/src/i18n/messages.en.xlf +++ b/src/i18n/messages.en.xlf @@ -6610,6 +6610,70 @@ Julkaisu on tallennettu organisaatio- tai tieteenalakohtaiseen julkaisuarkistoon joko välittömästi tai kustantajan määrittämän kohtuullisen embargoajan jälkeen. The publication has been published in a publication archive specific to an organization or field of science either immediately or after a reasonable embargo period set by the publisher. + + + + + Avoimesti saatavilla + Open access + + + + Ei avoin + Not open + + + + Ei tietoa + Unknown + + + + Kokonaan avoin julkaisukanava + Fully open publication channel + + + + Osittain avoin julkaisukanava + Partially open publication channel + + + + Viivästetysti avoin julkaisukanava + Delayed open publication channel + + + + Ei vastausta + No answer + + + + Ei tietoa + No information + + + + Rinnakkaistallennettu + Self-archived + + + + Ei rinnakkaistallennettu + Not self-archived + + + + Ei tietoa + Unknown + + + + + Rinnakkaistallenne + Self-archived + + diff --git a/src/i18n/messages.sv.xlf b/src/i18n/messages.sv.xlf index 370aae3e3..6e79c8802 100644 --- a/src/i18n/messages.sv.xlf +++ b/src/i18n/messages.sv.xlf @@ -6614,6 +6614,71 @@ uppgifter till datalagret för forskningsinformation. De gemensamma personuppgif Julkaisu on tallennettu organisaatio- tai tieteenalakohtaiseen julkaisuarkistoon joko välittömästi tai kustantajan määrittämän kohtuullisen embargoajan jälkeen. Publikationen har publicerats i ett organisations- eller vetenskapsområdesspecifikt publikationsarkiv antingen omedelbart eller efter en av förläggaren fastställd skälig embargotid. + + + + + Avoimesti saatavilla + Öppet tillgänglig + + + + Ei avoin + Inte öppen + + + + Ei tietoa + Okänt + + + + Kokonaan avoin julkaisukanava + Helt öppen publikationskanal + + + + Osittain avoin julkaisukanava + Delvis öppen publikationskanal + + + + Viivästetysti avoin julkaisukanava + Fördröjd öppen publikationskanal + + + + Ei vastausta + Inget svar + + + + Ei tietoa + Ingen information + + + + Rinnakkaistallennettu + Parallellsparad + + + + Ei rinnakkaistallennettu + Inte parallellsparad + + + + Ei tietoa + Okänt + + + + + + Rinnakkaistallenne + Parallell publicering + + diff --git a/src/i18n/messages.xlf b/src/i18n/messages.xlf index ddfced27a..98b340b58 100644 --- a/src/i18n/messages.xlf +++ b/src/i18n/messages.xlf @@ -2579,7 +2579,6 @@ - Kokonaan avoin julkaisukanava: @@ -2629,6 +2628,59 @@ Julkaisu on tallennettu organisaatio- tai tieteenalakohtaiseen julkaisuarkistoon joko välittömästi tai kustantajan määrittämän kohtuullisen embargoajan jälkeen. + + + + + Avoimesti saatavilla + + + + Ei avoin + + + + Ei tietoa + + + + Kokonaan avoin julkaisukanava + + + + Osittain avoin julkaisukanava + + + + Viivästetysti avoin julkaisukanava + + + + Ei vastausta + + + + Ei tietoa + + + + Rinnakkaistallennettu + + + + Ei rinnakkaistallennettu + + + + Ei tietoa + + + + + + Rinnakkaistallenne + + From 723b6bed3ef9a6c5251d0cac061500d185100b3d Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Tue, 20 Feb 2024 11:37:13 +0200 Subject: [PATCH 27/47] Add modal that shows filters in mobile --- .../publications2.component.html | 881 +++++++++--------- .../publications2.component.scss | 6 +- .../publications2/publications2.component.ts | 76 +- .../portal/services/publication2.service.ts | 13 +- src/styles.scss | 8 + 5 files changed, 505 insertions(+), 479 deletions(-) diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index 2592d5465..23851658f 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -1,54 +1,7 @@ -
- - - - - -
- -
- - -
- {{tab}} -
-
- - -
- {{tab}} -
-
-
-
-
- -
-
-
-

Julkaisut - {{total$ | async}}

-
- -
- Näytetään tulokset {{(page - 1) * size + 1}} - {{page * size}} / {{total$ | async}} - - - - tulosta / sivu - - Mitä julkaisutietoja palvelu sisältää? -
-
-
- -
-
+ +
+

Rajaa hakua

@@ -56,450 +9,555 @@

Rajaa hakua

+ -
- - - - - - -
- - Näytä enemmän - - - Näytä vähemmän - -
+
+ + + + + - -
+
+ + Näytä enemmän + - -
+ + Näytä vähemmän + +
+
+ +
+
- + +
- -
+ -
- - - Tilastokeskuksen tieteenalaluokitus. Taiteenalat OKM:n luokituksen mukaisesti. Julkaisulla voi olla 1-6 tieteen- tai taiteenalaa. - + +
- +
+ + + Tilastokeskuksen tieteenalaluokitus. Taiteenalat OKM:n luokituksen mukaisesti. Julkaisulla voi olla 1-6 tieteen- tai taiteenalaa. + - - + - - - - + + + + + - - - -
+
+
- -
+ + +
-
- - - OKM:n julkaisutiedonkeruun mukainen julkaisutyyppi A–G. - + +
- +
+ + + OKM:n julkaisutiedonkeruun mukainen julkaisutyyppi A–G. + - - + - - - - + + + + + - - - - -
- - -
-
-
- - -

- Artikkeli: - Sisältää alkuperäis- ja katsausartikkelit, kirjan tai lehden johdannot ja esipuheet, lyhyet tutkimusselostukset, pääkirjoitukset, keskustelupuheenvuorot ja kommentit. -

- -

- Erillisteos: - Sisältää monografiat/kirjat, tutkimus- ja kehitystyöhön perustuva kehittämis- tai tutkimusraportti, selvitykset, ns. white paperit sekä working papers ja discussion papers -tyyppiset julkaisut. -

- - -

- Toimitustyö: - Sisältää useista eri kirjoittajien artikkeleista koostuvan tieteellisen kirjan tai lehden erikoisnumeron toimitustyöt. -

- -

- Abstrakti: - Sisältää konferenssiesitelmien abstraktit sekä laajennetut abstraktit. -

- -

- Posteri: - Sisältää konferenssiesitelmien posterit. -

- -

- Blogikirjoitus: - Sisältää blogimuotoiset julkaisut, joiden julkaisemisesta on päättänyt riippumaton toimituskunta tai joiden julkaisualustalla on ISSN-tunnus. -

-
- - - -
-
-
- -
- -
- - -

- Julkaisukanavan kohdeyleisö -

+
+
+
-

- Tieteellinen julkaisu: - Julkaisut, jotka on tarkoitettu edistämään tiedettä sekä tuottamaan uutta tietoa. -

+ +
+
+
+ +

- Ammatillinen julkaisu: - Julkaisut, jotka levittävät tutkimukseen ja kehitystyöhön perustuvaa tietoa ammattiyhteisön käyttöön. + Artikkeli: + Sisältää alkuperäis- ja katsausartikkelit, kirjan tai lehden johdannot ja esipuheet, lyhyet tutkimusselostukset, pääkirjoitukset, keskustelupuheenvuorot ja kommentit.

- Yleistajuinen julkaisu: - Julkaisut, jotka levittävät tutkimus- ja kehitystyöhön perustuvaa tietoa suurelle yleisölle ja joiden sisällön ymmärtäminen ei edellytä erityistä perehtyneisyyttä alaan. + Erillisteos: + Sisältää monografiat/kirjat, tutkimus- ja kehitystyöhön perustuva kehittämis- tai tutkimusraportti, selvitykset, ns. white paperit sekä working papers ja discussion papers -tyyppiset julkaisut.

-
- - - - - -
-
- -
- -
- -

- Lehti: - sisältää tieteelliset aikakauslehdet ja ammattilehdet. + Toimitustyö: + Sisältää useista eri kirjoittajien artikkeleista koostuvan tieteellisen kirjan tai lehden erikoisnumeron toimitustyöt.

- Kokoomateos: - Sisältää tieteelliset kokoomateokset, tieteelliset vuosikirjat ja vastaavat, ammatilliset käsi- tai opaskirjat, ammatilliset tietojärjestelmät tai kokoomateokset, oppikirja-aineistot sekä lyhyet ensyklopediatekstit. + Abstrakti: + Sisältää konferenssiesitelmien abstraktit sekä laajennetut abstraktit.

- Konferenssi: - Sisältää konferenssin painetut tai julkisesti saatavilla olevat julkaisut, ns. proceedings-julkaisut. + Posteri: + Sisältää konferenssiesitelmien posterit.

- Verkkoalusta: - Sisältää muilla sähköisillä alustoilla julkaistut julkaisut. + Blogikirjoitus: + Sisältää blogimuotoiset julkaisut, joiden julkaisemisesta on päättänyt riippumaton toimituskunta tai joiden julkaisualustalla on ISSN-tunnus.

- - + +
+
- -
+ +
+ +
+ + +

+ Julkaisukanavan kohdeyleisö +

+ +

+ Tieteellinen julkaisu: + Julkaisut, jotka on tarkoitettu edistämään tiedettä sekä tuottamaan uutta tietoa. +

+ +

+ Ammatillinen julkaisu: + Julkaisut, jotka levittävät tutkimukseen ja kehitystyöhön perustuvaa tietoa ammattiyhteisön käyttöön. +

+ +

+ Yleistajuinen julkaisu: + Julkaisut, jotka levittävät tutkimus- ja kehitystyöhön perustuvaa tietoa suurelle yleisölle ja joiden sisällön ymmärtäminen ei edellytä erityistä perehtyneisyyttä alaan. +

+
-
- - -

- Alkuperäisartikkeli: + + + + + +

- on pääosin aiemmin julkaisemattomasta materiaalista koostuva tieteellinen artikkeli. -

-

- Katsausartikkeli: + +

- perustuu aikaisempiin samasta aihepiiristä tehtyihin julkaisuihin. -

+
+ + +

+ Lehti: + sisältää tieteelliset aikakauslehdet ja ammattilehdet. +

-

- Data-artikkeli: +

+ Kokoomateos: + Sisältää tieteelliset kokoomateokset, tieteelliset vuosikirjat ja vastaavat, ammatilliset käsi- tai opaskirjat, ammatilliset tietojärjestelmät tai kokoomateokset, oppikirja-aineistot sekä lyhyet ensyklopediatekstit. +

- sisältää ns. data journals -julkaisuissa ilmestyneet, tutkimusaineistoja kuvailevat artikkelit. -

+

+ Konferenssi: + Sisältää konferenssin painetut tai julkisesti saatavilla olevat julkaisut, ns. proceedings-julkaisut. +

-

- Muu artikkeli: +

+ Verkkoalusta: + Sisältää muilla sähköisillä alustoilla julkaistut julkaisut. +

+
- sisältää muihin luokkiin kuulumattomat artikkelit. -

- + + + + +
+
+ +
- - - - -
-
+
+ + +

+ Alkuperäisartikkeli: - -

+ on pääosin aiemmin julkaisemattomasta materiaalista koostuva tieteellinen artikkeli. +

-
- - Tieteellisten julkaisujen vertaisarvioinnilla tarkoitetaan menettelyä, jossa tutkimustuloksia julkaiseva lehti, konferenssi tai kirjakustantaja pyytää tieteenalan asiantuntijoita suorittamaan ennakkoarvion julkaistavaksi tarjottujen käsikirjoitusten tieteellisestä julkaisukelpoisuudesta. - - - - - - -
+

+ Katsausartikkeli: - -

+ perustuu aikaisempiin samasta aihepiiristä tehtyihin julkaisuihin. +

-
- - - Kotimaisen julkaisun kustantaja on suomalainen tai se on ensisijaisesti julkaistu Suomessa. Kansainvälisen julkaisun kustantaja ei ole suomalainen tai se on ensisijaisesti julkaistu muualla kuin Suomessa. - +

+ Data-artikkeli: - - - - - -

+ sisältää ns. data journals -julkaisuissa ilmestyneet, tutkimusaineistoja kuvailevat artikkelit. +

- -
+

+ Muu artikkeli: -

- - - Kieli, jolla julkaisu on kirjoitettu. - + sisältää muihin luokkiin kuulumattomat artikkelit. +

+ - - - - - -
- - Näytä enemmän - + + + + + +
- - Näytä vähemmän - -
-
+ +
+
+ + Tieteellisten julkaisujen vertaisarvioinnilla tarkoitetaan menettelyä, jossa tutkimustuloksia julkaiseva lehti, konferenssi tai kirjakustantaja pyytää tieteenalan asiantuntijoita suorittamaan ennakkoarvion julkaistavaksi tarjottujen käsikirjoitusten tieteellisestä julkaisukelpoisuudesta. - -
+ + + + +
+
- -
+ +
-
- - - Julkaisufoorumin (www.julkaisufoorumi.fi) mukainen julkaisukanavan (kirjakustantaja, konferenssi tai julkaisusarja) tasoluokitus: 1 = perustaso, 2 = johtava taso, 3 = korkein taso. Tasolla 0 ovat kanavat, jotka eivät joltain osin täytä tason 1 vaatimuksia tai ovat uusia. Julkaisufoorumitaso määräytyy julkaisun julkaisuvuoden mukaan. - +
+ + + Kotimaisen julkaisun kustantaja on suomalainen tai se on ensisijaisesti julkaistu Suomessa. Kansainvälisen julkaisun kustantaja ei ole suomalainen tai se on ensisijaisesti julkaistu muualla kuin Suomessa. + + + + + + + +
+ + +
- - +
+ + + Kieli, jolla julkaisu on kirjoitettu. + + + + + - -
- -
+
+ + Näytä enemmän + -
- - + + Näytä vähemmän + +
+ -

- Avoimesti saatavilla: - Julkaisu on avoimesti saatavilla kustantajan palvelussa. -

-

- Ei avoin: - Julkaisu ei ole avoimesti saatavilla kustantajan palvelussa. -

+ +
-

- Ei tietoa: - Tietoa ei ole raportoitu. -

+ +
-
+
+ + + Julkaisufoorumin (www.julkaisufoorumi.fi) mukainen julkaisukanavan (kirjakustantaja, konferenssi tai julkaisusarja) tasoluokitus: 1 = perustaso, 2 = johtava taso, 3 = korkein taso. Tasolla 0 ovat kanavat, jotka eivät joltain osin täytä tason 1 vaatimuksia tai ovat uusia. Julkaisufoorumitaso määräytyy julkaisun julkaisuvuoden mukaan. + - - - - - -
+ + + + +
+
- -
+ +
-
- - -

- Kokonaan avoin julkaisukanava: - Julkaisukanavan kaikki julkaisut ovat pysyvästi avoimesti saatavilla. -

+
+ + -

- Osittain avoin julkaisukanava: - Julkaisukanavassa vain osa julkaisuista on pysyvästi avoimesti saatavilla. -

+

+ Avoimesti saatavilla: + Julkaisu on avoimesti saatavilla kustantajan palvelussa. +

-

- Viivästetysti avoin julkaisukanava: - Julkaisukanavan tieteelliset artikkelit avataan kustantajan määrittelemän viiveajan jälkeen. -

+

+ Ei avoin: + Julkaisu ei ole avoimesti saatavilla kustantajan palvelussa. +

-

- Ei vastausta: - Julkaisukanava ei ole kokonaan, osittain tai viivästetysti avoin. -

+

+ Ei tietoa: + Tietoa ei ole raportoitu. +

-

- Ei tietoa: - Tietoa ei ole raportoitu. -

+
- + + + + +
+
- - - - -
-
+ +
+ +
+ + +

+ Kokonaan avoin julkaisukanava: + Julkaisukanavan kaikki julkaisut ovat pysyvästi avoimesti saatavilla. +

+ +

+ Osittain avoin julkaisukanava: + Julkaisukanavassa vain osa julkaisuista on pysyvästi avoimesti saatavilla. +

+ +

+ Viivästetysti avoin julkaisukanava: + Julkaisukanavan tieteelliset artikkelit avataan kustantajan määrittelemän viiveajan jälkeen. +

+ +

+ Ei vastausta: + Julkaisukanava ei ole kokonaan, osittain tai viivästetysti avoin. +

+ +

+ Ei tietoa: + Tietoa ei ole raportoitu. +

- -
+
+ + + + + +
+
+ + +
+ + +
+ + +

+ Rinnakkaistallennettu: + Julkaisu on tallennettu organisaatio- tai tieteenalakohtaiseen julkaisuarkistoon joko välittömästi tai kustantajan määrittämän kohtuullisen embargoajan jälkeen. +

+
- + + + + +
+
+ +
+ + + +
+
+ +

Rajaa hakua

+
+ + +
+ +
+
+ +
+
+
+ +
+ + + + + +
+ +
+ + +
+ {{tab}} +
+
+ + +
+ {{tab}} +
+
+
+
+
+ +
+
- - -

- Rinnakkaistallennettu: - Julkaisu on tallennettu organisaatio- tai tieteenalakohtaiseen julkaisuarkistoon joko välittömästi tai kustantajan määrittämän kohtuullisen embargoajan jälkeen. -

-
+

Julkaisut - {{total$ | async}}

+
- - - - - +
+ Näytetään tulokset {{(page - 1) * size + 1}} - {{page * size}} / {{total$ | async}} + + + + tulosta / sivu + + Mitä julkaisutietoja palvelu sisältää? +
+
+
+ + +
+ + +
+
+ +
+ + +
+
+ +
+
@@ -663,29 +721,6 @@

Rajaa hakua

- - - - - -
@@ -700,7 +735,7 @@

Rajaa hakua

-
+
@@ -772,20 +807,6 @@

Rajaa hakua

-
- - - - -
- - -
diff --git a/src/app/portal/components/results/publications2/publications2.component.scss b/src/app/portal/components/results/publications2/publications2.component.scss index e121b0ff4..2e6bc80e5 100644 --- a/src/app/portal/components/results/publications2/publications2.component.scss +++ b/src/app/portal/components/results/publications2/publications2.component.scss @@ -50,7 +50,7 @@ "tabs" "info" "summary" - "filters" + "filters-toggle" "results" "pagination"; } @@ -72,6 +72,10 @@ grid-area: filters; } +.search-filters-toggle { + grid-area: filters-toggle; +} + .search-summary { grid-area: summary; } diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index ea1d3834a..bfcec2cf5 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -1,9 +1,9 @@ -import { Component, inject, OnDestroy } from '@angular/core'; +import { Component, inject, OnDestroy, TemplateRef, ViewChild } from '@angular/core'; import { CdkTableModule, DataSource } from '@angular/cdk/table'; -import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest, interval, Observable } from 'rxjs'; import { ActivatedRoute, Router, RouterLink, RouterModule } from '@angular/router'; import { FormsModule } from '@angular/forms'; -import { AsyncPipe, JsonPipe, NgForOf, NgIf, NgStyle } from '@angular/common'; +import { AsyncPipe, JsonPipe, NgForOf, NgIf, NgStyle, NgTemplateOutlet } from '@angular/common'; import { getArticleTypeCodeAdditions, getFieldsOfScienceAdditions, @@ -37,6 +37,8 @@ import { FirstDigitPipe } from '@shared/pipes/first-digit.pipe'; import { FirstLetterPipe } from '@shared/pipes/first-letter.pipe'; import { BreakpointObserver, LayoutModule } from '@angular/cdk/layout'; import { ColumnSorterComponent } from '@shared/components/column-sorter/column-sorter.component'; +import { faSlidersH } from '@fortawesome/free-solid-svg-icons'; +import { Dialog, DialogRef } from '@angular/cdk/dialog'; @Component({ selector: 'app-publications2', @@ -47,7 +49,7 @@ import { ColumnSorterComponent } from '@shared/components/column-sorter/column-s FormsModule, RouterModule, SearchBar2Component, OrganizationFilterComponent, FilterOptionComponent, CollapsibleComponent, MatButtonModule, NgStyle, FilterLimitButtonComponent, FirstDigitPipe, FirstLetterPipe, RouterLink, - LayoutModule, ColumnSorterComponent + LayoutModule, ColumnSorterComponent, NgTemplateOutlet ], standalone: true }) @@ -55,8 +57,9 @@ export class Publications2Component implements OnDestroy { route = inject(ActivatedRoute); router = inject(Router); publications2Service = inject(Publication2Service); - breakpointObserver = inject(BreakpointObserver); + dialog = inject(Dialog); + keywords = ""; page = 1; @@ -64,6 +67,24 @@ export class Publications2Component implements OnDestroy { sort = ""; + dialogRef?: DialogRef; + + @ViewChild('searchDialog') dialogTemplate: TemplateRef; + + openDialog() { + this.dialogRef = this.dialog.open(this.dialogTemplate, { + panelClass: 'fullscreen-panel', + }); + + this.dialogRef.closed.subscribe(() => { + console.log('The dialog2 was closed'); + }); + } + + closeDialog() { + this.dialogRef?.close(); + } + labelText = { yearOfPublication: $localize`:@@yearOfPublication:Julkaisuvuosi`, organization: $localize`:@@organization:Organisaatio`, @@ -216,26 +237,18 @@ export class Publications2Component implements OnDestroy { tap(filters => this.updateFilterCount("peerReviewed", filters.filter(filter => filter.enabled).length)) ); - // getParentPublicationTypeAdditions - // getInternationalPublicationAdditions - // getArticleTypeCodeAdditions - // getJufoClassCodeAdditions - parentPublicationTypeAdditions$ = this.aggregations$.pipe( map(aggs => getParentPublicationTypeAdditions(aggs).map((bucket: any) => ({ id: bucket.key, count: bucket.doc_count })) ?? []), map(aggs => aggs.sort((a, b) => b.count - a.count)) ); - // publisherInternationalityAdditions$ publisherInternationalityAdditions$ = this.aggregations$.pipe( map(aggs => getPublisherInternationalityAdditions(aggs).map((bucket: any) => ({ id: bucket.key.toString(), count: bucket.doc_count })) ?? []), map(aggs => aggs.sort((a, b) => b.count - a.count)) ); - // publisherInternationalityNames$ publisherInternationalityNames$ = this.publications2Service.getInternationalPublicationNames(); - // publisherInternationalityFilters$ publisherInternationalityFilters$ = combineLatest([this.publisherInternationalityAdditions$, this.publisherInternationalityNames$, this.searchParams$.pipe(map(params => params.international ?? []))]).pipe( map(([internationalPublicationAdditions, internationalPublicationNames, enabledFilters]) => internationalPublicationAdditions.map(internationalPublicationAddition => ({ id: internationalPublicationAddition.id, @@ -367,36 +380,22 @@ export class Publications2Component implements OnDestroy { tap(filters => this.updateFilterCount("publisherOpenAccess", filters.filter(filter => filter.enabled).length)) ); - // TODO selfArchiveCode - additionsFromSelfArchivedCode$ = this.aggregations$.pipe( map(aggs => getSelfArchivedCodeAdditions(aggs).map((bucket: any) => ({ id: bucket.key.toString(), count: bucket.doc_count })) ?? []), map(aggs => aggs.sort((a, b) => b.count - a.count)) ); - // names selfArchivedCodeNames$ = this.publications2Service.getSelfArchivedCodeNames(); - // OLD - /*selfArchivedCodeFilters$ = combineLatest([this.additionsFromSelfArchivedCode$, this.searchParams$.pipe(map(params => params.selfArchiveCode ?? []))]).pipe( - map(([additionsFromSelfArchive, enabledFilters]) => additionsFromSelfArchive.map(additionFromSelfArchive => ({ - id: additionFromSelfArchive.id, - count: additionFromSelfArchive.count, - name: additionFromSelfArchive.id, // TODO TODO TODO TODO - enabled: enabledFilters.includes(additionFromSelfArchive.id) - }))), - tap(filters => this.updateFilterCount("selfArchiveCode", filters.filter(filter => filter.enabled).length)) - );*/ - - // NEW - selfArchivedCodeFilters$ = combineLatest([this.additionsFromSelfArchivedCode$, this.selfArchivedCodeNames$, this.searchParams$.pipe(map(params => params.selfArchiveCode ?? []))]).pipe( + selfArchivedCodeFilters$ = combineLatest([this.additionsFromSelfArchivedCode$, this.selfArchivedCodeNames$, this.searchParams$.pipe(map(params => params.selfArchivedCode ?? []))]).pipe( + tap((values) => console.log(values)), map(([additionsFromSelfArchivedCode, selfArchivedCodeNames, enabledFilters]) => additionsFromSelfArchivedCode.map(additionFromSelfArchivedCode => ({ id: additionFromSelfArchivedCode.id, count: additionFromSelfArchivedCode.count, name: selfArchivedCodeNames[additionFromSelfArchivedCode.id], enabled: enabledFilters.includes(additionFromSelfArchivedCode.id) }))), - tap(filters => this.updateFilterCount("selfArchiveCode", filters.filter(filter => filter.enabled).length)) + tap(filters => this.updateFilterCount("selfArchivedCode", filters.filter(filter => filter.enabled).length)) ); public mainFieldOfScienceName = { @@ -410,10 +409,6 @@ export class Publications2Component implements OnDestroy { "8": $localize`:@@fieldsOfArt:Taiteenala`, } - /*public collapseStates = { - "language": false, - };*/ - public filterLimits = { year: 10, language: 10, @@ -429,6 +424,8 @@ export class Publications2Component implements OnDestroy { } }); + narrowBreakpoint$: Observable = this.breakpointObserver.observe('(max-width: 990px)').pipe(map(result => result.matches)); + searchParamsSubscription = this.searchParams$.subscribe(searchParams => { this.publications2Service.updateSearchTerms(searchParams); @@ -467,6 +464,7 @@ export class Publications2Component implements OnDestroy { this.router.navigate([], { relativeTo: this.route, + skipLocationChange: true, queryParams: concatFields(queryParams) }); }); @@ -480,6 +478,7 @@ export class Publications2Component implements OnDestroy { this.router.navigate([], { relativeTo: this.route, + skipLocationChange: true, queryParams: concatFields(queryParams) }); }); @@ -492,6 +491,7 @@ export class Publications2Component implements OnDestroy { this.router.navigate([], { relativeTo: this.route, + skipLocationChange: true, queryParams: concatFields(queryParams) }); }); @@ -514,6 +514,7 @@ export class Publications2Component implements OnDestroy { searchKeywords(keywords: string) { this.router.navigate([], { relativeTo: this.route, + skipLocationChange: true, queryParams: { q: keywords }, queryParamsHandling: 'merge' }); } @@ -525,6 +526,7 @@ export class Publications2Component implements OnDestroy { queryParams.page = [`${page + 1}`]; this.router.navigate([], { relativeTo: this.route, + skipLocationChange: true, queryParams: queryParams }); }); @@ -537,18 +539,18 @@ export class Publications2Component implements OnDestroy { queryParams.page = [`${page - 1}`]; this.router.navigate([], { relativeTo: this.route, + skipLocationChange: true, queryParams: queryParams }); }); } - public num = 0; - setPageSize(size: number) { console.log(size); this.router.navigate([], { relativeTo: this.route, + skipLocationChange: true, queryParams: { size }, queryParamsHandling: 'merge' }); } @@ -556,6 +558,8 @@ export class Publications2Component implements OnDestroy { clamp(value: number, min: number, max: number) { return Math.min(Math.max(value, min), max); } + + faSlidersH = faSlidersH; } export class PublicationDataSource extends DataSource { diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index d81105c6f..25a6a7160 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -580,17 +580,6 @@ export class Publication2Service { // "9": $localize`:@@publication2.publisherOpenAccess.Unknown:Ei tietoa` }); } - /* - Rinnakkaistallennettu - - - - Ei rinnakkaistallennettu - - - - Ei tietoa - */ getSelfArchivedCodeNames(): Observable> { return of({ @@ -835,7 +824,7 @@ const lookup: Record = { articleType: {fieldName: "articleTypeCode", fieldPath: "articleTypeCode"}, jufo: {fieldName: "jufoClassCode", fieldPath: "jufoClassCode.keyword"}, publicationTypeCode: {fieldName: "publicationTypeCode", fieldPath: "publicationTypeCode.keyword"}, - publicationStatusCode: {fieldName: "publicationStatusCode", fieldPath: "publicationStatusCode.keyword"}, // No trace of the previous implementation + publicationStatusCode: {fieldName: "publicationStatusCode", fieldPath: "publicationStatusCode.keyword"}, fieldsOfScience: {fieldName: "fieldsOfScience", fieldPath: "fieldsOfScience.fieldIdScience"}, openAccess: {fieldName: "openAccess", fieldPath: "openAccess"}, publisherOpenAccessCode: {fieldName: "publisherOpenAccessCode", fieldPath: "publisherOpenAccessCode"}, diff --git a/src/styles.scss b/src/styles.scss index a64b48260..900f904b3 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -157,3 +157,11 @@ em.highlight { font-style: normal; padding: 0.1em 0; } + +.fullscreen-panel { + width: 100%; + height: 100%; + background-color: white; + + overflow-y: auto; +} From 37ffe0e201c3caadc43a04ff4eedb944b46d5973 Mon Sep 17 00:00:00 2001 From: Olli Mannevaara Date: Wed, 21 Feb 2024 12:48:39 +0200 Subject: [PATCH 28/47] Add banner for accessing the new publication search --- .../publications2.component.html | 95 ++++++++++++++++++- .../publications2.component.scss | 6 +- .../publications2/publications2.component.ts | 27 ++++-- .../components/results/results.component.html | 43 ++++++++- .../components/results/results.component.ts | 13 +++ 5 files changed, 165 insertions(+), 19 deletions(-) diff --git a/src/app/portal/components/results/publications2/publications2.component.html b/src/app/portal/components/results/publications2/publications2.component.html index 23851658f..56e11f726 100644 --- a/src/app/portal/components/results/publications2/publications2.component.html +++ b/src/app/portal/components/results/publications2/publications2.component.html @@ -486,8 +486,9 @@

Rajaa hakua

-
- + +
+ + + +
+

Julkaisut - {{total$ | async}}

@@ -613,7 +655,6 @@

Julkaisut - {{total$ | async}}

- @@ -685,6 +726,45 @@

Julkaisut - {{total$ | async}}

+ + + + + + + +
+
{{openAccessFilter.name}}
+ +
+ +
+
+ + + + + +
+
{{publisherOpenAccessFilter.name}}
+ +
+ +
+
+ + + + + +
+
{{selfArchivedCodeFilter.name}}
+ +
+ +
+
+
@@ -802,6 +882,13 @@

Julkaisut - {{total$ | async}}

+ +
diff --git a/src/app/portal/components/results/publications2/publications2.component.scss b/src/app/portal/components/results/publications2/publications2.component.scss index 2e6bc80e5..396294ee4 100644 --- a/src/app/portal/components/results/publications2/publications2.component.scss +++ b/src/app/portal/components/results/publications2/publications2.component.scss @@ -20,7 +20,7 @@ grid-template-areas: "search search search search" - ". tabs tabs ." + /*". tabs tabs ."*/ ". info info ." ". filters summary ." ". filters results ." @@ -33,7 +33,7 @@ grid-template-areas: "search search" - "tabs tabs" + /*"tabs tabs"*/ "info info" "filters summary" "filters results" @@ -47,7 +47,7 @@ grid-template-areas: "search" - "tabs" + /*"tabs"*/ "info" "summary" "filters-toggle" diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index bfcec2cf5..381c0ea6a 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -26,7 +26,7 @@ import { import { map, take, tap } from 'rxjs/operators'; import { SharedModule } from '@shared/shared.module'; import { SearchBar2Component } from '@portal/search-bar2/search-bar2.component'; -import { NgArrayPipesModule } from 'ngx-pipes'; +import { NgArrayPipesModule, NgMathPipesModule } from 'ngx-pipes'; import { OrganizationFilterComponent } from '@portal/components/organization-filter/organization-filter.component'; import { FilterOptionComponent } from '@portal/components/filter-option/filter-option.component'; import { LimitPipe } from '@portal/pipes/limit.pipe'; @@ -39,6 +39,7 @@ import { BreakpointObserver, LayoutModule } from '@angular/cdk/layout'; import { ColumnSorterComponent } from '@shared/components/column-sorter/column-sorter.component'; import { faSlidersH } from '@fortawesome/free-solid-svg-icons'; import { Dialog, DialogRef } from '@angular/cdk/dialog'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; @Component({ selector: 'app-publications2', @@ -49,7 +50,7 @@ import { Dialog, DialogRef } from '@angular/cdk/dialog'; FormsModule, RouterModule, SearchBar2Component, OrganizationFilterComponent, FilterOptionComponent, CollapsibleComponent, MatButtonModule, NgStyle, FilterLimitButtonComponent, FirstDigitPipe, FirstLetterPipe, RouterLink, - LayoutModule, ColumnSorterComponent, NgTemplateOutlet + LayoutModule, ColumnSorterComponent, NgTemplateOutlet, FontAwesomeModule, NgMathPipesModule ], standalone: true }) @@ -464,7 +465,7 @@ export class Publications2Component implements OnDestroy { this.router.navigate([], { relativeTo: this.route, - skipLocationChange: true, + // skipLocationChange: true, queryParams: concatFields(queryParams) }); }); @@ -478,7 +479,7 @@ export class Publications2Component implements OnDestroy { this.router.navigate([], { relativeTo: this.route, - skipLocationChange: true, + // skipLocationChange: true, queryParams: concatFields(queryParams) }); }); @@ -491,7 +492,7 @@ export class Publications2Component implements OnDestroy { this.router.navigate([], { relativeTo: this.route, - skipLocationChange: true, + // skipLocationChange: true, queryParams: concatFields(queryParams) }); }); @@ -514,8 +515,14 @@ export class Publications2Component implements OnDestroy { searchKeywords(keywords: string) { this.router.navigate([], { relativeTo: this.route, - skipLocationChange: true, - queryParams: { q: keywords }, queryParamsHandling: 'merge' + // skipLocationChange: true, + + queryParams: { + q: keywords, + page: "1" + }, + + queryParamsHandling: 'merge' }); } @@ -526,7 +533,7 @@ export class Publications2Component implements OnDestroy { queryParams.page = [`${page + 1}`]; this.router.navigate([], { relativeTo: this.route, - skipLocationChange: true, + // skipLocationChange: true, queryParams: queryParams }); }); @@ -539,7 +546,7 @@ export class Publications2Component implements OnDestroy { queryParams.page = [`${page - 1}`]; this.router.navigate([], { relativeTo: this.route, - skipLocationChange: true, + // skipLocationChange: true, queryParams: queryParams }); }); @@ -550,7 +557,7 @@ export class Publications2Component implements OnDestroy { this.router.navigate([], { relativeTo: this.route, - skipLocationChange: true, + // skipLocationChange: true, queryParams: { size }, queryParamsHandling: 'merge' }); } diff --git a/src/app/portal/components/results/results.component.html b/src/app/portal/components/results/results.component.html index 2882283a1..f28e70398 100644 --- a/src/app/portal/components/results/results.component.html +++ b/src/app/portal/components/results/results.component.html @@ -16,10 +16,49 @@

[allData]="tabValues" > + + + + + + +