diff --git a/src/app/mydata/components/profile/profile.component.ts b/src/app/mydata/components/profile/profile.component.ts index 9415effc4..66cc0785e 100644 --- a/src/app/mydata/components/profile/profile.component.ts +++ b/src/app/mydata/components/profile/profile.component.ts @@ -523,7 +523,6 @@ export class ProfileComponent implements OnInit, OnDestroy { for (const delay of delays) { await lastValueFrom(timer(delay)); - console.log("polling profile"); response = await lastValueFrom(this.updatePerson()); diff --git a/src/app/mydata/services/orcid-account-linking.service.ts b/src/app/mydata/services/orcid-account-linking.service.ts index 87ea5a8b4..84c1b57d6 100644 --- a/src/app/mydata/services/orcid-account-linking.service.ts +++ b/src/app/mydata/services/orcid-account-linking.service.ts @@ -57,8 +57,8 @@ export class OrcidAccoungLinkingService { /* * Get hash. For more explanation, see comments of function getOrcidLink() */ - async getHash(nonce, sessionState, clientId) { - const input = nonce + sessionState + clientId + 'orcid'; + async getHash(nonce, sid, clientId) { + const input = nonce + sid + clientId + 'orcid'; const encoder = new TextEncoder(); const data = encoder.encode(input); const sha256 = await crypto.subtle.digest('SHA-256', data); @@ -105,7 +105,7 @@ export class OrcidAccoungLinkingService { * This is a random string that your application must generate * hash: * This is a Base64 URL encoded hash. - * This hash is generated by Base64 URL encoding a SHA_256 hash of nonce + session_state (from token) + azp (from token) + provider + * This hash is generated by Base64 URL encoding a SHA_256 hash of nonce + sid (from token) + azp (from token) + provider * Basically you are hashing the random nonce, the user session id, the client id, and the identity provider alias you want to access. */ async getOrcidLink() { @@ -126,14 +126,16 @@ export class OrcidAccoungLinkingService { // azp: Authorized party - the party to which the ID Token was issued const clientId = idTokenPayload.azp; - // Get property 'session_state' from ID token. - const sessionState = idTokenPayload.session_state; + // Get property 'sid' from ID token. + // 2024-12-31: use 'sid' instead of 'session_state' + // https://www.keycloak.org/docs/latest/release_notes/index.html#lightweight-access-token-to-be-even-more-lightweight + const sid = idTokenPayload.sid; // Get nonce const nonce = this.getNonce(); // Get hash - const hash = await this.getHash(nonce, sessionState, clientId); + const hash = await this.getHash(nonce, sid, clientId); // Return ORCID account linking URL return this.getUrl(keycloakUrl, clientId, redirectUrl, nonce, hash); diff --git a/src/app/mydata/services/search-portal.service.ts b/src/app/mydata/services/search-portal.service.ts index 68619e3b4..eebdc825e 100644 --- a/src/app/mydata/services/search-portal.service.ts +++ b/src/app/mydata/services/search-portal.service.ts @@ -41,7 +41,6 @@ export class SearchPortalService { sortSettings: { active: string; direction: string } ) { let sortField: string; - switch (sortSettings.active) { case 'year': { sortField = this.getDefaultSortField(groupId); diff --git a/src/app/portal/components/results/active-filters/active-filters.component.ts b/src/app/portal/components/results/active-filters/active-filters.component.ts index 4dbdaff81..638fdbad8 100644 --- a/src/app/portal/components/results/active-filters/active-filters.component.ts +++ b/src/app/portal/components/results/active-filters/active-filters.component.ts @@ -38,6 +38,7 @@ import { FundingCallFilterService } from '@portal/services/filters/funding-call- import { StaticDataService } from '@portal/services/static-data.service'; import { FilterConfigType } from 'src/types'; import { ActiveFiltersListComponent } from '../../../../shared/components/active-filters-list/active-filters-list.component'; +import { ProjectFilterService } from '@portal/services/filters/project-filter.service'; @Component({ selector: 'app-active-filters', @@ -112,6 +113,7 @@ export class ActiveFiltersComponent private infrastructureFilters: InfrastructureFilterService, private organizationFilters: OrganizationFilterService, private fundingCallFilters: FundingCallFilterService, + private projectFilters: ProjectFilterService, private newsFilters: NewsFilterService, private settingsService: SettingsService, @Inject(PLATFORM_ID) private platformId: object, @@ -153,6 +155,9 @@ export class ActiveFiltersComponent case 'news': this.filtersConfig = this.newsFilters.filterData; break; + case 'projects': + this.filtersConfig = this.projectFilters.filterData; + break; default: break; @@ -167,17 +172,17 @@ export class ActiveFiltersComponent this.translationFlag = false; const errorMsg = 'error translating filter'; - this.queryParams = this.filterService.filters.subscribe((filter) => { + this.queryParams = this.filterService.filters.subscribe((allFilters) => { // Get from & to year values from filter list - this.fromYear = parseInt(filter.fromYear[0]?.slice(1), 10); - this.toYear = parseInt(filter.toYear[0]?.slice(1), 10); - const years = filter.year.map((item) => parseInt(item, 10)); + this.fromYear = parseInt(allFilters.fromYear[0]?.slice(1), 10); + this.toYear = parseInt(allFilters.toYear[0]?.slice(1), 10); + const years = allFilters.year.map((item) => parseInt(item, 10)); let yearWarning = false; if (this.fromYear && this.toYear) { // Check if years missing between range and add warning flag if ( - filter.year.filter( + allFilters.year.filter( (item) => this.fromYear <= item && item <= this.toYear ).length !== this.toYear - this.fromYear + 1 @@ -186,14 +191,14 @@ export class ActiveFiltersComponent } } else if (this.fromYear) { if ( - filter.year.filter((item) => this.fromYear <= item).length !== + allFilters.year.filter((item) => this.fromYear <= item).length !== Math.max(...years) - this.fromYear + 1 ) { yearWarning = true; } } else if (this.toYear) { if ( - filter.year.filter((item) => this.toYear >= item).length !== + allFilters.year.filter((item) => this.toYear >= item).length !== this.toYear + 1 - Math.min(...years) ) { yearWarning = true; @@ -202,21 +207,20 @@ export class ActiveFiltersComponent // Reset active filter so push doesn't duplicate this.activeFilters = []; - const newFilters = {}; + const filtersFromAllCategories = {}; // Merge and format arrays - Object.keys(filter).forEach((key) => { - newFilters[key] = filter[key].map((val) => { + Object.keys(allFilters).forEach((key) => { + filtersFromAllCategories[key] = allFilters[key].map((val) => { return { category: key, value: val, translation: this.translations[val] || val, }; }); - this.activeFilters.push(...newFilters[key]); + this.activeFilters.push(...filtersFromAllCategories[key]); }); const tab = this.tabChangeService.tab; - // Subscribe to aggregation data and shape to get corresponding values this.filterResponse = this.searchService .getAllFilters(tab) @@ -254,6 +258,10 @@ export class ActiveFiltersComponent this.response = this.newsFilters.shapeData(response); break; } + case 'projects': { + this.response = this.projectFilters.shapeData(response); + break; + } } if (response) { @@ -302,7 +310,7 @@ export class ActiveFiltersComponent } if (val.category === 'date') { - const dateString = filter.date ? filter.date[0] : ''; + const dateString = allFilters.date ? allFilters.date[0] : ''; const startDate = dateString?.split('|')[0]; const endDate = dateString?.split('|')[1]; const startDateString = startDate @@ -502,7 +510,7 @@ export class ActiveFiltersComponent if (val.category === 'organization' && source.organization) { // Funding organization name if (tab === 'fundings') { - setTimeout((t) => { + setTimeout(() => { if (source.organization.funded.sectorName.buckets) { source.organization.funded.sectorName.buckets.forEach( (sector) => { @@ -521,7 +529,7 @@ export class ActiveFiltersComponent }, 1); // Dataset & persons organization name } else if (tab === 'datasets' || tab === 'persons') { - setTimeout((t) => { + setTimeout(() => { if (source.organization.sectorName.buckets) { source.organization.sectorName.buckets.forEach( (sector) => { @@ -540,7 +548,7 @@ export class ActiveFiltersComponent }, 1); // Infrastructure organization name } else if (tab === 'infrastructures') { - setTimeout((t) => { + setTimeout(() => { if (source.organization.sector.buckets) { source.organization.sector.buckets.forEach((sector) => { if (sector.subData.find((x) => x.key === val.value)) { @@ -585,9 +593,23 @@ export class ActiveFiltersComponent } }); } - } else { + } else if (tab === 'projects') { + if (source.organizations?.organization?.buckets){ + source.organizations.organization.buckets.forEach( + (bucket) => { + if (bucket.key === val.value) { + const foundIndex = this.activeFilters.findIndex( + (x) => x.value === val.value + ); + this.activeFilters[foundIndex].translation = bucket.translation; + } + } + ); + } + } + else { // Common usage e.g. in publications, persons, datasets - setTimeout((t) => { + setTimeout(() => { if (source.organization.sectorName.buckets) { source.organization.sectorName.buckets.forEach( (sector) => { diff --git a/src/app/portal/components/results/filters/filters.component.html b/src/app/portal/components/results/filters/filters.component.html index 2d1b3b85a..bc8de1f5a 100644 --- a/src/app/portal/components/results/filters/filters.component.html +++ b/src/app/portal/components/results/filters/filters.component.html @@ -612,6 +612,7 @@

+ {{item.field}}
@@ -286,6 +289,12 @@ export class FiltersComponent implements OnInit, OnDestroy, OnChanges { this.fundingCallFilters.shapeData(this.responseData); break; } + case 'projects': { + this.currentFilter = this.projectFilters.filterData; + this.currentSingleFilter = this.projectFilters.singleFilterData; + this.projectFilters.shapeData(this.responseData); + break; + } case 'news': { this.currentFilter = this.newsFilters.filterData; // this.currentSingleFilter = this.newsFilters.singleFilterData; @@ -444,13 +453,13 @@ export class FiltersComponent implements OnInit, OnDestroy, OnChanges { if (event.value) { this.toYear ? source.map((x) => - x.key >= event.value && x.key <= this.toYear - ? selected.push(x.key.toString()) - : null - ) + x.key >= event.value && x.key <= this.toYear + ? selected.push(x.key.toString()) + : null + ) : source.map((x) => - x.key >= event.value ? selected.push(x.key.toString()) : null - ); + x.key >= event.value ? selected.push(x.key.toString()) : null + ); } else { source.map((x) => x.key <= this.toYear ? selected.push(x.key.toString()) : null @@ -463,13 +472,13 @@ export class FiltersComponent implements OnInit, OnDestroy, OnChanges { if (event.value) { this.fromYear ? source.map((x) => - x.key <= event.value && x.key >= this.fromYear - ? selected.push(x.key.toString()) - : null - ) + x.key <= event.value && x.key >= this.fromYear + ? selected.push(x.key.toString()) + : null + ) : source.map((x) => - x.key <= event.value ? selected.push(x.key.toString()) : null - ); + x.key <= event.value ? selected.push(x.key.toString()) : null + ); } else { source.map((x) => x.key >= this.fromYear ? selected.push(x.key.toString()) : null diff --git a/src/app/portal/components/results/projects/projects.component.html b/src/app/portal/components/results/projects/projects.component.html new file mode 100644 index 000000000..3052e7aa7 --- /dev/null +++ b/src/app/portal/components/results/projects/projects.component.html @@ -0,0 +1,37 @@ + + +
+ +
+ +
+ + + + +
+ + + + diff --git a/src/app/portal/components/results/projects/projects.component.scss b/src/app/portal/components/results/projects/projects.component.scss new file mode 100644 index 000000000..e79e31659 --- /dev/null +++ b/src/app/portal/components/results/projects/projects.component.scss @@ -0,0 +1,6 @@ +// This file is part of the research.fi API service +// +// Copyright 2019 Ministry of Education and Culture, Finland +// +// :author: CSC - IT Center for Science Ltd., Espoo Finland servicedesk@csc.fi +// :license: MIT diff --git a/src/app/portal/components/results/projects/projects.component.scss-theme.scss b/src/app/portal/components/results/projects/projects.component.scss-theme.scss new file mode 100644 index 000000000..aff6da44c --- /dev/null +++ b/src/app/portal/components/results/projects/projects.component.scss-theme.scss @@ -0,0 +1,8 @@ +@use 'src/migration' as *; + +@mixin app-fundings-theme($theme) { + $primary: map-get($theme, primary); + + app-fundings { + } +} diff --git a/src/app/portal/components/results/projects/projects.component.ts b/src/app/portal/components/results/projects/projects.component.ts new file mode 100644 index 000000000..2f8eda73d --- /dev/null +++ b/src/app/portal/components/results/projects/projects.component.ts @@ -0,0 +1,162 @@ +// This file is part of the research.fi API service +// +// Copyright 2019 Ministry of Education and Culture, Finland +// +// :author: CSC - IT Center for Science Ltd., Espoo Finland servicedesk@csc.fi +// :license: MIT + +import { + Component, + Input, + OnInit, + OnDestroy, + AfterViewInit, + ViewChild, + ElementRef, + ChangeDetectorRef, inject, LOCALE_ID +} from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { SortService } from '../../../services/sort.service'; +import { SearchService } from '@portal/services/search.service'; +import { TabChangeService } from '@portal/services/tab-change.service'; +import { Search } from '@portal/models/search.model'; +import { UtilityService } from '@shared/services/utility.service'; +import { TableColumn, TableRow } from 'src/types'; +import { HighlightSearchPipe } from '@portal/pipes/highlight.pipe'; +import { NoResultsComponent } from '../no-results/no-results.component'; +import { ResultsPaginationComponent } from '../results-pagination/results-pagination.component'; +import { TableComponent } from '@shared/components/table/table.component'; +import { MatProgressSpinner } from '@angular/material/progress-spinner'; +import { NgIf } from '@angular/common'; + +@Component({ + selector: 'app-projects', + templateUrl: './projects.component.html', + styleUrls: ['./projects.component.scss'], + standalone: true, + imports: [ + NgIf, + MatProgressSpinner, + TableComponent, + ResultsPaginationComponent, + NoResultsComponent, + ], +}) +export class ProjectsComponent implements OnInit, OnDestroy, AfterViewInit { + @Input() resultData: Search; + @ViewChild('main') mainContent: ElementRef; + expandStatus: Array = []; + sortColumn: string; + sortDirection: boolean; + faIcon: any = this.tabChangeService.tabData + .filter((t) => t.data === 'projects') + .map((t) => t.icon) + .pop(); + inputSub: any; + input: string; + focusSub: any; + + tableColumns: TableColumn[]; + tableRows: Record[]; + + dataMapped: boolean; + projectLinkTitle = $localize`:@@iconProjects: Hankkeiden tiedon ikoni`; + + constructor( + private route: ActivatedRoute, + private sortService: SortService, + private tabChangeService: TabChangeService, + private searchService: SearchService, + private cdr: ChangeDetectorRef, + public utilityService: UtilityService, + private highlightPipe: HighlightSearchPipe + ) {} + + ngOnInit() { + this.sortService.initSort(this.route.snapshot.queryParams.sort || ''); + this.sortColumn = this.sortService.sortColumn; + this.sortDirection = this.sortService.sortDirection; + this.inputSub = this.searchService.currentInput.subscribe((input) => { + this.input = input; + this.mapData(); + this.cdr.detectChanges(); + }); + } + + ngAfterViewInit() { + // Focus first element when clicked with skip-link + this.focusSub = this.tabChangeService.currentFocusTarget.subscribe( + (target) => { + if (target === 'main') { + this.mainContent?.nativeElement.focus(); + } + } + ); + } + + mapData() { + // Map data to table + // Use highlight pipe for higlighting search term + this.tableColumns = [ + { + key: 'name', + label: 'Hanke', + class: 'col-8 col-lg-5 col-xl-3', + mobile: true, + }, + { + key: 'abbreviation', + label: 'Akronyymi', + class: 'col-lg-4 col-xl-3 d-none d-lg-block', + mobile: false, + }, + { + key: 'organization', + label: 'Organisaatio', + class: 'col-xl-3 d-none d-xl-block', + mobile: false, + }, + { + key: 'year', + label: 'Aloitusvuosi', + class: 'col-lg-2', + mobile: true, + }, + ]; + this.tableRows = this.resultData.projects.map((project) => ({ + name: { + label: this.highlightPipe.transform(project.name, this.input), + title: project.name, + link: `/results/project/${project.id}`, + }, + abbreviation: { + label: this.highlightPipe.transform(project.abbreviation, this.input), + }, + organization: { + label: this.highlightPipe.transform( + project.responsibleOrganization, + this.input + ), + }, + year: { + label: this.highlightPipe.transform(project.startYear, this.input), + }, + })); + + this.dataMapped = true; + } + + ngOnDestroy() { + this.inputSub?.unsubscribe(); + this.focusSub?.unsubscribe(); + this.tabChangeService.targetFocus(''); + } +} + +function justOrgIfFundedBySuomenAkatemia(funding) { + if (funding.funder.name === "Suomen Akatemia" || funding.funder.name === "Research Council of Finland" || funding.funder.name === "Finlands Akademi") { + return funding.recipient.organizationName + } else { + return funding.recipient.personNameAndOrg; + } +} diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 6aa4cf9ff..07e78f1c3 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -654,8 +654,6 @@ export class Publications2Component implements OnDestroy { } setPageSize(size: number) { - console.log(size); - this.router.navigate([], { relativeTo: this.route, // skipLocationChange: true, diff --git a/src/app/portal/components/results/results.component.html b/src/app/portal/components/results/results.component.html index 3b33eabb5..1bc39b697 100644 --- a/src/app/portal/components/results/results.component.html +++ b/src/app/portal/components/results/results.component.html @@ -23,7 +23,7 @@

-
+
Testaa haun uudistettua betaversiota. Betaversio toimii vain julkaisujen hakuun. Tiedot vastaavat nykyisen sivuston sisältöä julkaisujen osalta.
@@ -31,19 +31,20 @@

+ *ngIf="tab === 'publications'" + role="link" + routerLink='/results/publications2' + content="Uuteen hakuun" + i18n-content="@@publication2.toNewSearch">
+ + +