From 0c106a7ffc3059ab747e44d0106b4bf320e8b28e Mon Sep 17 00:00:00 2001 From: sagely1 <114952739+sagely1@users.noreply.github.com> Date: Thu, 10 Oct 2024 21:41:10 +0000 Subject: [PATCH] checking in progress of gene-search component --- apps/agora/api/src/components/teams.ts | 12 +- .../src/lib/.openapi-generator/FILES | 1 + .../api-client-angular/src/lib/README.md | 24 +- .../src/lib/api/dataversion.service.ts | 268 +++++++++------- .../src/lib/api/genes.service.ts | 10 +- .../src/lib/configuration.ts | 290 +++++++++--------- .../api-client-angular/src/lib/encoder.ts | 24 +- .../src/lib/model/basicError.ts | 38 ++- .../src/lib/model/dataversion.ts | 12 +- .../src/lib/model/genesList.ts | 19 ++ .../src/lib/model/models.ts | 1 + .../api-client-angular/src/lib/model/team.ts | 16 +- .../src/lib/model/teamMember.ts | 12 +- .../agora/api-client-angular/src/lib/param.ts | 33 +- .../api-client-angular/src/lib/variables.ts | 10 +- libs/agora/api-description/build/openapi.yaml | 12 +- .../src/components/schemas/GenesList.yaml | 7 + .../src/paths/genes/search.yaml | 4 +- .../gene-search/gene-search.component.html | 135 +++++++- .../gene-search/gene-search.component.spec.ts | 2 + .../gene-search/gene-search.component.ts | 188 +++++++++++- libs/agora/home/src/lib/home.component.html | 1 + libs/agora/home/src/lib/home.component.ts | 9 +- .../components/header/header.component.html | 6 +- .../lib/components/header/header.component.ts | 3 +- 25 files changed, 756 insertions(+), 381 deletions(-) create mode 100644 libs/agora/api-client-angular/src/lib/model/genesList.ts create mode 100644 libs/agora/api-description/src/components/schemas/GenesList.yaml diff --git a/apps/agora/api/src/components/teams.ts b/apps/agora/api/src/components/teams.ts index b97530b616..fa3f604d4c 100644 --- a/apps/agora/api/src/components/teams.ts +++ b/apps/agora/api/src/components/teams.ts @@ -56,11 +56,7 @@ export function sortNullProgramsForTeamsLast(teams: Team[]): void { }); } -export async function teamsRoute( - req: Request, - res: Response, - next: NextFunction, -) { +export async function teamsRoute(req: Request, res: Response, next: NextFunction) { try { const teams = await getTeams(); setHeaders(res); @@ -88,11 +84,7 @@ export async function getTeamMemberImage(name: string) { return files[0] || undefined; } -export async function teamMemberImageRoute( - req: Request, - res: Response, - next: NextFunction, -) { +export async function teamMemberImageRoute(req: Request, res: Response, next: NextFunction) { if (!req.params || !req.params.name) { res.status(404).send('Not found'); return; diff --git a/libs/agora/api-client-angular/src/lib/.openapi-generator/FILES b/libs/agora/api-client-angular/src/lib/.openapi-generator/FILES index 5096c294b2..12e2b866df 100644 --- a/libs/agora/api-client-angular/src/lib/.openapi-generator/FILES +++ b/libs/agora/api-client-angular/src/lib/.openapi-generator/FILES @@ -25,6 +25,7 @@ model/gCTGeneNominations.ts model/gCTGeneTissue.ts model/gCTGenesList.ts model/gene.ts +model/genesList.ts model/medianExpression.ts model/models.ts model/neuropathologicCorrelation.ts diff --git a/libs/agora/api-client-angular/src/lib/README.md b/libs/agora/api-client-angular/src/lib/README.md index de16f95a8b..6dd2e6a86d 100644 --- a/libs/agora/api-client-angular/src/lib/README.md +++ b/libs/agora/api-client-angular/src/lib/README.md @@ -3,6 +3,7 @@ ### Building To install the required dependencies and to build the typescript sources run: + ``` npm install npm run build @@ -10,7 +11,7 @@ npm run build ### publishing -First build the package then run ```npm publish dist``` (don't forget to specify the `dist` folder!) +First build the package then run `npm publish dist` (don't forget to specify the `dist` folder!) ### consuming @@ -33,25 +34,25 @@ _It's important to take the tgz file, otherwise you'll get trouble with links on _using `npm link`:_ In PATH_TO_GENERATED_PACKAGE/dist: + ``` npm link ``` In your project: + ``` -npm link +npm link ``` -__Note for Windows users:__ The Angular CLI has troubles to use linked npm packages. +**Note for Windows users:** The Angular CLI has troubles to use linked npm packages. Please refer to this issue https://github.com/angular/angular-cli/issues/8284 for a solution / workaround. Published packages are not effected by this issue. - #### General usage In your Angular project: - ``` // without configuring providers import { ApiModule } from ''; @@ -128,9 +129,11 @@ Note: The ApiModule is restricted to being instantiated once app wide. This is to ensure that all services are treated as singletons. #### Using multiple OpenAPI files / APIs / ApiModules + In order to use multiple `ApiModules` generated from different OpenAPI files, you can create an alias name when importing the modules in order to avoid naming conflicts: + ``` import { ApiModule } from 'my-api-path'; import { ApiModule as OtherApiModule } from 'my-other-api-path'; @@ -150,8 +153,8 @@ export class AppModule { } ``` - ### Set service base path + If different than the generated base path, during app bootstrap, you can provide the base path to your service. ``` @@ -161,6 +164,7 @@ bootstrap(AppComponent, [ { provide: BASE_PATH, useValue: 'https://your-web-service.com' }, ]); ``` + or ``` @@ -175,8 +179,8 @@ import { BASE_PATH } from ''; export class AppModule {} ``` - #### Using @angular/cli + First extend your `src/environments/*.ts` files by adding the corresponding base path: ``` @@ -187,6 +191,7 @@ export const environment = { ``` In the src/app/app.module.ts: + ``` import { BASE_PATH } from ''; import { environment } from '../environments/environment'; @@ -215,10 +220,11 @@ pass an arrow-function or method-reference to the `encodeParam` property of the (see [General Usage](#general-usage) above). Example value for use in your Configuration-Provider: + ```typescript new Configuration({ - encodeParam: (param: Param) => myFancyParamEncoder(param), -}) + encodeParam: (param: Param) => myFancyParamEncoder(param), +}); ``` [parameter-locations-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-locations diff --git a/libs/agora/api-client-angular/src/lib/api/dataversion.service.ts b/libs/agora/api-client-angular/src/lib/api/dataversion.service.ts index edb892117e..90dfe54959 100644 --- a/libs/agora/api-client-angular/src/lib/api/dataversion.service.ts +++ b/libs/agora/api-client-angular/src/lib/api/dataversion.service.ts @@ -3,7 +3,7 @@ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * * The version of the OpenAPI document: 1.0.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -11,12 +11,18 @@ */ /* tslint:disable:no-unused-variable member-ordering */ -import { Inject, Injectable, Optional } from '@angular/core'; -import { HttpClient, HttpHeaders, HttpParams, - HttpResponse, HttpEvent, HttpParameterCodec, HttpContext - } from '@angular/common/http'; -import { CustomHttpParameterCodec } from '../encoder'; -import { Observable } from 'rxjs'; +import { Inject, Injectable, Optional } from '@angular/core'; +import { + HttpClient, + HttpHeaders, + HttpParams, + HttpResponse, + HttpEvent, + HttpParameterCodec, + HttpContext, +} from '@angular/common/http'; +import { CustomHttpParameterCodec } from '../encoder'; +import { Observable } from 'rxjs'; // @ts-ignore import { BasicError } from '../model/basicError'; @@ -24,129 +30,157 @@ import { BasicError } from '../model/basicError'; import { Dataversion } from '../model/dataversion'; // @ts-ignore -import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; -import { Configuration } from '../configuration'; - - +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class DataversionService { - - protected basePath = 'http://localhost/v1'; - public defaultHeaders = new HttpHeaders(); - public configuration = new Configuration(); - public encoder: HttpParameterCodec; - - constructor(protected httpClient: HttpClient, @Optional()@Inject(BASE_PATH) basePath: string|string[], @Optional() configuration: Configuration) { - if (configuration) { - this.configuration = configuration; - } - if (typeof this.configuration.basePath !== 'string') { - if (Array.isArray(basePath) && basePath.length > 0) { - basePath = basePath[0]; - } - - if (typeof basePath !== 'string') { - basePath = this.basePath; - } - this.configuration.basePath = basePath; - } - this.encoder = this.configuration.encoder || new CustomHttpParameterCodec(); + protected basePath = 'http://localhost/v1'; + public defaultHeaders = new HttpHeaders(); + public configuration = new Configuration(); + public encoder: HttpParameterCodec; + + constructor( + protected httpClient: HttpClient, + @Optional() @Inject(BASE_PATH) basePath: string | string[], + @Optional() configuration: Configuration, + ) { + if (configuration) { + this.configuration = configuration; } - - - // @ts-ignore - private addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams { - if (typeof value === "object" && value instanceof Date === false) { - httpParams = this.addToHttpParamsRecursive(httpParams, value); - } else { - httpParams = this.addToHttpParamsRecursive(httpParams, value, key); - } - return httpParams; + if (typeof this.configuration.basePath !== 'string') { + if (Array.isArray(basePath) && basePath.length > 0) { + basePath = basePath[0]; + } + + if (typeof basePath !== 'string') { + basePath = this.basePath; + } + this.configuration.basePath = basePath; } + this.encoder = this.configuration.encoder || new CustomHttpParameterCodec(); + } + + // @ts-ignore + private addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams { + if (typeof value === 'object' && value instanceof Date === false) { + httpParams = this.addToHttpParamsRecursive(httpParams, value); + } else { + httpParams = this.addToHttpParamsRecursive(httpParams, value, key); + } + return httpParams; + } - private addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams { - if (value == null) { - return httpParams; - } + private addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams { + if (value == null) { + return httpParams; + } - if (typeof value === "object") { - if (Array.isArray(value)) { - (value as any[]).forEach( elem => httpParams = this.addToHttpParamsRecursive(httpParams, elem, key)); - } else if (value instanceof Date) { - if (key != null) { - httpParams = httpParams.append(key, (value as Date).toISOString().substr(0, 10)); - } else { - throw Error("key may not be null if value is Date"); - } - } else { - Object.keys(value).forEach( k => httpParams = this.addToHttpParamsRecursive( - httpParams, value[k], key != null ? `${key}[${k}]` : k)); - } - } else if (key != null) { - httpParams = httpParams.append(key, value); + if (typeof value === 'object') { + if (Array.isArray(value)) { + (value as any[]).forEach( + (elem) => (httpParams = this.addToHttpParamsRecursive(httpParams, elem, key)), + ); + } else if (value instanceof Date) { + if (key != null) { + httpParams = httpParams.append(key, (value as Date).toISOString().substr(0, 10)); } else { - throw Error("key may not be null if value is not object or array"); + throw Error('key may not be null if value is Date'); } - return httpParams; + } else { + Object.keys(value).forEach( + (k) => + (httpParams = this.addToHttpParamsRecursive( + httpParams, + value[k], + key != null ? `${key}[${k}]` : k, + )), + ); + } + } else if (key != null) { + httpParams = httpParams.append(key, value); + } else { + throw Error('key may not be null if value is not object or array'); + } + return httpParams; + } + + /** + * Get dataversion + * Get dataversion + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public getDataversion( + observe?: 'body', + reportProgress?: boolean, + options?: { + httpHeaderAccept?: 'application/json' | 'application/problem+json'; + context?: HttpContext; + }, + ): Observable; + public getDataversion( + observe?: 'response', + reportProgress?: boolean, + options?: { + httpHeaderAccept?: 'application/json' | 'application/problem+json'; + context?: HttpContext; + }, + ): Observable>; + public getDataversion( + observe?: 'events', + reportProgress?: boolean, + options?: { + httpHeaderAccept?: 'application/json' | 'application/problem+json'; + context?: HttpContext; + }, + ): Observable>; + public getDataversion( + observe: any = 'body', + reportProgress: boolean = false, + options?: { + httpHeaderAccept?: 'application/json' | 'application/problem+json'; + context?: HttpContext; + }, + ): Observable { + let localVarHeaders = this.defaultHeaders; + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = ['application/json', 'application/problem+json']; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); } - /** - * Get dataversion - * Get dataversion - * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. - * @param reportProgress flag to report request and response progress. - */ - public getDataversion(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json' | 'application/problem+json', context?: HttpContext}): Observable; - public getDataversion(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json' | 'application/problem+json', context?: HttpContext}): Observable>; - public getDataversion(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json' | 'application/problem+json', context?: HttpContext}): Observable>; - public getDataversion(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json' | 'application/problem+json', context?: HttpContext}): Observable { - - let localVarHeaders = this.defaultHeaders; - - let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; - if (localVarHttpHeaderAcceptSelected === undefined) { - // to determine the Accept header - const httpHeaderAccepts: string[] = [ - 'application/json', - 'application/problem+json' - ]; - localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); - } - if (localVarHttpHeaderAcceptSelected !== undefined) { - localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); - } - - let localVarHttpContext: HttpContext | undefined = options && options.context; - if (localVarHttpContext === undefined) { - localVarHttpContext = new HttpContext(); - } - - - let responseType_: 'text' | 'json' | 'blob' = 'json'; - if (localVarHttpHeaderAcceptSelected) { - if (localVarHttpHeaderAcceptSelected.startsWith('text')) { - responseType_ = 'text'; - } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { - responseType_ = 'json'; - } else { - responseType_ = 'blob'; - } - } + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } - let localVarPath = `/dataversion`; - return this.httpClient.get(`${this.configuration.basePath}${localVarPath}`, - { - context: localVarHttpContext, - responseType: responseType_, - withCredentials: this.configuration.withCredentials, - headers: localVarHeaders, - observe: observe, - reportProgress: reportProgress - } - ); + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } } + let localVarPath = `/dataversion`; + return this.httpClient.get(`${this.configuration.basePath}${localVarPath}`, { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress, + }); + } } diff --git a/libs/agora/api-client-angular/src/lib/api/genes.service.ts b/libs/agora/api-client-angular/src/lib/api/genes.service.ts index cb722d9377..4eae42b9fe 100644 --- a/libs/agora/api-client-angular/src/lib/api/genes.service.ts +++ b/libs/agora/api-client-angular/src/lib/api/genes.service.ts @@ -31,6 +31,8 @@ import { GCTGenesList } from '../model/gCTGenesList'; // @ts-ignore import { Gene } from '../model/gene'; // @ts-ignore +import { GenesList } from '../model/genesList'; +// @ts-ignore import { NominatedGenesList } from '../model/nominatedGenesList'; // @ts-ignore @@ -494,7 +496,7 @@ export class GenesService { httpHeaderAccept?: 'application/json' | 'application/problem+json'; context?: HttpContext; }, - ): Observable>; + ): Observable; public searchGene( id: string, observe?: 'response', @@ -503,7 +505,7 @@ export class GenesService { httpHeaderAccept?: 'application/json' | 'application/problem+json'; context?: HttpContext; }, - ): Observable>>; + ): Observable>; public searchGene( id: string, observe?: 'events', @@ -512,7 +514,7 @@ export class GenesService { httpHeaderAccept?: 'application/json' | 'application/problem+json'; context?: HttpContext; }, - ): Observable>>; + ): Observable>; public searchGene( id: string, observe: any = 'body', @@ -560,7 +562,7 @@ export class GenesService { } let localVarPath = `/genes/search`; - return this.httpClient.get>(`${this.configuration.basePath}${localVarPath}`, { + return this.httpClient.get(`${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, params: localVarQueryParameters, responseType: responseType_, diff --git a/libs/agora/api-client-angular/src/lib/configuration.ts b/libs/agora/api-client-angular/src/lib/configuration.ts index d38a4c153f..3893e366a1 100644 --- a/libs/agora/api-client-angular/src/lib/configuration.ts +++ b/libs/agora/api-client-angular/src/lib/configuration.ts @@ -2,165 +2,165 @@ import { HttpParameterCodec } from '@angular/common/http'; import { Param } from './param'; export interface ConfigurationParameters { - /** - * @deprecated Since 5.0. Use credentials instead - */ - apiKeys?: {[ key: string ]: string}; - username?: string; - password?: string; - /** - * @deprecated Since 5.0. Use credentials instead - */ - accessToken?: string | (() => string); - basePath?: string; - withCredentials?: boolean; - /** - * Takes care of encoding query- and form-parameters. - */ - encoder?: HttpParameterCodec; - /** - * Override the default method for encoding path parameters in various - * styles. - *

- * See {@link README.md} for more details - *

- */ - encodeParam?: (param: Param) => string; - /** - * The keys are the names in the securitySchemes section of the OpenAPI - * document. They should map to the value used for authentication - * minus any standard prefixes such as 'Basic' or 'Bearer'. - */ - credentials?: {[ key: string ]: string | (() => string | undefined)}; + /** + * @deprecated Since 5.0. Use credentials instead + */ + apiKeys?: { [key: string]: string }; + username?: string; + password?: string; + /** + * @deprecated Since 5.0. Use credentials instead + */ + accessToken?: string | (() => string); + basePath?: string; + withCredentials?: boolean; + /** + * Takes care of encoding query- and form-parameters. + */ + encoder?: HttpParameterCodec; + /** + * Override the default method for encoding path parameters in various + * styles. + *

+ * See {@link README.md} for more details + *

+ */ + encodeParam?: (param: Param) => string; + /** + * The keys are the names in the securitySchemes section of the OpenAPI + * document. They should map to the value used for authentication + * minus any standard prefixes such as 'Basic' or 'Bearer'. + */ + credentials?: { [key: string]: string | (() => string | undefined) }; } export class Configuration { - /** - * @deprecated Since 5.0. Use credentials instead - */ - apiKeys?: {[ key: string ]: string}; - username?: string; - password?: string; - /** - * @deprecated Since 5.0. Use credentials instead - */ - accessToken?: string | (() => string); - basePath?: string; - withCredentials?: boolean; - /** - * Takes care of encoding query- and form-parameters. - */ - encoder?: HttpParameterCodec; - /** - * Encoding of various path parameter - * styles. - *

- * See {@link README.md} for more details - *

- */ - encodeParam: (param: Param) => string; - /** - * The keys are the names in the securitySchemes section of the OpenAPI - * document. They should map to the value used for authentication - * minus any standard prefixes such as 'Basic' or 'Bearer'. - */ - credentials: {[ key: string ]: string | (() => string | undefined)}; + /** + * @deprecated Since 5.0. Use credentials instead + */ + apiKeys?: { [key: string]: string }; + username?: string; + password?: string; + /** + * @deprecated Since 5.0. Use credentials instead + */ + accessToken?: string | (() => string); + basePath?: string; + withCredentials?: boolean; + /** + * Takes care of encoding query- and form-parameters. + */ + encoder?: HttpParameterCodec; + /** + * Encoding of various path parameter + * styles. + *

+ * See {@link README.md} for more details + *

+ */ + encodeParam: (param: Param) => string; + /** + * The keys are the names in the securitySchemes section of the OpenAPI + * document. They should map to the value used for authentication + * minus any standard prefixes such as 'Basic' or 'Bearer'. + */ + credentials: { [key: string]: string | (() => string | undefined) }; - constructor(configurationParameters: ConfigurationParameters = {}) { - this.apiKeys = configurationParameters.apiKeys; - this.username = configurationParameters.username; - this.password = configurationParameters.password; - this.accessToken = configurationParameters.accessToken; - this.basePath = configurationParameters.basePath; - this.withCredentials = configurationParameters.withCredentials; - this.encoder = configurationParameters.encoder; - if (configurationParameters.encodeParam) { - this.encodeParam = configurationParameters.encodeParam; - } - else { - this.encodeParam = param => this.defaultEncodeParam(param); - } - if (configurationParameters.credentials) { - this.credentials = configurationParameters.credentials; - } - else { - this.credentials = {}; - } + constructor(configurationParameters: ConfigurationParameters = {}) { + this.apiKeys = configurationParameters.apiKeys; + this.username = configurationParameters.username; + this.password = configurationParameters.password; + this.accessToken = configurationParameters.accessToken; + this.basePath = configurationParameters.basePath; + this.withCredentials = configurationParameters.withCredentials; + this.encoder = configurationParameters.encoder; + if (configurationParameters.encodeParam) { + this.encodeParam = configurationParameters.encodeParam; + } else { + this.encodeParam = (param) => this.defaultEncodeParam(param); } - - /** - * Select the correct content-type to use for a request. - * Uses {@link Configuration#isJsonMime} to determine the correct content-type. - * If no content type is found return the first found type if the contentTypes is not empty - * @param contentTypes - the array of content types that are available for selection - * @returns the selected content-type or undefined if no selection could be made. - */ - public selectHeaderContentType (contentTypes: string[]): string | undefined { - if (contentTypes.length === 0) { - return undefined; - } - - const type = contentTypes.find((x: string) => this.isJsonMime(x)); - if (type === undefined) { - return contentTypes[0]; - } - return type; + if (configurationParameters.credentials) { + this.credentials = configurationParameters.credentials; + } else { + this.credentials = {}; } + } - /** - * Select the correct accept content-type to use for a request. - * Uses {@link Configuration#isJsonMime} to determine the correct accept content-type. - * If no content type is found return the first found type if the contentTypes is not empty - * @param accepts - the array of content types that are available for selection. - * @returns the selected content-type or undefined if no selection could be made. - */ - public selectHeaderAccept(accepts: string[]): string | undefined { - if (accepts.length === 0) { - return undefined; - } + /** + * Select the correct content-type to use for a request. + * Uses {@link Configuration#isJsonMime} to determine the correct content-type. + * If no content type is found return the first found type if the contentTypes is not empty + * @param contentTypes - the array of content types that are available for selection + * @returns the selected content-type or undefined if no selection could be made. + */ + public selectHeaderContentType(contentTypes: string[]): string | undefined { + if (contentTypes.length === 0) { + return undefined; + } - const type = accepts.find((x: string) => this.isJsonMime(x)); - if (type === undefined) { - return accepts[0]; - } - return type; + const type = contentTypes.find((x: string) => this.isJsonMime(x)); + if (type === undefined) { + return contentTypes[0]; } + return type; + } - /** - * Check if the given MIME is a JSON MIME. - * JSON MIME examples: - * application/json - * application/json; charset=UTF8 - * APPLICATION/JSON - * application/vnd.company+json - * @param mime - MIME (Multipurpose Internet Mail Extensions) - * @return True if the given MIME is JSON, false otherwise. - */ - public isJsonMime(mime: string): boolean { - const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i'); - return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json'); + /** + * Select the correct accept content-type to use for a request. + * Uses {@link Configuration#isJsonMime} to determine the correct accept content-type. + * If no content type is found return the first found type if the contentTypes is not empty + * @param accepts - the array of content types that are available for selection. + * @returns the selected content-type or undefined if no selection could be made. + */ + public selectHeaderAccept(accepts: string[]): string | undefined { + if (accepts.length === 0) { + return undefined; } - public lookupCredential(key: string): string | undefined { - const value = this.credentials[key]; - return typeof value === 'function' - ? value() - : value; + const type = accepts.find((x: string) => this.isJsonMime(x)); + if (type === undefined) { + return accepts[0]; } + return type; + } - private defaultEncodeParam(param: Param): string { - // This implementation exists as fallback for missing configuration - // and for backwards compatibility to older typescript-angular generator versions. - // It only works for the 'simple' parameter style. - // Date-handling only works for the 'date-time' format. - // All other styles and Date-formats are probably handled incorrectly. - // - // But: if that's all you need (i.e.: the most common use-case): no need for customization! + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public isJsonMime(mime: string): boolean { + const jsonMime: RegExp = new RegExp( + '^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$', + 'i', + ); + return ( + mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json') + ); + } - const value = param.dataFormat === 'date-time' - ? (param.value as Date).toISOString() - : param.value; + public lookupCredential(key: string): string | undefined { + const value = this.credentials[key]; + return typeof value === 'function' ? value() : value; + } - return encodeURIComponent(String(value)); - } + private defaultEncodeParam(param: Param): string { + // This implementation exists as fallback for missing configuration + // and for backwards compatibility to older typescript-angular generator versions. + // It only works for the 'simple' parameter style. + // Date-handling only works for the 'date-time' format. + // All other styles and Date-formats are probably handled incorrectly. + // + // But: if that's all you need (i.e.: the most common use-case): no need for customization! + + const value = + param.dataFormat === 'date-time' ? (param.value as Date).toISOString() : param.value; + + return encodeURIComponent(String(value)); + } } diff --git a/libs/agora/api-client-angular/src/lib/encoder.ts b/libs/agora/api-client-angular/src/lib/encoder.ts index 138c4d5cf2..889ef00dd4 100644 --- a/libs/agora/api-client-angular/src/lib/encoder.ts +++ b/libs/agora/api-client-angular/src/lib/encoder.ts @@ -5,16 +5,16 @@ import { HttpParameterCodec } from '@angular/common/http'; * Workaround for https://github.com/angular/angular/issues/18261 */ export class CustomHttpParameterCodec implements HttpParameterCodec { - encodeKey(k: string): string { - return encodeURIComponent(k); - } - encodeValue(v: string): string { - return encodeURIComponent(v); - } - decodeKey(k: string): string { - return decodeURIComponent(k); - } - decodeValue(v: string): string { - return decodeURIComponent(v); - } + encodeKey(k: string): string { + return encodeURIComponent(k); + } + encodeValue(v: string): string { + return encodeURIComponent(v); + } + decodeKey(k: string): string { + return decodeURIComponent(k); + } + decodeValue(v: string): string { + return decodeURIComponent(v); + } } diff --git a/libs/agora/api-client-angular/src/lib/model/basicError.ts b/libs/agora/api-client-angular/src/lib/model/basicError.ts index 1ddf4cdf01..d3531ef2ee 100644 --- a/libs/agora/api-client-angular/src/lib/model/basicError.ts +++ b/libs/agora/api-client-angular/src/lib/model/basicError.ts @@ -3,33 +3,31 @@ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * * The version of the OpenAPI document: 1.0.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech * Do not edit the class manually. */ - /** * Problem details (tools.ietf.org/html/rfc7807) */ -export interface BasicError { - /** - * A human readable documentation for the problem type - */ - title: string; - /** - * The HTTP status code - */ - status: number; - /** - * A human readable explanation specific to this occurrence of the problem - */ - detail?: string; - /** - * An absolute URI that identifies the problem type - */ - type?: string; +export interface BasicError { + /** + * A human readable documentation for the problem type + */ + title: string; + /** + * The HTTP status code + */ + status: number; + /** + * A human readable explanation specific to this occurrence of the problem + */ + detail?: string; + /** + * An absolute URI that identifies the problem type + */ + type?: string; } - diff --git a/libs/agora/api-client-angular/src/lib/model/dataversion.ts b/libs/agora/api-client-angular/src/lib/model/dataversion.ts index 011d4c92e5..33b3d8b785 100644 --- a/libs/agora/api-client-angular/src/lib/model/dataversion.ts +++ b/libs/agora/api-client-angular/src/lib/model/dataversion.ts @@ -3,20 +3,18 @@ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * * The version of the OpenAPI document: 1.0.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech * Do not edit the class manually. */ - /** * Synapse data version */ -export interface Dataversion { - data_file: string; - data_version: string; - team_images_id: string; +export interface Dataversion { + data_file: string; + data_version: string; + team_images_id: string; } - diff --git a/libs/agora/api-client-angular/src/lib/model/genesList.ts b/libs/agora/api-client-angular/src/lib/model/genesList.ts new file mode 100644 index 0000000000..d3a419a4be --- /dev/null +++ b/libs/agora/api-client-angular/src/lib/model/genesList.ts @@ -0,0 +1,19 @@ +/** + * Agora REST API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { Gene } from './gene'; + +/** + * List of Genes + */ +export interface GenesList { + items?: Array; +} diff --git a/libs/agora/api-client-angular/src/lib/model/models.ts b/libs/agora/api-client-angular/src/lib/model/models.ts index d683eb3a3e..3491bc9305 100644 --- a/libs/agora/api-client-angular/src/lib/model/models.ts +++ b/libs/agora/api-client-angular/src/lib/model/models.ts @@ -12,6 +12,7 @@ export * from './gCTGeneNominations'; export * from './gCTGeneTissue'; export * from './gCTGenesList'; export * from './gene'; +export * from './genesList'; export * from './medianExpression'; export * from './neuropathologicCorrelation'; export * from './nominatedGenesList'; diff --git a/libs/agora/api-client-angular/src/lib/model/team.ts b/libs/agora/api-client-angular/src/lib/model/team.ts index 496882284a..00e5c8e91a 100644 --- a/libs/agora/api-client-angular/src/lib/model/team.ts +++ b/libs/agora/api-client-angular/src/lib/model/team.ts @@ -3,7 +3,7 @@ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * * The version of the OpenAPI document: 1.0.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -11,15 +11,13 @@ */ import { TeamMember } from './teamMember'; - /** * Team */ -export interface Team { - team: string; - team_full: string; - program: string; - description: string; - members: Array; +export interface Team { + team: string; + team_full: string; + program: string; + description: string; + members: Array; } - diff --git a/libs/agora/api-client-angular/src/lib/model/teamMember.ts b/libs/agora/api-client-angular/src/lib/model/teamMember.ts index e39072534f..ce712200e6 100644 --- a/libs/agora/api-client-angular/src/lib/model/teamMember.ts +++ b/libs/agora/api-client-angular/src/lib/model/teamMember.ts @@ -3,20 +3,18 @@ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * * The version of the OpenAPI document: 1.0.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech * Do not edit the class manually. */ - /** * Team Member */ -export interface TeamMember { - name: string; - isPrimaryInvestigator: boolean; - url?: string; +export interface TeamMember { + name: string; + isPrimaryInvestigator: boolean; + url?: string; } - diff --git a/libs/agora/api-client-angular/src/lib/param.ts b/libs/agora/api-client-angular/src/lib/param.ts index 78a2d20a64..0f75f4b359 100644 --- a/libs/agora/api-client-angular/src/lib/param.ts +++ b/libs/agora/api-client-angular/src/lib/param.ts @@ -8,8 +8,7 @@ export type StandardParamStyle = | 'simple' | 'spaceDelimited' | 'pipeDelimited' - | 'deepObject' - ; + | 'deepObject'; /** * The OpenAPI standard {@link StandardParamStyle}s may be extended by custom styles by the user. @@ -24,14 +23,7 @@ export type ParamLocation = 'query' | 'header' | 'path' | 'cookie'; /** * Standard types as defined in OpenAPI Specification: Data Types */ -export type StandardDataType = - | "integer" - | "number" - | "boolean" - | "string" - | "object" - | "array" - ; +export type StandardDataType = 'integer' | 'number' | 'boolean' | 'string' | 'object' | 'array'; /** * Standard {@link DataType}s plus your own types/classes. @@ -42,16 +34,15 @@ export type DataType = StandardDataType | string; * Standard formats as defined in OpenAPI Specification: Data Types */ export type StandardDataFormat = - | "int32" - | "int64" - | "float" - | "double" - | "byte" - | "binary" - | "date" - | "date-time" - | "password" - ; + | 'int32' + | 'int64' + | 'float' + | 'double' + | 'byte' + | 'binary' + | 'date' + | 'date-time' + | 'password'; export type DataFormat = StandardDataFormat | string; @@ -62,7 +53,7 @@ export interface Param { name: string; value: unknown; in: ParamLocation; - style: ParamStyle, + style: ParamStyle; explode: boolean; dataType: DataType; dataFormat: DataFormat | undefined; diff --git a/libs/agora/api-client-angular/src/lib/variables.ts b/libs/agora/api-client-angular/src/lib/variables.ts index 6fe58549f3..2277a9cc3c 100644 --- a/libs/agora/api-client-angular/src/lib/variables.ts +++ b/libs/agora/api-client-angular/src/lib/variables.ts @@ -2,8 +2,8 @@ import { InjectionToken } from '@angular/core'; export const BASE_PATH = new InjectionToken('basePath'); export const COLLECTION_FORMATS = { - 'csv': ',', - 'tsv': ' ', - 'ssv': ' ', - 'pipes': '|' -} + csv: ',', + tsv: ' ', + ssv: ' ', + pipes: '|', +}; diff --git a/libs/agora/api-description/build/openapi.yaml b/libs/agora/api-description/build/openapi.yaml index 0bb12af76b..46fb8bac51 100644 --- a/libs/agora/api-description/build/openapi.yaml +++ b/libs/agora/api-description/build/openapi.yaml @@ -139,9 +139,7 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/Gene' + $ref: '#/components/schemas/GenesList' '400': $ref: '#/components/responses/BadRequest' '500': @@ -856,6 +854,14 @@ components: - druggability - total_nominations - ensembl_info + GenesList: + type: object + description: List of Genes + properties: + items: + type: array + items: + $ref: '#/components/schemas/Gene' GCTGeneTissue: type: object description: GCTGeneTissue diff --git a/libs/agora/api-description/src/components/schemas/GenesList.yaml b/libs/agora/api-description/src/components/schemas/GenesList.yaml new file mode 100644 index 0000000000..5f0802c4cf --- /dev/null +++ b/libs/agora/api-description/src/components/schemas/GenesList.yaml @@ -0,0 +1,7 @@ +type: object +description: List of Genes +properties: + items: + type: array + items: + $ref: Gene.yaml diff --git a/libs/agora/api-description/src/paths/genes/search.yaml b/libs/agora/api-description/src/paths/genes/search.yaml index a25a8f9909..670c5bccd1 100644 --- a/libs/agora/api-description/src/paths/genes/search.yaml +++ b/libs/agora/api-description/src/paths/genes/search.yaml @@ -16,9 +16,7 @@ get: content: application/json: schema: - type: array - items: - $ref: ../../components/schemas/Gene.yaml + $ref: ../../components/schemas/GenesList.yaml '400': $ref: ../../components/responses/BadRequest.yaml '500': diff --git a/libs/agora/genes/src/lib/components/gene-search/gene-search.component.html b/libs/agora/genes/src/lib/components/gene-search/gene-search.component.html index 83e48ee687..98670e4a24 100644 --- a/libs/agora/genes/src/lib/components/gene-search/gene-search.component.html +++ b/libs/agora/genes/src/lib/components/gene-search/gene-search.component.html @@ -1 +1,134 @@ -

gene-search works!

+
+ +
+
+
    +
  • + + + + + {{ error }} +
  • +
+ +
    +
  • + + {{ result.hgnc_symbol || result.ensembl_gene_id }} + + +  {{ result.ensembl_gene_id }} + + +  (Also known as {{ query.toUpperCase() }}) + +
  • +
+
+
+
diff --git a/libs/agora/genes/src/lib/components/gene-search/gene-search.component.spec.ts b/libs/agora/genes/src/lib/components/gene-search/gene-search.component.spec.ts index 10795a9d32..ac58dabf7c 100644 --- a/libs/agora/genes/src/lib/components/gene-search/gene-search.component.spec.ts +++ b/libs/agora/genes/src/lib/components/gene-search/gene-search.component.spec.ts @@ -1,5 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { GeneSearchComponent } from './gene-search.component'; +import { provideHttpClient } from '@angular/common/http'; describe('GeneSearchComponent', () => { let component: GeneSearchComponent; @@ -8,6 +9,7 @@ describe('GeneSearchComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [GeneSearchComponent], + providers: [provideHttpClient()], }).compileComponents(); fixture = TestBed.createComponent(GeneSearchComponent); diff --git a/libs/agora/genes/src/lib/components/gene-search/gene-search.component.ts b/libs/agora/genes/src/lib/components/gene-search/gene-search.component.ts index 80f28137aa..bbc33a67dc 100644 --- a/libs/agora/genes/src/lib/components/gene-search/gene-search.component.ts +++ b/libs/agora/genes/src/lib/components/gene-search/gene-search.component.ts @@ -1,11 +1,193 @@ -import { Component } from '@angular/core'; +import { + AfterViewInit, + Component, + ElementRef, + EventEmitter, + HostListener, + inject, + Input, + OnDestroy, + Output, + ViewChild, +} from '@angular/core'; import { CommonModule } from '@angular/common'; +import { Gene, GenesList, GenesService } from '@sagebionetworks/agora/api-client-angular'; +import { + catchError, + debounceTime, + distinctUntilChanged, + EMPTY, + fromEvent, + Observable, + Subject, + switchMap, + takeUntil, + throwError, +} from 'rxjs'; +import { Router } from '@angular/router'; +import { FormsModule } from '@angular/forms'; @Component({ selector: 'agora-gene-search', standalone: true, - imports: [CommonModule], + imports: [CommonModule, FormsModule], templateUrl: './gene-search.component.html', styleUrl: './gene-search.component.scss', }) -export class GeneSearchComponent {} +export class GeneSearchComponent implements AfterViewInit, OnDestroy { + @Input() location: 'header' | 'home' = 'header'; + @Output() searchNavigated = new EventEmitter(); + + router = inject(Router); + apiService = inject(GenesService); + + protected unsubscribe$ = new Subject(); + + results: Gene[] = []; + isLoading = false; + isEnsemblId = false; + query = ''; + error = ''; + hgncSymbolCounts: { [key: string]: number } = {}; + + showGeneResults = false; // controls display of gene results list items + + errorMessages: { [key: string]: string } = { + hgncSymbolNotFound: 'No results found. Try searching by the Ensembl Gene ID.', + ensemblIdNotFound: 'Unable to find a matching gene. Try searching by gene symbol.', + notValidSearch: 'Please enter at least two characters.', + notValidEnsemblId: + 'You must enter a full 15-character value to search for a gene by Ensembl identifier.', + unknown: 'An unknown error occurred, please try again.', + }; + + @ViewChild('root') root: ElementRef = {} as ElementRef; + @ViewChild('input') input: ElementRef = {} as ElementRef; + + @HostListener('document:click', ['$event']) + onClick(event: Event) { + this.checkClickIsInsideComponent(event); + } + + ngAfterViewInit() { + fromEvent(this.input.nativeElement, 'keyup') + .pipe( + takeUntil(this.unsubscribe$), + debounceTime(500), + distinctUntilChanged(), + switchMap((event: any) => { + return this.search(event.target.value); + }), + catchError((err) => { + this.error = this.errorMessages['unknown']; + this.isLoading = false; + return throwError(err); + }), + ) + .subscribe((response: any) => { + this.showGeneResults = true; + this.setResults(response.items); + }); + } + + search(query: string): Observable { + this.results = []; + this.error = ''; + this.query = query = query.trim().replace(/[^a-z0-9-_]/gi, ''); + this.isEnsemblId = 'ensg' === query.toLowerCase().substring(0, 4); + + if (query.length > 0 && query.length < 2) { + this.showGeneResults = true; + this.error = this.errorMessages['notValidSearch']; + } else if (this.isEnsemblId) { + const digits = query.toLowerCase().substring(4, query.length); + if (digits.length !== 11 || !/^\d+$/.test(digits)) { + this.showGeneResults = true; + this.error = this.errorMessages['notValidEnsemblId']; + } + } + + this.isLoading = query && !this.error ? true : false; + return this.isLoading ? this.apiService.searchGene(query) : EMPTY; + } + + setResults(results: Gene[]) { + // If we got an empty array as response, or no genes found + if (results.length < 1) { + this.error = this.isEnsemblId + ? this.errorMessages['ensemblIdNotFound'] + : this.errorMessages['hgncSymbolNotFound']; + } else { + if (this.isEnsemblId) { + // Multiple matching genes: This should never happen…but if it does, log an error + if (results.length > 1) { + console.log( + 'Unexpected duplicate gene_info objects for ensembl ID "' + this.query + '" found.', + ); + this.error = this.errorMessages['ensemblIdNotFound']; + } else { + this.goToGene(results[0].ensembl_gene_id); + this.isLoading = false; + return; + } + } else { + this.hgncSymbolCounts = {}; + for (const item of results) { + if (item.hgnc_symbol) { + if (!this.hgncSymbolCounts[item.hgnc_symbol]) { + this.hgncSymbolCounts[item.hgnc_symbol] = 1; + } else { + this.hgncSymbolCounts[item.hgnc_symbol]++; + } + } + } + } + } + + this.results = results; + this.isLoading = false; + } + + goToGene(id: string) { + this.input.nativeElement.blur(); + this.query = ''; + this.results = []; + this.showGeneResults = false; + this.searchNavigated.emit(); + // https://github.com/angular/angular/issues/45202 + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.router.navigate(['/genes/' + id]); + } + + hasAlias(gene: Gene): boolean { + return ( + !gene.hgnc_symbol.toLowerCase().includes(this.query.toLowerCase()) && + gene.alias?.map((s: string) => s.toLowerCase()).includes(this.query.toLowerCase()) + ); + } + + onFocus() { + if (this.results.length > 0 || this.error) { + this.showGeneResults = true; + } + } + + clearInput() { + this.input.nativeElement.focus(); + this.query = ''; + this.error = ''; + this.results = []; + } + + checkClickIsInsideComponent(event: Event) { + // if clicked element is not part of this component, hide gene results + if (!this.root.nativeElement.contains(event.target)) { + this.showGeneResults = false; + } + } + + ngOnDestroy(): void { + this.unsubscribe$.next(); + this.unsubscribe$.complete(); + } +} diff --git a/libs/agora/home/src/lib/home.component.html b/libs/agora/home/src/lib/home.component.html index e0a2c04927..92cad025ca 100644 --- a/libs/agora/home/src/lib/home.component.html +++ b/libs/agora/home/src/lib/home.component.html @@ -52,6 +52,7 @@

Gene Search

+