From 9b07582724563633a213a6f262eead97d656c2b7 Mon Sep 17 00:00:00 2001 From: Adam Coffman Date: Fri, 7 Jun 2024 16:18:17 -0500 Subject: [PATCH] add support for filtering search term types --- .../quicksearch/quicksearch-component.html | 26 +++++++++- .../quicksearch/quicksearch-component.less | 10 ++++ .../quicksearch/quicksearch-component.ts | 47 +++++++++++++++---- .../layout/quicksearch/quicksearch.module.ts | 4 ++ .../icon-name-for-subscribable-entity.ts | 8 +++- client/src/app/generated/civic.apollo.ts | 2 + client/src/app/generated/server.model.graphql | 2 + client/src/app/generated/server.schema.json | 12 +++++ server/app/graphql/resolvers/quicksearch.rb | 17 +++++-- .../graphql/resolvers/top_level_therapies.rb | 4 +- .../types/quicksearch/searchable_entities.rb | 2 + server/app/models/disease.rb | 16 +++++++ server/app/models/therapy.rb | 15 ++++++ 13 files changed, 149 insertions(+), 16 deletions(-) diff --git a/client/src/app/components/layout/quicksearch/quicksearch-component.html b/client/src/app/components/layout/quicksearch/quicksearch-component.html index 8a26308a9..668401f1c 100644 --- a/client/src/app/components/layout/quicksearch/quicksearch-component.html +++ b/client/src/app/components/layout/quicksearch/quicksearch-component.html @@ -8,6 +8,7 @@ nzPlaceHolder="Search CIViC" [nzOptionHeightPx]="28" [nzShowArrow]="false" + [nzDropdownRender]="searchEntities" [nzDropdownMatchSelectWidth]="false"> @@ -39,3 +40,26 @@ Loading Data... + + +
+ +
+ @for(entity of searchableEntities; track entity) { + + } +
+
+
diff --git a/client/src/app/components/layout/quicksearch/quicksearch-component.less b/client/src/app/components/layout/quicksearch/quicksearch-component.less index 0fe6e7508..ed25871fa 100644 --- a/client/src/app/components/layout/quicksearch/quicksearch-component.less +++ b/client/src/app/components/layout/quicksearch/quicksearch-component.less @@ -5,3 +5,13 @@ nz-select { width: 100%; } + +hr { + border-color: #6666 +} + +.entity-select { + margin-right: 10px; + margin-left: 10px; + +} diff --git a/client/src/app/components/layout/quicksearch/quicksearch-component.ts b/client/src/app/components/layout/quicksearch/quicksearch-component.ts index dbf2aaaa1..0634f85ac 100644 --- a/client/src/app/components/layout/quicksearch/quicksearch-component.ts +++ b/client/src/app/components/layout/quicksearch/quicksearch-component.ts @@ -49,6 +49,10 @@ export class CvcQuicksearchComponent { @ViewChild(NzSelectComponent, { static: true }) selectNode!: NzSelectComponent + selectedEntities: SearchableEntities[] = Object.values(SearchableEntities) + searchableEntities = Object.keys(SearchableEntities) + currentSearchTerm?: string + // SOURCE STREAMS onSearch$: Subject onSelect$: Subject @@ -62,10 +66,12 @@ export class CvcQuicksearchComponent { option$: Observable isLoading$: Observable - converter = entityTypeToTypename - constructor(private gql: QuicksearchGQL, private router: Router) { + constructor( + private gql: QuicksearchGQL, + private router: Router + ) { this.onSearch$ = new Subject() this.onSelect$ = new Subject() @@ -73,14 +79,16 @@ export class CvcQuicksearchComponent { skip(1), // drop initial empty string from input init throttleTime(300, asyncScheduler, { leading: false, trailing: true }), switchMap((str: string) => { + this.currentSearchTerm = str + let selectedEntities = this.selectedEntities // if queryRef doesn't exist, create it with watchQuery observable // if it does, refetch with fetchQuery observable. // using defer() ensures functions are not called until // values are emitted. otherwise they'll be called on subscribe. return iif( () => this.queryRef === undefined, // predicate - defer(() => watchQuery(str)), // true - defer(() => fetchQuery(str)) + defer(() => watchQuery(str, selectedEntities)), // true + defer(() => fetchQuery(str, selectedEntities)) ) // false }) ) @@ -114,14 +122,19 @@ export class CvcQuicksearchComponent { }) // set queryRef with watch(), return its valueChanges observable - const watchQuery = (str: string) => { - this.queryRef = this.gql.watch({ query: str, highlightMatches: true }) + const watchQuery = (str: string, entities: Maybe) => { + this.queryRef = this.gql.watch({ + query: str, + highlightMatches: true, + types: entities, + }) return this.queryRef.valueChanges } // return observable from refetch() promise - const fetchQuery = (str: string) => - from(this.queryRef.refetch({ query: str })) + const fetchQuery = (str: string, entities: Maybe) => { + return from(this.queryRef.refetch({ query: str, types: entities })) + } } urlForResult(res: SearchResult): string { @@ -136,10 +149,28 @@ export class CvcQuicksearchComponent { case SearchableEntities.MolecularProfile: name = 'molecular-profiles' break + case SearchableEntities.Therapy: + name = 'therapies' + break default: name = `${res.resultType.toLowerCase()}s` } return `/${name}/${res.id}/summary` } + + selectedEntitiesChanged(selectedEntities: string[]) { + this.selectedEntities = selectedEntities.map( + (x) => SearchableEntities[x as keyof typeof SearchableEntities] + ) + + if (this.currentSearchTerm) { + this.onSearch$.next(this.currentSearchTerm) + } + } + + isSelected(entity: string): boolean { + const x = SearchableEntities[entity as keyof typeof SearchableEntities] + return this.selectedEntities.includes(x) + } } diff --git a/client/src/app/components/layout/quicksearch/quicksearch.module.ts b/client/src/app/components/layout/quicksearch/quicksearch.module.ts index 2757f6db5..4792096bd 100644 --- a/client/src/app/components/layout/quicksearch/quicksearch.module.ts +++ b/client/src/app/components/layout/quicksearch/quicksearch.module.ts @@ -11,6 +11,8 @@ import { FormsModule } from '@angular/forms' import { NzSelectModule } from 'ng-zorro-antd/select' import { NzTypographyModule } from 'ng-zorro-antd/typography' import { CvcPipesModule } from '@app/core/pipes/pipes.module' +import { NzCheckboxModule } from 'ng-zorro-antd/checkbox' +import { NzToolTipModule } from 'ng-zorro-antd/tooltip' @NgModule({ declarations: [CvcQuicksearchComponent], @@ -27,6 +29,8 @@ import { CvcPipesModule } from '@app/core/pipes/pipes.module' NzFormModule, NzIconModule, NzAutocompleteModule, + NzCheckboxModule, + NzToolTipModule, ], exports: [CvcQuicksearchComponent], }) diff --git a/client/src/app/core/pipes/icon-name-for-subscribable-entity.ts b/client/src/app/core/pipes/icon-name-for-subscribable-entity.ts index 5fcd3d6d2..82b7823b3 100644 --- a/client/src/app/core/pipes/icon-name-for-subscribable-entity.ts +++ b/client/src/app/core/pipes/icon-name-for-subscribable-entity.ts @@ -31,11 +31,17 @@ export class IconNameForSubscribableEntity implements PipeTransform { case SubscribableEntities.VariantGroup: case 'VARIANT_GROUP': case 'VariantGroup': - return 'civic-variant-group' + return 'civic-variantgroup' case SubscribableEntities.MolecularProfile: case 'MOLECULAR_PROFILE': case 'MolecularProfile': return 'civic-molecularprofile' + case 'THERAPY': + case 'Therapy': + return 'civic-therapy' + case 'DISEASE': + case 'Disease': + return 'civic-disease' default: console.log('String No icon name found for ' + e) return 'border-outer' diff --git a/client/src/app/generated/civic.apollo.ts b/client/src/app/generated/civic.apollo.ts index 296b30c35..3b76bddcd 100644 --- a/client/src/app/generated/civic.apollo.ts +++ b/client/src/app/generated/civic.apollo.ts @@ -5195,10 +5195,12 @@ export type SearchResult = { export enum SearchableEntities { Assertion = 'ASSERTION', + Disease = 'DISEASE', EvidenceItem = 'EVIDENCE_ITEM', Feature = 'FEATURE', MolecularProfile = 'MOLECULAR_PROFILE', Revision = 'REVISION', + Therapy = 'THERAPY', Variant = 'VARIANT', VariantGroup = 'VARIANT_GROUP' } diff --git a/client/src/app/generated/server.model.graphql b/client/src/app/generated/server.model.graphql index 3d10fc435..ee387d675 100644 --- a/client/src/app/generated/server.model.graphql +++ b/client/src/app/generated/server.model.graphql @@ -8763,10 +8763,12 @@ type SearchResult { enum SearchableEntities { ASSERTION + DISEASE EVIDENCE_ITEM FEATURE MOLECULAR_PROFILE REVISION + THERAPY VARIANT VARIANT_GROUP } diff --git a/client/src/app/generated/server.schema.json b/client/src/app/generated/server.schema.json index 67fa31f12..205d7d4f8 100644 --- a/client/src/app/generated/server.schema.json +++ b/client/src/app/generated/server.schema.json @@ -40478,6 +40478,18 @@ "description": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "THERAPY", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "DISEASE", + "description": null, + "isDeprecated": false, + "deprecationReason": null } ], "possibleTypes": null diff --git a/server/app/graphql/resolvers/quicksearch.rb b/server/app/graphql/resolvers/quicksearch.rb index 63f5d5f3d..d7ee16832 100644 --- a/server/app/graphql/resolvers/quicksearch.rb +++ b/server/app/graphql/resolvers/quicksearch.rb @@ -16,7 +16,9 @@ def resolve(query:, types: nil, highlight_matches: false) Assertion, VariantGroup, Revision, - MolecularProfile + MolecularProfile, + Therapy, + Disease ] else types @@ -33,8 +35,15 @@ def resolve(query:, types: nil, highlight_matches: false) models: query_targets, highlight: tag, limit: 10, - fields: ['id^10', 'name', 'aliases', 'feature', 'feature_type'] - ).with_highlights(multiple: true) + fields: [ + {'id^10' => :word }, + {'ncit_id' => :word }, + {'doid' => :word }, + {'name^10' => :word }, + {'aliases' => :word_start }, + {'feature' => :word }, + {'feature_type' => :word } + ]).with_highlights(multiple: true) results.map do |res, highlights| { @@ -59,7 +68,7 @@ def format_name(name, highlights) def format_highlights(highlights) rows = highlights.map do |field_name, matches| next if field_name == :name - name = field_name.to_s.titleize + name = field_name.to_s.split(".").first&.titleize match_string = matches.join(', ') "#{name}: #{match_string}" end diff --git a/server/app/graphql/resolvers/top_level_therapies.rb b/server/app/graphql/resolvers/top_level_therapies.rb index 79212a228..adc4324a5 100644 --- a/server/app/graphql/resolvers/top_level_therapies.rb +++ b/server/app/graphql/resolvers/top_level_therapies.rb @@ -9,11 +9,11 @@ class Resolvers::TopLevelTherapies < GraphQL::Schema::Resolver description 'List and filter Therapies from the NCI Thesaurus.' scope do - Therapy.select('therapies.id, therapies.name, max(therapies.ncit_id) as ncit_id, count(distinct(assertions.id)) as assertion_count, count(distinct(evidence_items.id)) as evidence_count') + Therapy.select('therapies.id, therapies.name, therapies.deprecated, max(therapies.ncit_id) as ncit_id, count(distinct(assertions.id)) as assertion_count, count(distinct(evidence_items.id)) as evidence_count') .left_outer_joins(:assertions, :evidence_items) .where("evidence_items.status != 'rejected' OR assertions.status != 'rejected'") .where(deprecated: false) - .group('therapies.id, therapies.name') + .group('therapies.id, therapies.name, therapies.deprecated') .having('COUNT(evidence_items.id) > 0 OR COUNT(assertions.id) > 0') .order('evidence_count DESC', :id) end diff --git a/server/app/graphql/types/quicksearch/searchable_entities.rb b/server/app/graphql/types/quicksearch/searchable_entities.rb index 1d3c6b7a4..4233ccb84 100644 --- a/server/app/graphql/types/quicksearch/searchable_entities.rb +++ b/server/app/graphql/types/quicksearch/searchable_entities.rb @@ -7,5 +7,7 @@ class SearchableEntities < Types::BaseEnum value 'VARIANT_GROUP', value: VariantGroup value 'REVISION', value: Revision value 'MOLECULAR_PROFILE', value: MolecularProfile + value 'THERAPY', value: Therapy + value 'DISEASE', value: Disease end end diff --git a/server/app/models/disease.rb b/server/app/models/disease.rb index b8fa1a663..b358fddfd 100644 --- a/server/app/models/disease.rb +++ b/server/app/models/disease.rb @@ -9,6 +9,22 @@ class Disease < ApplicationRecord has_many :source_suggestions has_and_belongs_to_many :disease_aliases + searchkick highlight: [:name, :aliases], callbacks: :async, word_start: [:name, :aliases] + scope :search_import, -> { includes(:disease_aliases) } + + def search_data + { + name: name, + doid: "DOID:#{doid}", + aliases: disease_aliases.map(&:name) + } + end + + def should_index? + evidence_items.any? + end + + def disease_url Disease.url_for_doid(doid) end diff --git a/server/app/models/therapy.rb b/server/app/models/therapy.rb index f9d19a4cb..8a56b98f9 100644 --- a/server/app/models/therapy.rb +++ b/server/app/models/therapy.rb @@ -10,6 +10,21 @@ class Therapy < ApplicationRecord validates :ncit_id, uniqueness: true, allow_nil: true + searchkick highlight: [:name, :aliases], callbacks: :async, word_start: [:name, :aliases] + scope :search_import, -> { includes(:therapy_aliases) } + + def search_data + { + name: name, + ncit_id: ncit_id, + aliases: therapy_aliases.map(&:name) + } + end + + def should_index? + evidence_items.any? + end + def self.url_for(ncit_id:) if ncit_id.nil? nil