Skip to content

Commit 7b1da3d

Browse files
Merge pull request #1493 from CSCfi/persons-initial-implementation
Initial implementation for person results and details
2 parents a9fecf7 + 44b05e6 commit 7b1da3d

File tree

72 files changed

+2147
-712
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+2147
-712
lines changed

src/app/app.component.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
// :license: MIT
77

88
import { Component, Inject, PLATFORM_ID } from '@angular/core';
9-
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
9+
import { DOCUMENT, isPlatformBrowser, PlatformLocation } from '@angular/common';
1010
import { AppConfigService } from '@shared/services/app-config-service.service';
1111
import { OidcSecurityService } from 'angular-auth-oidc-client';
1212
import { NavigationStart, Router } from '@angular/router';
1313
import { take } from 'rxjs/operators';
1414
import 'reflect-metadata'; // Required by ApmService
1515
import { ApmService } from '@elastic/apm-rum-angular';
16+
import { AppSettingsService } from '@shared/services/app-settings.service';
1617

1718
@Component({
1819
selector: 'app-root',
@@ -25,7 +26,9 @@ export class AppComponent {
2526
constructor(
2627
private appConfigService: AppConfigService,
2728
private oidcSecurityService: OidcSecurityService,
29+
private appSettingsService: AppSettingsService,
2830
private router: Router,
31+
private platform: PlatformLocation,
2932
@Inject(PLATFORM_ID) private platformId: object,
3033
@Inject(DOCUMENT) private document: any,
3134
@Inject(ApmService) apmService: ApmService
@@ -49,9 +52,30 @@ export class AppComponent {
4952
// ],
5053
// });
5154

52-
// Start auth process
5355
this.router.events.pipe(take(1)).subscribe((e) => {
5456
if (e instanceof NavigationStart) {
57+
if (isPlatformBrowser(this.platformId)) {
58+
// List of host identifiers for enablding or disabling content in matching host
59+
const allowedHostIdentifiers = [
60+
'localhost',
61+
'test',
62+
'qa',
63+
'mydata',
64+
];
65+
66+
const checkHostMatch = (host: string) =>
67+
this.platform.hostname.includes(host);
68+
69+
if (!allowedHostIdentifiers.some(checkHostMatch)) {
70+
// Prevent development implementation of MyData routes in production
71+
e.url.includes('/mydata') && this.router.navigate(['/']);
72+
} else {
73+
// Global flag for preventing predefined content in production
74+
this.appSettingsService.develop = true;
75+
}
76+
}
77+
78+
// Start MyData auth process
5579
if (e.url.includes('/mydata')) {
5680
this.oidcSecurityService.checkAuth().subscribe(() => {});
5781
}

src/app/layout/header/header.component.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -140,26 +140,10 @@ export class HeaderComponent implements OnInit, OnDestroy {
140140
this.isAuthenticated = this.oidcSecurityService.isAuthenticated$;
141141
}
142142

143-
/*
144-
* Current route based features
145-
* MyData -module uses authentication and this process needs to start only in '/mydata' routes
146-
*/
147143
routeEvent(router: Router) {
148144
this.routeSub = router.events.subscribe((e) => {
149145
if (e instanceof NavigationEnd) {
150146
if (isPlatformBrowser(this.platformId)) {
151-
// Prevent MyData routes in production
152-
const allowedHostIdentifiers = ['localhost', 'test', 'qa', 'mydata'];
153-
const checkHostMatch = (host: string) =>
154-
this.platform.hostname.includes(host);
155-
156-
if (
157-
!allowedHostIdentifiers.some(checkHostMatch) &&
158-
e.url.includes('/mydata')
159-
) {
160-
this.router.navigate(['/']);
161-
}
162-
163147
// Check if consent has been chosen & set variable. This is used in preserving consent status between language versions
164148
if (localStorage.getItem('cookieConsent')) {
165149
this.consent = localStorage.getItem('cookieConsent');

src/app/portal/components/result-tab/tab-item/tab-item.component.html

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
></ng-template>
99
<a
1010
class="bundler item-wrapper"
11-
[class.disabled]="tab.data.length === 0"
12-
[class.disabled-home]="isHomepage && tab.data.length === 0"
11+
[class.disabled]="tab.data.length === 0 || tab.disabled"
12+
[class.disabled-home]="isHomepage && (tab.data.length === 0 || tab.disabled)"
1313
[class.homepage]="isHomepage"
1414
[class.active]="selectedTab === tab.link"
1515
[queryParams]="targetQueryParams"
@@ -30,20 +30,23 @@
3030
<div class="label text-center" [class.homepage-label]="isHomepage">
3131
{{ tab.label }}
3232
</div>
33-
<div
34-
class="numeric-box d-sm-block"
35-
[countUp]="
36-
tab.data
37-
? counted.aggregations._index.buckets[tab.data]?.doc_count
38-
: 999
39-
"
40-
[options]="countOps"
41-
[reanimateOnClick]="false"
42-
>
43-
0
33+
<div class="numeric-box d-sm-block">
34+
<div
35+
*ngIf="!tab.disabled"
36+
[countUp]="
37+
tab.data
38+
? counted.aggregations._index.buckets[tab.data]?.doc_count
39+
: 999
40+
"
41+
[options]="countOps"
42+
[reanimateOnClick]="false"
43+
>
44+
0
45+
</div>
46+
<div *ngIf="tab.disabled" i18n="@@comingSoon">Tulossa</div>
4447
</div>
45-
<!-- TODO: Remove the ternary operator when all data becomes available -->
4648
</div>
49+
4750
<ng-template #disabledTab>
4851
<div
4952
class="tab-icon"

src/app/portal/components/results/filters/filters.component.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ export class FiltersComponent implements OnInit, OnDestroy, OnChanges {
182182
if (isPlatformBrowser(this.platformId)) {
183183
this.activeElement = this.document.activeElement.id;
184184
}
185+
185186
// Initialize data and set filter data by index
186187
if (this.responseData) {
187188
// Set filters and shape data
@@ -196,9 +197,9 @@ export class FiltersComponent implements OnInit, OnDestroy, OnChanges {
196197
break;
197198
}
198199
case 'persons': {
199-
// this.currentFilter = this.personFilters.filterData;
200+
this.currentFilter = this.personFilters.filterData;
201+
this.personFilters.shapeData(this.responseData);
200202
// this.currentSingleFilter = this.personFilters.singleFilterData;
201-
// TODO: Shape data
202203
break;
203204
}
204205
case 'fundings': {

src/app/portal/components/results/persons/persons.component.html

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,50 @@
33
:author: CSC - IT Center for Science Ltd., Espoo Finland servicedesk@csc.fi
44
:license: MIT -->
55

6-
<div class="row" *ngFor="let persons of resultData">
7-
<div
8-
class="col-12 no-margin single"
9-
*ngFor="let person of persons.hits.hits; let i = index"
10-
>
11-
<li>{{ person._source.lastName }}</li>
6+
<div class="spinner-center" *ngIf="!resultData">
7+
<mat-spinner
8+
[diameter]="80"
9+
i18n-aria-label="@@loading"
10+
aria-label="Ladataan"
11+
></mat-spinner>
12+
</div>
13+
14+
<!-- Results -->
15+
<div *ngIf="resultData?.total > 0; else noResults">
16+
<app-table
17+
*ngIf="dataMapped"
18+
[columns]="tableColumns"
19+
[rows]="tableRows"
20+
[alignCenter]="true"
21+
[icon]="rowIcon"
22+
[iconTitle]="iconTitle"
23+
iconLinkField="name"
24+
[sortColumn]="sortService.sortColumn"
25+
[sortDirection]="sortService.sortDirection ? 'desc' : 'asc'"
26+
(onSortChange)="utilityService.sortBy($event.active)"
27+
></app-table>
28+
29+
<!-- Templates -->
30+
<div *ngIf="resultData" class="result-container">
31+
<div *ngFor="let person of resultData.persons; let i = index">
32+
<ng-template #personNameColumn>
33+
<a
34+
routerLink="/results/person/{{ person.id }}"
35+
[innerHtml]="person.name | highlight: input"
36+
>
37+
</a>
38+
<app-orcid></app-orcid>
39+
</ng-template>
40+
</div>
1241
</div>
42+
43+
<!-- Pagination -->
44+
<app-results-pagination [data]="resultData"></app-results-pagination>
1345
</div>
46+
47+
<ng-template #noResults>
48+
<app-no-results
49+
*ngIf="resultData && resultData.total === 0"
50+
heading="Ei tutkijoita"
51+
></app-no-results>
52+
</ng-template>

src/app/portal/components/results/persons/persons.component.ts

Lines changed: 137 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,148 @@
55
// :author: CSC - IT Center for Science Ltd., Espoo Finland servicedesk@csc.fi
66
// :license: MIT
77

8-
import { Component, Input } from '@angular/core';
8+
import {
9+
AfterViewInit,
10+
ChangeDetectorRef,
11+
Component,
12+
ElementRef,
13+
Input,
14+
OnInit,
15+
QueryList,
16+
TemplateRef,
17+
ViewChild,
18+
ViewChildren,
19+
} from '@angular/core';
20+
import { faUser } from '@fortawesome/free-solid-svg-icons';
21+
import { Search } from '@portal/models/search.model';
22+
import { HighlightSearch } from '@portal/pipes/highlight.pipe';
23+
import { SearchService } from '@portal/services/search.service';
24+
import { SortService } from '@portal/services/sort.service';
25+
import { TabChangeService } from '@portal/services/tab-change.service';
26+
import { Subscription } from 'rxjs';
27+
import { TableColumn, TableRow } from 'src/types';
28+
import { UtilityService } from '@shared/services/utility.service';
929

1030
@Component({
1131
selector: 'app-persons',
1232
templateUrl: '../persons/persons.component.html',
1333
styleUrls: ['./persons.component.scss'],
1434
})
15-
export class PersonsComponent {
16-
@Input() resultData: any[];
35+
export class PersonsComponent implements OnInit, AfterViewInit {
36+
@Input() resultData: Search;
1737
expandStatus: Array<boolean> = [];
38+
39+
inputSub: Subscription;
40+
input: string;
41+
focusSub: any;
42+
tableColumns: TableColumn[];
43+
tableRows: Record<string, TableRow>[];
44+
rowIcon = faUser;
45+
iconTitle: 'Tutkijoiden tiedon ikoni';
46+
dataMapped: boolean;
47+
48+
@ViewChild('main') mainContent: ElementRef;
49+
50+
@ViewChildren('personNameColumn', { read: TemplateRef })
51+
personNameColumns: QueryList<ElementRef>;
52+
53+
constructor(
54+
private searchService: SearchService,
55+
private cdr: ChangeDetectorRef,
56+
private highlightPipe: HighlightSearch,
57+
private tabChangeService: TabChangeService,
58+
public sortService: SortService,
59+
public utilityService: UtilityService
60+
) {}
61+
62+
ngOnInit(): void {}
63+
64+
ngAfterViewInit() {
65+
// Focus first element when clicked with skip-link
66+
this.focusSub = this.tabChangeService.currentFocusTarget.subscribe(
67+
(target) => {
68+
if (target === 'main') {
69+
this.mainContent?.nativeElement.focus();
70+
}
71+
}
72+
);
73+
74+
this.inputSub = this.searchService.currentInput.subscribe((input) => {
75+
this.input = input;
76+
this.mapData();
77+
this.cdr.detectChanges();
78+
});
79+
}
80+
81+
mapData() {
82+
// Get cell data from template
83+
const nameColumnArray = this.personNameColumns.toArray();
84+
85+
// Map data to table
86+
// Use highlight pipe for higlighting search term
87+
this.tableColumns = [
88+
{
89+
key: 'name',
90+
label: $localize`:@@name:Nimi`,
91+
class: 'col-3',
92+
mobile: true,
93+
},
94+
{
95+
key: 'organization',
96+
label: $localize`:@@organization:Organisaatio`,
97+
class: 'col-3',
98+
mobile: true,
99+
},
100+
{
101+
key: 'positionName',
102+
label: 'Nimike',
103+
class: 'col-3',
104+
mobile: true,
105+
},
106+
{
107+
key: 'keywords',
108+
label: $localize`:@@keywords:Avainsanat`,
109+
class: 'col-2',
110+
mobile: true,
111+
},
112+
];
113+
114+
const getUnique = (items: string[]) => [...new Set(items)];
115+
116+
this.tableRows = this.resultData.persons.map((person, index) => ({
117+
name: {
118+
template: nameColumnArray[index],
119+
link: `/results/person/${person.id}`,
120+
},
121+
organization: {
122+
label: this.highlightPipe.transform(
123+
getUnique(
124+
person.affiliations.organizations.map((item) => item.name)
125+
).join(', '),
126+
this.input
127+
),
128+
},
129+
positionName: {
130+
label: this.highlightPipe.transform(
131+
getUnique(
132+
person.affiliations.organizations
133+
.flatMap((organanization) => organanization.items)
134+
.map((item) => item.positionName)
135+
).join(', '),
136+
this.input
137+
),
138+
},
139+
keywords: {
140+
label: this.highlightPipe.transform(person.keywords, this.input),
141+
},
142+
}));
143+
144+
this.dataMapped = true;
145+
}
146+
147+
ngOnDestroy() {
148+
this.inputSub?.unsubscribe();
149+
this.focusSub?.unsubscribe();
150+
this.tabChangeService.targetFocus('');
151+
}
18152
}

0 commit comments

Comments
 (0)