Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor informal taxon groups usage according to api changes #701

Merged
merged 6 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<laji-spinner [spinning]="!selectedInformalGroup && !informalGroups">
<laji-informal-list-breadcrumb *ngIf="showBreadcrumb"
[informalGroup]="selectedInformalGroup"
[parentGroups]="parentGroups"
[parentGroup]="parentGroup"
(informalGroupSelect)="informalGroupSelect.emit($event)">
</laji-informal-list-breadcrumb>
<laji-informal-list [informalTaxonGroups]="informalGroups"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class InformalGroupSelectComponent implements OnInit, OnDestroy, OnChange

public selectedInformalGroup: InformalTaxonGroup | undefined;
public informalGroups: InformalTaxonGroup[] = [];
public parentGroups: InformalTaxonGroup[] = [];
public parentGroup?: InformalTaxonGroup;

private sub = new Subscription();

Expand All @@ -42,7 +42,6 @@ export class InformalGroupSelectComponent implements OnInit, OnDestroy, OnChange
}

private refreshInformalGroups() {
this.parentGroups = [];
this.selectedInformalGroup = undefined;

if (!this.id) {
Expand All @@ -59,7 +58,7 @@ export class InformalGroupSelectComponent implements OnInit, OnDestroy, OnChange
this.selectedInformalGroup = data[1];
});

this.informalTaxonService.informalTaxonGroupGetParents(this.id, this.translate.currentLang)
.subscribe(data => this.parentGroups = data, () => {});
this.informalTaxonService.informalTaxonGroupGetParent(this.id, this.translate.currentLang)
.subscribe(data => this.parentGroup = data, () => {});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<li routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<a (click)="informalGroupSelect.emit()" tabindex="0" role="button" luKeyboardClickable>{{ 'observation.active.informalTaxonGroupAll' | translate }}</a>
</li>
<li *ngFor="let group of parentGroups; let i = index; let active = last;">
<a (click)="informalGroupSelect.emit(group.id)" tabindex="0" role="button" luKeyboardClickable>{{ group.name }}</a>
<li *ngIf="parentGroup">
<a (click)="informalGroupSelect.emit(parentGroup.id)" tabindex="0" role="button" luKeyboardClickable>{{ parentGroup.name }}</a>
</li>
<li *ngIf="informalGroup" class="active">
<span>{{ informalGroup.name }}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ import { InformalTaxonGroup } from '../../../../shared/model/InformalTaxonGroup'

export class InformalListBreadcrumbComponent {
@Input() informalGroup?: InformalTaxonGroup;
@Input() parentGroups!: Array<InformalTaxonGroup>;
@Input() parentGroup?: InformalTaxonGroup;
@Output() informalGroupSelect = new EventEmitter<string>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SelectedOption, TreeOptionsChangeEvent, TreeOptionsNode } from '../tree
import { Util } from '../../shared/service/util.service';
import { WarehouseQueryInterface } from '../../shared/model/WarehouseQueryInterface';
import { RedListTaxonGroup } from '../../shared/model/RedListTaxonGroup';
import { ArrayResult } from '../../shared/model/ArrayResult';

export interface InformalGroupEvent {
[key: string]: string[];
Expand Down Expand Up @@ -60,7 +61,7 @@ export abstract class ExtendedGroupSelectComponent<T extends RedListTaxonGroup|I

abstract findByIds(groupIds: string[], lang: string): Observable<PagedResult<T>>;
abstract convertToInformalTaxonGroup(group: T): InformalTaxonGroup;
abstract getTree(lang: string): Observable<PagedResult<T>>;
abstract getTree(lang: string): Observable<ArrayResult<T>>;
abstract getOptions(query: Record<string, any>): string[][];
abstract prepareEmit(includedOptions: string[], excludedOptions?: string[]): InformalGroupEvent;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ExtendedGroupSelectComponent, InformalGroupEvent } from './extended-gro
import { InformalTaxonGroup } from '../../shared/model/InformalTaxonGroup';
import { PagedResult } from '../../shared/model/PagedResult';
import { RedListTaxonGroup } from '../../shared/model/RedListTaxonGroup';
import { ArrayResult } from '../../shared/model/ArrayResult';

export const OBSERVATION_GROUP_SELECT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
Expand Down Expand Up @@ -39,14 +40,14 @@ export class ObservationExtendedGroupSelectComponent extends ExtendedGroupSelect
}

findByIds(groupIds: string[], lang: string): Observable<PagedResult<RedListTaxonGroup>> {
return this.informalTaxonService.informalTaxonGroupFind(lang, undefined, undefined, {idIn: groupIds});
return this.informalTaxonService.informalTaxonGroupFind(lang, undefined, undefined, groupIds);
}

convertToInformalTaxonGroup(group: InformalTaxonGroup): InformalTaxonGroup {
return {...group};
}

getTree(lang: string): Observable<PagedResult<InformalTaxonGroup>> {
getTree(lang: string): Observable<ArrayResult<InformalTaxonGroup>> {
return this.informalTaxonService.informalTaxonGroupGetTree(lang);
}

Expand Down
127 changes: 19 additions & 108 deletions projects/laji/src/app/shared/api/InformalTaxonGroupApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,11 @@ import { HttpClient } from '@angular/common/http';
import { Util } from '../service/util.service';
import { InformalTaxonGroup } from '../model/InformalTaxonGroup';
import { environment } from '../../../environments/environment';


'use strict';
import { ArrayResult } from '../model/ArrayResult';

@Injectable({providedIn: 'root'})
export class InformalTaxonGroupApi {
protected basePath = environment.apiBase;
protected basePath = `${environment.apiBase}/informal-taxon-groups`;

constructor(protected http: HttpClient) {
}
Expand All @@ -47,12 +45,8 @@ export class InformalTaxonGroupApi {
* @param page Page number
* @param pageSize Page size
*/
public informalTaxonGroupFind(lang?: string, page?: string, pageSize?: string, extraHttpRequestParams?: any): Observable<PagedResult<InformalTaxonGroup>> {
const path = this.basePath + '/informal-taxon-groups';

const queryParameters = {...Util.removeFromObject(extraHttpRequestParams)};

return this.httpGet<InformalTaxonGroup>(path, queryParameters, lang, page, pageSize);
public informalTaxonGroupFind(lang?: string, page?: string, pageSize?: string, idIn?: string[]): Observable<PagedResult<InformalTaxonGroup>> {
return this.http.get<PagedResult<InformalTaxonGroup>>(this.basePath, { params: Util.withNonNullableKeys({ lang, page, pageSize, idIn }) });
}

/**
Expand All @@ -61,20 +55,11 @@ export class InformalTaxonGroupApi {
* @param id
* @param lang Language of fields that have multiple languages. Return english if asked language not found. If multi is selected fields will contain language objects
*/
public informalTaxonGroupFindById(id: string, lang?: string, extraHttpRequestParams?: any): Observable<InformalTaxonGroup> {
const path = this.basePath + '/informal-taxon-groups/{id}'
.replace('{' + 'id' + '}', String(id));

const queryParameters = {...Util.removeFromObject(extraHttpRequestParams)};
// verify required parameter 'id' is not null or undefined
if (id === null || id === undefined) {
throw new Error('Required parameter id was null or undefined when calling informalTaxonGroupFindById.');
public informalTaxonGroupFindById(id: string, lang?: string): Observable<InformalTaxonGroup> {
if (!id.length) {
throw new Error('Required parameter id was empty when calling informalTaxonGroupFindById.');
}
if (lang !== undefined) {
queryParameters['lang'] = lang;
}

return this.http.get<InformalTaxonGroup>(path, {params: queryParameters});
return this.http.get<InformalTaxonGroup>(`${this.basePath}/${id}`, {params: Util.withNonNullableKeys({ lang })});
}

/**
Expand All @@ -84,12 +69,8 @@ export class InformalTaxonGroupApi {
* @param page Page number
* @param pageSize Page size
*/
public informalTaxonGroupFindRoots(lang?: string, page?: string, pageSize?: string, extraHttpRequestParams?: any): Observable<PagedResult<InformalTaxonGroup>> {
const path = this.basePath + '/informal-taxon-groups/roots';

const queryParameters = {...Util.removeFromObject(extraHttpRequestParams)};

return this.httpGet<InformalTaxonGroup>(path, queryParameters, lang, page, pageSize);
public informalTaxonGroupFindRoots(lang?: string): Observable<ArrayResult<InformalTaxonGroup>> {
return this.http.get<ArrayResult<InformalTaxonGroup>>(`${this.basePath}/roots`, { params: Util.withNonNullableKeys({ lang }) });
}

/**
Expand All @@ -100,60 +81,18 @@ export class InformalTaxonGroupApi {
* @param page Page number
* @param pageSize Page size
*/
public informalTaxonGroupGetChildren(id: string, lang?: string, page?: string, pageSize?: string, extraHttpRequestParams?: any): Observable<PagedResult<InformalTaxonGroup>> {
const path = this.basePath + '/informal-taxon-groups/{id}/children'
.replace('{' + 'id' + '}', String(id));

const queryParameters = {...Util.removeFromObject(extraHttpRequestParams)};
// verify required parameter 'id' is not null or undefined
if (id === null || id === undefined) {
throw new Error('Required parameter id was null or undefined when calling informalTaxonGroupGetChildren.');
}

return this.httpGet<InformalTaxonGroup>(path, queryParameters, lang, page, pageSize);
}

/**
* Get parents for a informal group
*
* @param id
* @param lang Language of fields that have multiple languages. Return english if asked language not found.
*/
public informalTaxonGroupGetParents(id: string, lang?: string, extraHttpRequestParams?: any): Observable<Array<InformalTaxonGroup>> {
const path = this.basePath + '/informal-taxon-groups/{id}/parents'
.replace('{' + 'id' + '}', String(id));

const queryParameters = {...Util.removeFromObject(extraHttpRequestParams)};
// verify required parameter 'id' is not null or undefined
if (id === null || id === undefined) {
throw new Error('Required parameter id was null or undefined when calling informalTaxonGroupGetChildren.');
}
if (lang !== undefined) {
queryParameters['lang'] = lang;
}

return this.http.get<InformalTaxonGroup[]>(path, {params: queryParameters});
public informalTaxonGroupGetChildren(id: string, lang?: string): Observable<ArrayResult<InformalTaxonGroup>> {
return this.http.get<ArrayResult<InformalTaxonGroup>>(`${this.basePath}/${id}/children`, { params: Util.withNonNullableKeys({ lang }) } );
}

/**
* Get current groups parents and parents siblings
* Get parent for a informal group
*
* @param id
* @param lang Language of fields that have multiple languages. Return english if asked language not found.
* @param page Page number
* @param pageSize Page size
*/
public informalTaxonGroupGetParentLevel(id: string, lang?: string, page?: string, pageSize?: string, extraHttpRequestParams?: any): Observable<PagedResult<InformalTaxonGroup>> {
const path = this.basePath + '/informal-taxon-groups/{id}/parentLevel'
.replace('{' + 'id' + '}', String(id));

const queryParameters = {...Util.removeFromObject(extraHttpRequestParams)};
// verify required parameter 'id' is not null or undefined
if (id === null || id === undefined) {
throw new Error('Required parameter id was null or undefined when calling informalTaxonGroupGetParentLevel.');
}

return this.httpGet<InformalTaxonGroup>(path, queryParameters, lang, page, pageSize);
public informalTaxonGroupGetParent(id: string, lang?: string): Observable<InformalTaxonGroup> {
return this.http.get<InformalTaxonGroup>(`${this.basePath}/${id}/parent`, { params: Util.withNonNullableKeys({ lang })});
}

/**
Expand All @@ -163,11 +102,8 @@ export class InformalTaxonGroupApi {
* @param page Page number
* @param pageSize Page size
*/
public informalTaxonGroupGetTree(lang?: string, page?: string, pageSize?: string, extraHttpRequestParams?: any): Observable<PagedResult<InformalTaxonGroup>> {
const path = this.basePath + '/informal-taxon-groups/tree';
const queryParameters = {...Util.removeFromObject(extraHttpRequestParams)};

return this.httpGet<InformalTaxonGroup>(path, queryParameters, lang, page, pageSize);
public informalTaxonGroupGetTree(lang?: string): Observable<ArrayResult<InformalTaxonGroup>> {
return this.http.get<ArrayResult<InformalTaxonGroup>>(`${this.basePath}/tree`, { params: Util.withNonNullableKeys({ lang }) });
}

/**
Expand All @@ -178,32 +114,7 @@ export class InformalTaxonGroupApi {
* @param page Page number
* @param pageSize Page size
*/
public informalTaxonGroupGetWithSiblings(id: string, lang?: string, page?: string, pageSize?: string, extraHttpRequestParams?: any): Observable<PagedResult<InformalTaxonGroup>> {
const path = this.basePath + '/informal-taxon-groups/{id}/siblings'
.replace('{' + 'id' + '}', String(id));

const queryParameters = {...Util.removeFromObject(extraHttpRequestParams)};
// verify required parameter 'id' is not null or undefined
if (id === null || id === undefined) {
throw new Error('Required parameter id was null or undefined when calling informalTaxonGroupGetWithSiblings.');
}

return this.httpGet<InformalTaxonGroup>(path, queryParameters, lang, page, pageSize);
}

private httpGet<T>(path: string, queryParameters: any, lang?: string, page?: string, pageSize?: string) {
if (lang !== undefined) {
queryParameters['lang'] = lang;
}

if (page !== undefined) {
queryParameters['page'] = page;
}

if (pageSize !== undefined) {
queryParameters['pageSize'] = pageSize;
}

return this.http.get<PagedResult<T>>(path, { params: queryParameters });
public informalTaxonGroupGetWithSiblings(id: string, lang?: string): Observable<ArrayResult<InformalTaxonGroup>> {
return this.http.get<ArrayResult<InformalTaxonGroup>>(`${this.basePath}/${id}/siblings`, { params: Util.withNonNullableKeys({ lang }) });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Logger } from '../logger/logger.service';
import { TranslateService } from '@ngx-translate/core';
import { Group } from '../model/Group';
import { PagedResult } from '../model/PagedResult';
import { ArrayResult } from '../model/ArrayResult';

@Directive()
export abstract class GroupSelectComponent<T extends Group> implements ControlValueAccessor, OnChanges {
Expand Down Expand Up @@ -152,7 +153,7 @@ export abstract class GroupSelectComponent<T extends Group> implements ControlVa
}
}

getRoot(lang: string): Observable<PagedResult<T>> {
getRoot(lang: string): Observable<ArrayResult<T>> {
if (this.rootGroups) {
return this.findByIds(this.rootGroups, lang);
}
Expand All @@ -161,9 +162,9 @@ export abstract class GroupSelectComponent<T extends Group> implements ControlVa

abstract findById(groupId: string, lang: string): Observable<T>;
abstract findByIds(groupIds: string[], lang: any): Observable<PagedResult<T>>;
abstract getWithSiblings(groupId: any, lang: string): Observable<PagedResult<T>>;
abstract getChildren(groupId: any, lang: string): Observable<PagedResult<T>>;
abstract findRoots(lang: any): Observable<PagedResult<T>>;
abstract getWithSiblings(groupId: any, lang: string): Observable<ArrayResult<T>>;
abstract getChildren(groupId: any, lang: string): Observable<ArrayResult<T>>;
abstract findRoots(lang: any): Observable<ArrayResult<T>>;
abstract convertToInformalTaxonGroup(group: T): InformalTaxonGroup;

empty() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { GroupSelectComponent } from './group-select.component';
import { InformalTaxonGroup } from '../model/InformalTaxonGroup';
import { PagedResult } from '../model/PagedResult';
import { RedListTaxonGroup } from '../model/RedListTaxonGroup';
import { ArrayResult } from '../model/ArrayResult';

export const OBSERVATION_GROUP_SELECT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
Expand Down Expand Up @@ -39,18 +40,18 @@ export class ObservationGroupSelectComponent extends GroupSelectComponent<Inform
}

findByIds(groupIds: string[], lang: string | undefined): Observable<PagedResult<RedListTaxonGroup>> {
return this.informalTaxonService.informalTaxonGroupFind(lang, undefined, undefined, {idIn: groupIds});
return this.informalTaxonService.informalTaxonGroupFind(lang, undefined, undefined, groupIds);
}

getWithSiblings(groupId: string, lang: string | undefined): Observable<PagedResult<RedListTaxonGroup>> {
getWithSiblings(groupId: string, lang: string | undefined): Observable<ArrayResult<RedListTaxonGroup>> {
return this.informalTaxonService.informalTaxonGroupGetWithSiblings(groupId, lang);
}

getChildren(groupId: string, lang: string | undefined): Observable<PagedResult<RedListTaxonGroup>> {
getChildren(groupId: string, lang: string | undefined): Observable<ArrayResult<RedListTaxonGroup>> {
return this.informalTaxonService.informalTaxonGroupGetChildren(groupId, lang);
}

findRoots(lang: string | undefined): Observable<PagedResult<RedListTaxonGroup>> {
findRoots(lang: string | undefined): Observable<ArrayResult<RedListTaxonGroup>> {
return this.informalTaxonService.informalTaxonGroupFindRoots(lang);
}

Expand Down
4 changes: 4 additions & 0 deletions projects/laji/src/app/shared/model/ArrayResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

export interface ArrayResult<T> {
results: T[];
Blodir marked this conversation as resolved.
Show resolved Hide resolved
}
20 changes: 20 additions & 0 deletions projects/laji/src/app/shared/service/util.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { NavigationEnd, Event } from '@angular/router';
import * as merge from 'deepmerge';
import { Document } from '../model/Document';

export type WithNonNullableKeys<T, K extends keyof T> = T & {
[P in K]-?: NonNullable<T[P]>;
};

export class Util {
/**
* Clones the object using JSON stringify
Expand Down Expand Up @@ -66,6 +70,22 @@ export class Util {
}, {} as Partial<T>);
}

/**
* Remove all undefined and null values from object
*
* @param obj object to remove keys from
* @param keys array of keys that should be removed
*/
public static withNonNullableKeys<T extends {[prop: string]: any}, K extends keyof T>(obj: T): WithNonNullableKeys<T, K> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be removeNullableFields or at least withNoNullableKeys? This name sounds to me like you are adding extra keys rather than removing nullable keys

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another alternative: getNonNullableKeys

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The term NonNullable comes from TypeScript: NonNullable

Though the "keys" word usage is weird... How about withNonNullableValues? getNonNullableKeys sounds to me like it would return keys of an object.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed withNonNullableValues as a proposal. Updated the JS doc comment also. It was copy pasted from some other utility fn so there was an extra param.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't think I would immediately understand what withNonNullableValues does without reading the comment. Usually the word with I would associate with adding something to the result, eg. withLatestFrom in rxjs. Maybe excludeNullables or invert it selectNonNullables.

Sorry this is getting rather pedantic... I'll give the PR an approve.

return Object.keys(obj).reduce((cumulative, current: keyof T) => {
if (obj[current] !== undefined && obj[current] !== null) {
(cumulative as any)[current] = obj[current];
}
return cumulative;
}, {} as WithNonNullableKeys<T, K>);
}


/**
* Add leading zero so that the length of return string will be 2
*/
Expand Down
Loading