From d033a54f637cf9d87939b4778dd13f890a0d7127 Mon Sep 17 00:00:00 2001 From: Kathy Tran Date: Wed, 20 Nov 2024 16:03:46 -0500 Subject: [PATCH 01/10] Try different browser --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c0b0cf9a3..ecc971344 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -197,7 +197,7 @@ jobs: - setup_integration_test - run: name: Test - command: bash -i -c 'npx cypress run --browser chrome --record --config numTestsKeptInMemory=1 --reporter junit --spec cypress/e2e/<< parameters.integration_test_name >>/**/*' + command: bash -i -c 'npx cypress run --record --config numTestsKeptInMemory=1 --reporter junit --spec cypress/e2e/<< parameters.integration_test_name >>/**/*' no_output_timeout: 30m environment: MOCHA_FILE: integration-tests/test-results/junit/test-results-[hash].xml From ad19a3abe126d016ee760500da315ca9aca1c82d Mon Sep 17 00:00:00 2001 From: Kathy Tran Date: Mon, 13 Jan 2025 13:14:02 -0500 Subject: [PATCH 02/10] Search cards from one component --- src/app/search/query-builder.service.ts | 3 + src/app/search/search-entry-table.ts | 67 +++++++++- .../search-notebook-table.component.html | 81 ++++++++++-- .../search-notebook-table.component.ts | 19 ++- .../search-results.component.html | 17 +-- .../search-results.component.ts | 3 +- .../search-tool-table.component.ts | 2 +- .../search-workflow-table.component.html | 121 ++++++++++-------- .../search-workflow-table.component.ts | 26 +++- src/app/search/state/search.query.ts | 2 +- src/app/search/state/search.service.ts | 8 +- src/app/shared/entry-to-display-name.pipe.ts | 8 +- src/app/shared/styles/entry-table.scss | 4 + src/styles.scss | 5 + 14 files changed, 274 insertions(+), 92 deletions(-) diff --git a/src/app/search/query-builder.service.ts b/src/app/search/query-builder.service.ts index 4bb323a7a..e568bebd2 100644 --- a/src/app/search/query-builder.service.ts +++ b/src/app/search/query-builder.service.ts @@ -82,14 +82,17 @@ export class QueryBuilderService { 'approvedAITopic', 'descriptorType', 'descriptorTypeSubclass', + 'entryTypeMetadata', 'full_workflow_path', 'gitUrl', + 'last_modified_date', 'name', 'namespace', 'organization', 'private_access', 'providerUrl', 'repository', + 'selected_concept_doi', 'starredUsers', 'toolname', 'tool_path', diff --git a/src/app/search/search-entry-table.ts b/src/app/search/search-entry-table.ts index 2749e0e76..8ed563dd5 100644 --- a/src/app/search/search-entry-table.ts +++ b/src/app/search/search-entry-table.ts @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Directive, OnInit, ViewChild } from '@angular/core'; +import { Directive, Input, OnInit, ViewChild } from '@angular/core'; import { MatLegacyPaginator as MatPaginator, LegacyPageEvent as PageEvent } from '@angular/material/legacy-paginator'; -import { MatSort } from '@angular/material/sort'; +import { MatSort, Sort } from '@angular/material/sort'; import { MatLegacyTableDataSource as MatTableDataSource } from '@angular/material/legacy-table'; import { combineLatest, Observable, Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -23,16 +23,25 @@ import { Base } from '../shared/base'; import { DateService } from '../shared/date.service'; import { SearchQuery, SearchResult } from './state/search.query'; import { SearchService } from './state/search.service'; +import { EntryType, EntryTypeMetadata } from 'app/shared/openapi'; + +export interface SortOption { + label: string; + sort: Sort; +} @Directive() // eslint-disable-next-line @angular-eslint/directive-class-suffix export abstract class SearchEntryTable extends Base implements OnInit { + public EntryType = EntryType; + @Input() entryType: EntryType; @ViewChild(MatPaginator, { static: true }) protected paginator: MatPaginator; @ViewChild(MatSort, { static: true }) protected sort: MatSort; protected verifiedLink: string; + protected entryTypeMetadata: EntryTypeMetadata; protected ngUnsubscribe: Subject<{}> = new Subject(); - public readonly displayedColumns = ['name', 'all_authors', 'descriptorType', 'starredUsers']; + public readonly displayedColumns = ['name']; public readonly columnsToDisplayWithExpand = [...this.displayedColumns, 'expand']; public readonly searchEverythingFriendlyNames = new Map([ ['full_workflow_path', 'Path'], @@ -46,9 +55,50 @@ export abstract class SearchEntryTable extends Base implements OnInit { ['categories.topic', 'Category Topic'], ['categories.displayName', 'Category'], ]); - abstract readonly entryType: 'tool' | 'workflow' | 'notebook'; + public defaultSortOption: SortOption = { + label: 'Most Stars', + sort: { active: 'starredUsers', direction: 'desc' }, + }; + public sortOptions: SortOption[] = [ + this.defaultSortOption, + { + label: 'Least Stars', + sort: { active: 'starredUsers', direction: 'asc' }, + }, + { + label: 'Name, A-Z', + sort: { active: 'name', direction: 'asc' }, + }, + { + label: 'Name, Z-A', + sort: { active: 'name', direction: 'desc' }, + }, + { + label: 'Authors, A-Z', + sort: { active: 'all_authors', direction: 'asc' }, + }, + { + label: 'Authors, Z-A', + sort: { active: 'all_authors', direction: 'desc' }, + }, + ]; + + //abstract readonly entryType: EntryType; abstract dataSource: MatTableDataSource; - abstract privateNgOnInit(): Observable; + //abstract privateNgOnInit(): Observable; + + privateNgOnInit(): Observable> { + switch (this.entryType) { + case EntryType.WORKFLOW: + return this.searchQuery.workflows$; + case EntryType.TOOL: + return this.searchQuery.tools$; + case EntryType.NOTEBOOK: + return this.searchQuery.notebooks$; + default: + return null; + } + } constructor(protected dateService: DateService, protected searchQuery: SearchQuery, protected searchService: SearchService) { super(); @@ -58,6 +108,7 @@ export abstract class SearchEntryTable extends Base implements OnInit { ngOnInit(): void { this.dataSource = new MatTableDataSource(); this.dataSource.sort = this.sort; + this.setSort(this.defaultSortOption.sort); this.dataSource.paginator = this.paginator; combineLatest([this.searchQuery.pageSize$, this.searchQuery.pageIndex$, this.privateNgOnInit()]) .pipe(takeUntil(this.ngUnsubscribe)) @@ -82,4 +133,10 @@ export abstract class SearchEntryTable extends Base implements OnInit { updatePageSizeAndIndex($event: PageEvent) { this.searchService.setPageSizeAndIndex($event.pageSize, $event.pageIndex); } + + setSort(sortValue: Sort) { + this.sort.active = sortValue.active; + this.sort.direction = sortValue.direction; + this.sort.sortChange.emit(sortValue); + } } diff --git a/src/app/search/search-notebook-table/search-notebook-table.component.html b/src/app/search/search-notebook-table/search-notebook-table.component.html index c9bf63827..35cbdd1aa 100644 --- a/src/app/search/search-notebook-table/search-notebook-table.component.html +++ b/src/app/search/search-notebook-table/search-notebook-table.component.html @@ -2,11 +2,76 @@
+ + Sort by + + {{ sortOption.label }} + + - Name and Description + -
+ +
+ +
+ star_rate + {{ + !notebook?.source.starredUsers || notebook?.source.starredUsers.length === 0 ? '' : notebook?.source.starredUsers.length + }} +
+
+
+ {{ notebook?.source.topicAutomatic }} + +
+ +
+ account_circle +
+ + +
+ + + + + +
{{ this.searchEverythingFriendlyNames.get(highlight.key.toString()) }}:
+
+ +
+
+ Last updated {{ notebook?.source.last_modified_date | date }} + {{ notebook?.source.descriptorType | descriptorLanguage }} +
+ +
+
+ + @@ -58,7 +123,7 @@ - + - - - + + + >; constructor(dateService: DateService, searchQuery: SearchQuery, searchService: SearchService) { super(dateService, searchQuery, searchService); diff --git a/src/app/search/search-results/search-results.component.html b/src/app/search/search-results/search-results.component.html index 85a327394..5fe15b27f 100644 --- a/src/app/search/search-results/search-results.component.html +++ b/src/app/search/search-results/search-results.component.html @@ -14,7 +14,7 @@ ~ limitations under the License. -->
-
+
@@ -43,13 +43,13 @@
- - + +
-
+
@@ -78,13 +78,13 @@
- - + +
-
+
@@ -113,6 +113,7 @@
- + +
diff --git a/src/app/search/search-results/search-results.component.ts b/src/app/search/search-results/search-results.component.ts index 8725316f1..5331d224b 100644 --- a/src/app/search/search-results/search-results.component.ts +++ b/src/app/search/search-results/search-results.component.ts @@ -16,7 +16,7 @@ import { Component, OnInit } from '@angular/core'; import { faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'; import { CloudData, CloudOptions, TagCloudComponent } from 'angular-tag-cloud-module'; -import { ExtendedGA4GHService } from 'app/shared/openapi'; +import { EntryType, ExtendedGA4GHService } from 'app/shared/openapi'; import { Observable } from 'rxjs'; import { Base } from '../../shared/base'; import { QueryBuilderService } from '../query-builder.service'; @@ -50,6 +50,7 @@ import { NgIf, AsyncPipe } from '@angular/common'; export class SearchResultsComponent extends Base implements OnInit { faPlus = faPlus; faMinus = faMinus; + public EntryType = EntryType; public noToolHits$: Observable; public noWorkflowHits$: Observable; public noNotebookHits$: Observable; diff --git a/src/app/search/search-tool-table/search-tool-table.component.ts b/src/app/search/search-tool-table/search-tool-table.component.ts index 900fdda72..c6b6202c0 100644 --- a/src/app/search/search-tool-table/search-tool-table.component.ts +++ b/src/app/search/search-tool-table/search-tool-table.component.ts @@ -55,7 +55,7 @@ import TopicSelectionEnum = DockstoreTool.TopicSelectionEnum; ], }) export class SearchToolTableComponent extends SearchEntryTable implements OnInit { - readonly entryType = 'tool'; + //readonly entryType = 'tool'; public dataSource: MatTableDataSource>; constructor(dateService: DateService, searchQuery: SearchQuery, searchService: SearchService) { super(dateService, searchQuery, searchService); diff --git a/src/app/search/search-workflow-table/search-workflow-table.component.html b/src/app/search/search-workflow-table/search-workflow-table.component.html index ae9cf1c2d..6ce43f00b 100644 --- a/src/app/search/search-workflow-table/search-workflow-table.component.html +++ b/src/app/search/search-workflow-table/search-workflow-table.component.html @@ -2,23 +2,39 @@
+ + Sort by + + {{ sortOption.label }} + + + - Name and Description + -
- workflow icon - {{ - workflow?.source.organization + - '/' + - workflow?.source.repository + - (workflow?.source.workflowName ? '/' + workflow?.source.workflowName : '') - }} + +
+ +
+ star_rate + {{ + !workflow?.source.starredUsers || workflow?.source.starredUsers.length === 0 ? '' : workflow?.source.starredUsers.length + }} +
+
{{ workflow?.source.topicAutomatic }}
-
-
-
- - Author - -
- -
-
-
- - Format - -
- {{ workflow?.source.descriptorType | descriptorLanguage }} -
-
-
- - Stars - star_rate - {{ !workflow?.source.starredUsers || workflow?.source.starredUsers.length === 0 ? '' : workflow?.source.starredUsers.length }} - - + +
+ account_circle +
- - - -
- - - - - -
{{ this.searchEverythingFriendlyNames.get(highlight.key.toString()) }}:
-
+ +
+ + + + + +
{{ this.searchEverythingFriendlyNames.get(highlight.key.toString()) }}:
+
+ +
+
+ Last updated {{ workflow?.source.last_modified_date ? (workflow?.source.last_modified_date | date) : 'n/a' }} + {{ + workflow?.source.descriptorType | descriptorLanguage + }} + + + {{ language ? language : '' }} + + +
+ +
+
- - - +
>; constructor(dateService: DateService, searchQuery: SearchQuery, searchService: SearchService) { super(dateService, searchQuery, searchService); } - privateNgOnInit(): Observable>> { - return this.searchQuery.workflows$; - } protected readonly TopicSelectionEnum = TopicSelectionEnum; } diff --git a/src/app/search/state/search.query.ts b/src/app/search/state/search.query.ts index ed997e7d5..7d4b594c1 100644 --- a/src/app/search/state/search.query.ts +++ b/src/app/search/state/search.query.ts @@ -7,7 +7,7 @@ import { SearchState, SearchStore } from './search.store'; import { parseTerms } from '../helpers'; export interface SearchResult { - source: T; + source: any; highlight: Map; } diff --git a/src/app/search/state/search.service.ts b/src/app/search/state/search.service.ts index 49387c700..840dc0e96 100644 --- a/src/app/search/state/search.service.ts +++ b/src/app/search/state/search.service.ts @@ -28,7 +28,7 @@ import { ImageProviderService } from '../../shared/image-provider.service'; import { SubBucket } from '../../shared/models/SubBucket'; import { ExtendedGA4GHService } from '../../shared/openapi/api/extendedGA4GH.service'; import { ProviderService } from '../../shared/provider.service'; -import { DockstoreTool, Workflow } from '../../shared/openapi'; +import { DockstoreTool, EntryType, Workflow } from '../../shared/openapi'; import { SearchQuery } from './search.query'; import { SearchStore } from './search.store'; import { SearchAuthorsHtmlPipe } from '../search-authors-html.pipe'; @@ -232,13 +232,13 @@ export class SearchService { b: DockstoreTool | Workflow, attribute: string, direction: SortDirection, - entryType: 'tool' | 'workflow' | 'notebook' + entryType: EntryType ) { // For sorting tools by name, sort tool_path // For sorting workflows by name, sort full_workflow_path - if (entryType === 'tool' && attribute === 'name') { + if (entryType === EntryType.TOOL && attribute === 'name') { attribute = 'tool_path'; - } else if ((entryType === 'workflow' || entryType == 'notebook') && attribute === 'name') { + } else if ((entryType === EntryType.WORKFLOW || entryType == EntryType.NOTEBOOK) && attribute === 'name') { attribute = 'full_workflow_path'; } let aVal = a[attribute]; diff --git a/src/app/shared/entry-to-display-name.pipe.ts b/src/app/shared/entry-to-display-name.pipe.ts index fe14cc407..2f2099d58 100644 --- a/src/app/shared/entry-to-display-name.pipe.ts +++ b/src/app/shared/entry-to-display-name.pipe.ts @@ -10,13 +10,16 @@ import { DockstoreTool, Workflow } from './openapi'; standalone: true, }) export class EntryToDisplayNamePipe implements PipeTransform { - transform(entry: DockstoreTool | Workflow): string { + transform(entry: DockstoreTool | Workflow, includeOrganization: boolean = false): string { if (!entry) { return ''; } if ((entry as Workflow).organization) { const workflow = entry as Workflow; const displayNameArray = []; + if (includeOrganization) { + displayNameArray.push(workflow.organization); + } displayNameArray.push(workflow.repository); if (workflow.workflowName) { displayNameArray.push(workflow.workflowName); @@ -25,6 +28,9 @@ export class EntryToDisplayNamePipe implements PipeTransform { } else { const tool = entry as DockstoreTool; const displayNameArray = []; + if (includeOrganization) { + displayNameArray.push(tool.namespace); + } displayNameArray.push(tool.name); if (tool.toolname) { displayNameArray.push(tool.toolname); diff --git a/src/app/shared/styles/entry-table.scss b/src/app/shared/styles/entry-table.scss index 4bead7c00..c82d767d9 100644 --- a/src/app/shared/styles/entry-table.scss +++ b/src/app/shared/styles/entry-table.scss @@ -48,3 +48,7 @@ th { white-space: nowrap; vertical-align: top; } + +.mat-cell { + overflow: visible; // Needed so the mat-card's box-shadow is displayed +} diff --git a/src/styles.scss b/src/styles.scss index b33eb0b5e..8523eec9e 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -976,6 +976,11 @@ mat-icon.star-icon { font-size: 1.6rem; height: 1.6rem; width: 1.6rem; + color: mat.get-color-from-palette($dockstore-app-accent-1, darker); +} + +.star-color { + color: mat.get-color-from-palette($dockstore-app-accent-1, darker); } .date-display { From c04b4c01578582652b166b1afc2e85e79710c481 Mon Sep 17 00:00:00 2001 From: Kathy Tran Date: Wed, 15 Jan 2025 16:54:45 -0500 Subject: [PATCH 03/10] Simplify by using EntryType --- cypress/e2e/smokeTests/sharedTests/search.ts | 8 +- src/app/search/query-builder.service.spec.ts | 5 +- src/app/search/query-builder.service.ts | 5 +- .../search/search-entry-table.component.html | 121 ++++++++ .../search/search-entry-table.component.scss | 44 +++ ...s => search-entry-table.component.spec.ts} | 29 +- .../search/search-entry-table.component.ts | 276 ++++++++++++++++++ src/app/search/search-entry-table.ts | 142 --------- .../search-notebook-table.component.html | 149 ---------- .../search-notebook-table.component.scss | 11 - .../search-notebook-table.component.spec.ts | 40 --- .../search-notebook-table.component.ts | 93 ------ .../search-results.component.html | 111 +------ .../search-results.component.scss | 36 --- .../search-results.component.spec.ts | 9 +- .../search-results.component.ts | 109 +------ .../search-tool-table.component.html | 115 -------- .../search-tool-table.component.scss | 0 .../search-tool-table.component.ts | 68 ----- .../search-workflow-table.component.html | 93 ------ .../search-workflow-table.component.scss | 3 - .../search-workflow-table.component.spec.ts | 40 --- .../search-workflow-table.component.ts | 92 ------ src/app/search/search.component.html | 3 - src/app/search/search.component.spec.ts | 7 +- src/app/search/state/search.query.ts | 7 +- src/app/search/state/search.service.spec.ts | 24 +- src/app/search/state/search.service.ts | 34 +-- src/app/search/state/search.store.ts | 11 +- 29 files changed, 520 insertions(+), 1165 deletions(-) create mode 100644 src/app/search/search-entry-table.component.html create mode 100644 src/app/search/search-entry-table.component.scss rename src/app/search/{search-tool-table/search-tool-table.component.spec.ts => search-entry-table.component.spec.ts} (51%) create mode 100644 src/app/search/search-entry-table.component.ts delete mode 100644 src/app/search/search-entry-table.ts delete mode 100644 src/app/search/search-notebook-table/search-notebook-table.component.html delete mode 100644 src/app/search/search-notebook-table/search-notebook-table.component.scss delete mode 100644 src/app/search/search-notebook-table/search-notebook-table.component.spec.ts delete mode 100644 src/app/search/search-notebook-table/search-notebook-table.component.ts delete mode 100644 src/app/search/search-tool-table/search-tool-table.component.html delete mode 100644 src/app/search/search-tool-table/search-tool-table.component.scss delete mode 100644 src/app/search/search-tool-table/search-tool-table.component.ts delete mode 100644 src/app/search/search-workflow-table/search-workflow-table.component.html delete mode 100644 src/app/search/search-workflow-table/search-workflow-table.component.scss delete mode 100644 src/app/search/search-workflow-table/search-workflow-table.component.spec.ts delete mode 100644 src/app/search/search-workflow-table/search-workflow-table.component.ts diff --git a/cypress/e2e/smokeTests/sharedTests/search.ts b/cypress/e2e/smokeTests/sharedTests/search.ts index 6dd7263b2..9d8b4aea9 100644 --- a/cypress/e2e/smokeTests/sharedTests/search.ts +++ b/cypress/e2e/smokeTests/sharedTests/search.ts @@ -25,15 +25,15 @@ describe('Admin UI', () => { cy.url().should('not.include', 'search=dhockstore'); cy.contains('Items per page'); - cy.get('[data-cy=search-workflow-table-paginator]').within(() => { + cy.get('[data-cy=search-entry-table-paginator]').within(() => { cy.get('.mat-paginator-range-label').contains('10 of'); }); - cy.get('[data-cy=search-workflow-table-paginator]').contains(10).should('be.visible').click(); + cy.get('[data-cy=search-entry-table-paginator]').contains(10).should('be.visible').click(); cy.get('mat-option').contains(20).click(); - cy.get('[data-cy=search-workflow-table-paginator]').contains(20); + cy.get('[data-cy=search-entry-table-paginator]').contains(20); cy.get('a').contains('Organizations').click(); cy.go('back'); - cy.get('[data-cy=search-workflow-table-paginator]').contains(20); + cy.get('[data-cy=search-entry-table-paginator]').contains(20); cy.get('[data-cy=basic-search]').type('dockstore_{enter}'); cy.contains('Open Advanced Search').click(); diff --git a/src/app/search/query-builder.service.spec.ts b/src/app/search/query-builder.service.spec.ts index 4fea15a04..2492c662f 100644 --- a/src/app/search/query-builder.service.spec.ts +++ b/src/app/search/query-builder.service.spec.ts @@ -20,6 +20,7 @@ import { SearchStubService } from './../test/service-stubs'; import { inject, TestBed } from '@angular/core/testing'; import { QueryBuilderService } from './query-builder.service'; import { SearchService } from './state/search.service'; +import { EntryType } from 'app/shared/openapi'; describe('Service: QueryBuilder', () => { beforeEach(() => { @@ -32,7 +33,7 @@ describe('Service: QueryBuilder', () => { expect(service).toBeTruthy(); })); it('should exclude terms from tagcloud query', inject([QueryBuilderService], (service: QueryBuilderService) => { - expect(service.getTagCloudQuery('tool')).toContain('significant_text'); - expect(service.getTagCloudQuery('tool')).toContain('exclude'); + expect(service.getTagCloudQuery(EntryType.TOOL)).toContain('significant_text'); + expect(service.getTagCloudQuery(EntryType.TOOL)).toContain('exclude'); })); }); diff --git a/src/app/search/query-builder.service.ts b/src/app/search/query-builder.service.ts index e568bebd2..de98fd40e 100644 --- a/src/app/search/query-builder.service.ts +++ b/src/app/search/query-builder.service.ts @@ -22,6 +22,7 @@ import { tagCloudCommonTerms } from './../shared/constants'; import { AdvancedSearchObject } from './../shared/models/AdvancedSearchObject'; import { SearchService } from './state/search.service'; import { parseTerms } from './helpers'; +import { EntryType } from 'app/shared/openapi'; type Index = 'workflows' | 'tools' | 'notebooks'; @@ -37,9 +38,9 @@ export class QueryBuilderService { private shard_size = 10000; constructor(private searchService: SearchService) {} - getTagCloudQuery(type: string): string { + getTagCloudQuery(type: EntryType): string { const tagCloudSize = 20; - const index = type + 's'; + const index = type.toLowerCase() + 's'; // Size to 0 here because https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html#agg-caches let body = bodybuilder().size(0); body = this.excludeContent(body); diff --git a/src/app/search/search-entry-table.component.html b/src/app/search/search-entry-table.component.html new file mode 100644 index 000000000..30adbd6ba --- /dev/null +++ b/src/app/search/search-entry-table.component.html @@ -0,0 +1,121 @@ +
+
+ +
+
+ + Sort by + + {{ sortOption.label }} + + +
+ +
+ + +
+
+
+ + + + + +
+ +
+ star_rate + {{ + !entry?.source.starredUsers || entry?.source.starredUsers.length === 0 ? '' : entry?.source.starredUsers.length + }} +
+
+
+ {{ entry?.source.topicAutomatic }} + +
+ +
+ account_circle +
+ + +
+ + + + + +
{{ this.searchEverythingFriendlyNames.get(highlight.key.toString()) }}:
+
+ +
+
+ Last updated {{ entry?.source.last_modified_date ? (entry?.source.last_modified_date | date) : 'n/a' }} + {{ + entry?.source.descriptorType | descriptorLanguage + }} + + + {{ language ? language : '' }} + + +
+ +
+
+
+
+ + +
+ +
diff --git a/src/app/search/search-entry-table.component.scss b/src/app/search/search-entry-table.component.scss new file mode 100644 index 000000000..4617066eb --- /dev/null +++ b/src/app/search/search-entry-table.component.scss @@ -0,0 +1,44 @@ +/*! + * Copyright 2025 OICR + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@use '@angular/material' as mat; +@import '/src/materialColorScheme.scss'; + +.tag-cloud-dropdown { + position: relative; + display: inline-block !important; +} + +.tagCloud { + position: absolute; + right: 0; + top: 3.5rem; + z-index: 1; + border-radius: 4px; + background-color: #fff; + box-shadow: 0 0 10px 0px mat.get-color-from-palette($dockstore-app-gray, 7); +} + +.tagCloud-btn { + box-shadow: 1px 3px 5px 0px mat.get-color-from-palette($dockstore-app-gray, 7); + color: $header-color; +} + +::ng-deep .sort-by { + .mat-form-field-wrapper { + // Removing the padding because we don't need hint + padding-bottom: 0; + } +} diff --git a/src/app/search/search-tool-table/search-tool-table.component.spec.ts b/src/app/search/search-entry-table.component.spec.ts similarity index 51% rename from src/app/search/search-tool-table/search-tool-table.component.spec.ts rename to src/app/search/search-entry-table.component.spec.ts index cfafb945c..fcee7f3c4 100644 --- a/src/app/search/search-tool-table/search-tool-table.component.spec.ts +++ b/src/app/search/search-entry-table.component.spec.ts @@ -1,38 +1,39 @@ /* eslint-disable no-unused-vars, @typescript-eslint/no-unused-vars */ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { ListContainersService } from '../../containers/list/list.service'; -import { DateService } from '../../shared/date.service'; -import { DockstoreService } from '../../shared/dockstore.service'; -import { DateStubService, DockstoreStubService, ListContainersStubService, SearchStubService } from '../../test/service-stubs'; -import { SearchService } from '../state/search.service'; -import { SearchToolTableComponent } from './search-tool-table.component'; +import { DockstoreService } from '../shared/dockstore.service'; +import { DockstoreStubService, QueryBuilderStubService, SearchStubService } from '../test/service-stubs'; +import { SearchService } from './state/search.service'; +import { SearchEntryTableComponent } from './search-entry-table.component'; +import { EntryType } from 'app/shared/openapi'; +import { QueryBuilderService } from './query-builder.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; -describe('SearchToolTableComponent', () => { - let component: SearchToolTableComponent; - let fixture: ComponentFixture; +describe('SearchEntryTableComponent', () => { + let component: SearchEntryTableComponent; + let fixture: ComponentFixture; beforeEach( waitForAsync(() => { TestBed.configureTestingModule({ schemas: [NO_ERRORS_SCHEMA], - imports: [BrowserAnimationsModule, RouterTestingModule, SearchToolTableComponent], + imports: [BrowserAnimationsModule, RouterTestingModule, SearchEntryTableComponent, HttpClientTestingModule], providers: [ { provide: DockstoreService, useClass: DockstoreStubService }, - { provide: DateService, useClass: DateStubService }, - { provide: ListContainersService, useClass: ListContainersStubService }, { provide: SearchService, useClass: SearchStubService }, + { provide: QueryBuilderService, useClass: QueryBuilderStubService }, ], }).compileComponents(); }) ); beforeEach(() => { - fixture = TestBed.createComponent(SearchToolTableComponent); + fixture = TestBed.createComponent(SearchEntryTableComponent); component = fixture.componentInstance; + component.entryType = EntryType.WORKFLOW; fixture.detectChanges(); }); diff --git a/src/app/search/search-entry-table.component.ts b/src/app/search/search-entry-table.component.ts new file mode 100644 index 000000000..7c1d03186 --- /dev/null +++ b/src/app/search/search-entry-table.component.ts @@ -0,0 +1,276 @@ +/* + * Copyright 2018 OICR + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { + MatLegacyPaginatorModule, + MatLegacyPaginator as MatPaginator, + LegacyPageEvent as PageEvent, +} from '@angular/material/legacy-paginator'; +import { MatSort, MatSortModule, Sort } from '@angular/material/sort'; +import { MatLegacyTableModule, MatLegacyTableDataSource as MatTableDataSource } from '@angular/material/legacy-table'; +import { combineLatest, Observable, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { Base } from '../shared/base'; +import { SearchQuery, SearchResult } from './state/search.query'; +import { SearchService } from './state/search.service'; +import { EntryType, ExtendedGA4GHService, Workflow } from 'app/shared/openapi'; +import { AsyncPipe, DatePipe, KeyValuePipe, LowerCasePipe, NgFor, NgIf } from '@angular/common'; +import TopicSelectionEnum = Workflow.TopicSelectionEnum; +import { MatLegacyProgressBarModule } from '@angular/material/legacy-progress-bar'; +import { MatLegacyTooltipModule } from '@angular/material/legacy-tooltip'; +import { RouterLink } from '@angular/router'; +import { AiBubbleComponent } from 'app/shared/ai-bubble/ai-bubble.component'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatIconModule } from '@angular/material/icon'; +import { MatLegacyCardModule } from '@angular/material/legacy-card'; +import { MatLegacyOptionModule } from '@angular/material/legacy-core'; +import { MatLegacyFormFieldModule } from '@angular/material/legacy-form-field'; +import { MatLegacySelectModule } from '@angular/material/legacy-select'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { ExtendedModule, FlexLayoutModule } from '@ngbracket/ngx-layout'; +import { RouterLinkPipe } from 'app/entry/router-link.pipe'; +import { EntryToDisplayNamePipe } from 'app/shared/entry-to-display-name.pipe'; +import { DescriptorLanguagePipe } from 'app/shared/entry/descriptor-language.pipe'; +import { DoiBadgeComponent } from 'app/shared/entry/doi/doi-badge/doi-badge.component'; +import { JoinWithEllipsesPipe } from './join-with-ellipses.pipe'; +import { SearchAuthorsHtmlPipe } from './search-authors-html.pipe'; +import { CloudData, CloudOptions, TagCloudComponent } from 'angular-tag-cloud-module'; +import { faPlus, faMinus } from '@fortawesome/free-solid-svg-icons'; +import { QueryBuilderService } from './query-builder.service'; +import { MatLegacyButtonModule } from '@angular/material/legacy-button'; + +export interface SortOption { + label: string; + sort: Sort; +} + +/** + * this component refers to search page not workflow listing search + */ + +@Component({ + selector: 'app-search-entry-table', + templateUrl: './search-entry-table.component.html', + styleUrls: ['../shared/styles/entry-table.scss', './search-entry-table.component.scss'], + standalone: true, + imports: [ + NgIf, + MatLegacyProgressBarModule, + MatLegacyTableModule, + MatSortModule, + MatLegacyTooltipModule, + RouterLink, + AiBubbleComponent, + ExtendedModule, + MatIconModule, + FontAwesomeModule, + NgFor, + MatLegacyPaginatorModule, + KeyValuePipe, + DescriptorLanguagePipe, + SearchAuthorsHtmlPipe, + JoinWithEllipsesPipe, + MatLegacyCardModule, + FlexLayoutModule, + DatePipe, + DoiBadgeComponent, + MatLegacyFormFieldModule, + MatLegacyOptionModule, + MatLegacySelectModule, + EntryToDisplayNamePipe, + RouterLinkPipe, + MatDividerModule, + TagCloudComponent, + AsyncPipe, + MatLegacyButtonModule, + LowerCasePipe, + ], +}) +export class SearchEntryTableComponent extends Base implements OnInit { + faPlus = faPlus; + faMinus = faMinus; + public EntryType = EntryType; + protected readonly TopicSelectionEnum = TopicSelectionEnum; + @Input() entryType: EntryType; + @ViewChild(MatPaginator, { static: true }) protected paginator: MatPaginator; + @ViewChild(MatSort, { static: true }) protected sort: MatSort; + protected ngUnsubscribe: Subject<{}> = new Subject(); + + public readonly displayedColumns = ['name']; + public readonly columnsToDisplayWithExpand = [...this.displayedColumns, 'expand']; + public readonly searchEverythingFriendlyNames = new Map([ + ['full_workflow_path', 'Path'], + ['tool_path', 'Path'], + ['workflowVersions.sourceFiles.content', 'Source Files'], + ['tags.sourceFiles.content', 'Source Files'], + ['description', 'Description'], + ['labels', 'Labels'], + ['all_authors.name', 'Authors'], + ['topicAutomatic', 'Topic'], + ['categories.topic', 'Category Topic'], + ['categories.displayName', 'Category'], + ]); + public defaultSortOption: SortOption = { + label: 'Most Stars', + sort: { active: 'starredUsers', direction: 'desc' }, + }; + public sortOptions: SortOption[] = [ + this.defaultSortOption, + { + label: 'Least Stars', + sort: { active: 'starredUsers', direction: 'asc' }, + }, + { + label: 'Name, A-Z', + sort: { active: 'name', direction: 'asc' }, + }, + { + label: 'Name, Z-A', + sort: { active: 'name', direction: 'desc' }, + }, + { + label: 'Authors, A-Z', + sort: { active: 'all_authors', direction: 'asc' }, + }, + { + label: 'Authors, Z-A', + sort: { active: 'all_authors', direction: 'desc' }, + }, + ]; + + dataSource: MatTableDataSource; + tagCloudData: Array; + showTagCloudForEntryType: boolean; + options: CloudOptions = { + width: 500, + height: 200, + overflow: false, + }; + + privateNgOnInit(): Observable> { + switch (this.entryType) { + case EntryType.WORKFLOW: + return this.searchQuery.workflows$; + case EntryType.TOOL: + return this.searchQuery.tools$; + case EntryType.NOTEBOOK: + return this.searchQuery.notebooks$; + default: + return null; + } + } + + constructor( + protected searchQuery: SearchQuery, + protected searchService: SearchService, + private queryBuilderService: QueryBuilderService, + private extendedGA4GHService: ExtendedGA4GHService + ) { + super(); + } + + ngOnInit(): void { + this.createTagCloud(this.entryType); + + this.dataSource = new MatTableDataSource(); + this.dataSource.sort = this.sort; + this.setSort(this.defaultSortOption.sort); + this.dataSource.paginator = this.paginator; + + combineLatest([this.searchQuery.showTagCloud$, this.searchQuery.currentEntryType$]) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe(([showTagCloud, currentEntryType]) => { + this.showTagCloudForEntryType = showTagCloud && currentEntryType === this.entryType; + }); + + combineLatest([this.searchQuery.pageSize$, this.searchQuery.pageIndex$, this.privateNgOnInit()]) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe(([pageSize, pageIndex, entries]) => { + this.dataSource.paginator.pageSize = pageSize; + this.dataSource.paginator.pageIndex = pageIndex; + // Must set data after paginator, just a material datatables thing. + this.dataSource.data = entries || []; + }); + this.dataSource.sortData = (data: SearchResult[], sort: MatSort) => { + if (sort.active && sort.direction) { + return data.slice().sort((a: SearchResult, b: SearchResult) => { + return this.searchService.compareAttributes(a.source, b.source, sort.active, sort.direction, this.entryType); + }); + } else { + // Either the active field or direction is unset, so return the data in the original order, unsorted. + return data; + } + }; + } + + updatePageSizeAndIndex($event: PageEvent) { + this.searchService.setPageSizeAndIndex($event.pageSize, $event.pageIndex); + } + + setSort(sortValue: Sort) { + this.sort.active = sortValue.active; + this.sort.direction = sortValue.direction; + this.sort.sortChange.emit(sortValue); + } + + createTagCloud(type: EntryType) { + const toolQuery = this.queryBuilderService.getTagCloudQuery(type); + this.createToolTagCloud(toolQuery); + } + + clickTagCloudBtn(type: EntryType) { + this.searchService.setShowTagCloud(type); + } + + createToolTagCloud(toolQuery: string) { + this.extendedGA4GHService.toolsIndexSearch(toolQuery).subscribe( + (hits: any) => { + let weight = 10; + let count = 0; + if (hits && hits.aggregations && hits.aggregations.tagcloud) { + console.log('createToolTagCloud'); + hits.aggregations.tagcloud.buckets.forEach((tag) => { + const theTag = { + text: tag.key, + weight: weight, + }; + if (weight === 10) { + /** just for fun...**/ + theTag['color'] = '#ffaaee'; + } + if (count % 2 !== 0) { + weight--; + } + if (!this.tagCloudData) { + this.tagCloudData = new Array(); + } + this.tagCloudData.push(theTag); + count--; + }); + } + }, + (error) => { + console.log(error); + } + ); + } + + tagClicked(clicked: CloudData) { + this.searchService.searchTerm$.next(true); + this.searchService.setSearchText(clicked.text); + this.searchService.tagClicked$.next(true); + } +} diff --git a/src/app/search/search-entry-table.ts b/src/app/search/search-entry-table.ts deleted file mode 100644 index 8ed563dd5..000000000 --- a/src/app/search/search-entry-table.ts +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2018 OICR - * - * Licensed under the Apache License, Version 2.0 (the "License") - * you may not use this file except in compliance with the License - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { Directive, Input, OnInit, ViewChild } from '@angular/core'; -import { MatLegacyPaginator as MatPaginator, LegacyPageEvent as PageEvent } from '@angular/material/legacy-paginator'; -import { MatSort, Sort } from '@angular/material/sort'; -import { MatLegacyTableDataSource as MatTableDataSource } from '@angular/material/legacy-table'; -import { combineLatest, Observable, Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; -import { Base } from '../shared/base'; -import { DateService } from '../shared/date.service'; -import { SearchQuery, SearchResult } from './state/search.query'; -import { SearchService } from './state/search.service'; -import { EntryType, EntryTypeMetadata } from 'app/shared/openapi'; - -export interface SortOption { - label: string; - sort: Sort; -} - -@Directive() -// eslint-disable-next-line @angular-eslint/directive-class-suffix -export abstract class SearchEntryTable extends Base implements OnInit { - public EntryType = EntryType; - @Input() entryType: EntryType; - @ViewChild(MatPaginator, { static: true }) protected paginator: MatPaginator; - @ViewChild(MatSort, { static: true }) protected sort: MatSort; - protected verifiedLink: string; - protected entryTypeMetadata: EntryTypeMetadata; - protected ngUnsubscribe: Subject<{}> = new Subject(); - - public readonly displayedColumns = ['name']; - public readonly columnsToDisplayWithExpand = [...this.displayedColumns, 'expand']; - public readonly searchEverythingFriendlyNames = new Map([ - ['full_workflow_path', 'Path'], - ['tool_path', 'Path'], - ['workflowVersions.sourceFiles.content', 'Source Files'], - ['tags.sourceFiles.content', 'Source Files'], - ['description', 'Description'], - ['labels', 'Labels'], - ['all_authors.name', 'Authors'], - ['topicAutomatic', 'Topic'], - ['categories.topic', 'Category Topic'], - ['categories.displayName', 'Category'], - ]); - public defaultSortOption: SortOption = { - label: 'Most Stars', - sort: { active: 'starredUsers', direction: 'desc' }, - }; - public sortOptions: SortOption[] = [ - this.defaultSortOption, - { - label: 'Least Stars', - sort: { active: 'starredUsers', direction: 'asc' }, - }, - { - label: 'Name, A-Z', - sort: { active: 'name', direction: 'asc' }, - }, - { - label: 'Name, Z-A', - sort: { active: 'name', direction: 'desc' }, - }, - { - label: 'Authors, A-Z', - sort: { active: 'all_authors', direction: 'asc' }, - }, - { - label: 'Authors, Z-A', - sort: { active: 'all_authors', direction: 'desc' }, - }, - ]; - - //abstract readonly entryType: EntryType; - abstract dataSource: MatTableDataSource; - //abstract privateNgOnInit(): Observable; - - privateNgOnInit(): Observable> { - switch (this.entryType) { - case EntryType.WORKFLOW: - return this.searchQuery.workflows$; - case EntryType.TOOL: - return this.searchQuery.tools$; - case EntryType.NOTEBOOK: - return this.searchQuery.notebooks$; - default: - return null; - } - } - - constructor(protected dateService: DateService, protected searchQuery: SearchQuery, protected searchService: SearchService) { - super(); - this.verifiedLink = this.dateService.getVerifiedLink(); - } - - ngOnInit(): void { - this.dataSource = new MatTableDataSource(); - this.dataSource.sort = this.sort; - this.setSort(this.defaultSortOption.sort); - this.dataSource.paginator = this.paginator; - combineLatest([this.searchQuery.pageSize$, this.searchQuery.pageIndex$, this.privateNgOnInit()]) - .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe(([pageSize, pageIndex, entries]) => { - this.dataSource.paginator.pageSize = pageSize; - this.dataSource.paginator.pageIndex = pageIndex; - // Must set data after paginator, just a material datatables thing. - this.dataSource.data = entries || []; - }); - this.dataSource.sortData = (data: SearchResult[], sort: MatSort) => { - if (sort.active && sort.direction) { - return data.slice().sort((a: SearchResult, b: SearchResult) => { - return this.searchService.compareAttributes(a.source, b.source, sort.active, sort.direction, this.entryType); - }); - } else { - // Either the active field or direction is unset, so return the data in the original order, unsorted. - return data; - } - }; - } - - updatePageSizeAndIndex($event: PageEvent) { - this.searchService.setPageSizeAndIndex($event.pageSize, $event.pageIndex); - } - - setSort(sortValue: Sort) { - this.sort.active = sortValue.active; - this.sort.direction = sortValue.direction; - this.sort.sortChange.emit(sortValue); - } -} diff --git a/src/app/search/search-notebook-table/search-notebook-table.component.html b/src/app/search/search-notebook-table/search-notebook-table.component.html deleted file mode 100644 index 35cbdd1aa..000000000 --- a/src/app/search/search-notebook-table/search-notebook-table.component.html +++ /dev/null @@ -1,149 +0,0 @@ -
-
- -
- - Sort by - - {{ sortOption.label }} - - - - - - - -
- -
- star_rate - {{ - !notebook?.source.starredUsers || notebook?.source.starredUsers.length === 0 ? '' : notebook?.source.starredUsers.length - }} -
-
-
- {{ notebook?.source.topicAutomatic }} - -
- -
- account_circle -
- - -
- - - - - -
{{ this.searchEverythingFriendlyNames.get(highlight.key.toString()) }}:
-
- -
-
- Last updated {{ notebook?.source.last_modified_date | date }} - {{ notebook?.source.descriptorType | descriptorLanguage }} -
- -
-
- - -
-
- - Author - -
- -
-
-
- - Format - -
- {{ notebook?.source.descriptorType | descriptorLanguage }} -
-
-
- - Language - -
- {{ notebook?.source.descriptorTypeSubclass }} -
-
-
- - Stars - star_rate - {{ !notebook?.source.starredUsers || notebook?.source.starredUsers.length === 0 ? '' : notebook?.source.starredUsers.length }} - - - - - - - - -
- -
diff --git a/src/app/search/search-notebook-table/search-notebook-table.component.scss b/src/app/search/search-notebook-table/search-notebook-table.component.scss deleted file mode 100644 index fabe79988..000000000 --- a/src/app/search/search-notebook-table/search-notebook-table.component.scss +++ /dev/null @@ -1,11 +0,0 @@ -.mat-column-descriptorType { - max-width: 10rem; -} - -.mat-column-descriptorTypeSubclass { - max-width: 10rem; -} - -td.mat-cell { - border-bottom-style: none; -} diff --git a/src/app/search/search-notebook-table/search-notebook-table.component.spec.ts b/src/app/search/search-notebook-table/search-notebook-table.component.spec.ts deleted file mode 100644 index 30caf1b70..000000000 --- a/src/app/search/search-notebook-table/search-notebook-table.component.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-disable no-unused-vars, @typescript-eslint/no-unused-vars */ -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; - -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { RouterTestingModule } from '@angular/router/testing'; -import { DateService } from '../../shared/date.service'; -import { DockstoreService } from '../../shared/dockstore.service'; -import { DateStubService, DockstoreStubService, SearchStubService } from '../../test/service-stubs'; -import { SearchService } from '../state/search.service'; -import { SearchNotebookTableComponent } from './search-notebook-table.component'; - -describe('SearchNotebookTableComponent', () => { - let component: SearchNotebookTableComponent; - let fixture: ComponentFixture; - - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - schemas: [NO_ERRORS_SCHEMA], - imports: [BrowserAnimationsModule, RouterTestingModule, SearchNotebookTableComponent], - providers: [ - { provide: DockstoreService, useClass: DockstoreStubService }, - { provide: DateService, useClass: DateStubService }, - { provide: SearchService, useClass: SearchStubService }, - ], - }).compileComponents(); - }) - ); - - beforeEach(() => { - fixture = TestBed.createComponent(SearchNotebookTableComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/search/search-notebook-table/search-notebook-table.component.ts b/src/app/search/search-notebook-table/search-notebook-table.component.ts deleted file mode 100644 index 658935ccf..000000000 --- a/src/app/search/search-notebook-table/search-notebook-table.component.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2023 OICR, UCSC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { Component, OnInit } from '@angular/core'; -import { MatLegacyTableDataSource as MatTableDataSource, MatLegacyTableModule } from '@angular/material/legacy-table'; -import { Observable } from 'rxjs'; -import { DateService } from '../../shared/date.service'; -import { Notebook, Workflow } from '../../shared/openapi'; -import { SearchEntryTable } from '../search-entry-table'; -import { SearchQuery, SearchResult } from '../state/search.query'; -import { SearchService } from '../state/search.service'; -import { JoinWithEllipsesPipe } from 'app/search/join-with-ellipses.pipe'; -import { SearchAuthorsHtmlPipe } from 'app/search/search-authors-html.pipe'; -import { DescriptorLanguagePipe } from '../../shared/entry/descriptor-language.pipe'; -import { MatLegacyPaginatorModule } from '@angular/material/legacy-paginator'; -import { MatIconModule } from '@angular/material/icon'; -import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { ExtendedModule } from '@ngbracket/ngx-layout/extended'; -import { AiBubbleComponent } from '../../shared/ai-bubble/ai-bubble.component'; -import { RouterLink } from '@angular/router'; -import { MatLegacyTooltipModule } from '@angular/material/legacy-tooltip'; -import { MatSortModule } from '@angular/material/sort'; -import { MatLegacyProgressBarModule } from '@angular/material/legacy-progress-bar'; -import { NgIf, NgFor, KeyValuePipe, DatePipe } from '@angular/common'; -import TopicSelectionEnum = Workflow.TopicSelectionEnum; -import { MatLegacyCardModule } from '@angular/material/legacy-card'; -import { FlexLayoutModule } from '@ngbracket/ngx-layout'; -import { DoiBadgeComponent } from 'app/shared/entry/doi/doi-badge/doi-badge.component'; -import { MatLegacyFormFieldModule } from '@angular/material/legacy-form-field'; -import { MatLegacyOptionModule } from '@angular/material/legacy-core'; -import { MatLegacySelectModule } from '@angular/material/legacy-select'; - -/** - * this component refers to search page not notebook listing search - */ - -@Component({ - selector: 'app-search-notebook-table', - templateUrl: './search-notebook-table.component.html', - styleUrls: ['../../shared/styles/entry-table.scss', './search-notebook-table.component.scss'], - standalone: true, - imports: [ - NgIf, - MatLegacyProgressBarModule, - MatLegacyTableModule, - MatSortModule, - MatLegacyTooltipModule, - RouterLink, - AiBubbleComponent, - ExtendedModule, - FontAwesomeModule, - MatIconModule, - NgFor, - MatLegacyPaginatorModule, - KeyValuePipe, - DescriptorLanguagePipe, - SearchAuthorsHtmlPipe, - JoinWithEllipsesPipe, - MatLegacyCardModule, - FlexLayoutModule, - DatePipe, - DoiBadgeComponent, - MatLegacyFormFieldModule, - MatLegacyOptionModule, - MatLegacySelectModule, - ], -}) -export class SearchNotebookTableComponent extends SearchEntryTable implements OnInit { - public readonly displayedColumns = ['name']; - //readonly entryType = 'notebook'; - public dataSource: MatTableDataSource>; - constructor(dateService: DateService, searchQuery: SearchQuery, searchService: SearchService) { - super(dateService, searchQuery, searchService); - } - - privateNgOnInit(): Observable>> { - return this.searchQuery.notebooks$; - } - - protected readonly TopicSelectionEnum = TopicSelectionEnum; -} diff --git a/src/app/search/search-results/search-results.component.html b/src/app/search/search-results/search-results.component.html index 5fe15b27f..629fc8f31 100644 --- a/src/app/search/search-results/search-results.component.html +++ b/src/app/search/search-results/search-results.component.html @@ -14,106 +14,25 @@ ~ limitations under the License. -->
-
-
-
- -

A Workflow can use multiple containers and executes multiple actions or steps, outlined by one or more descriptors

-
-
- -
- - -
-
-
-
- - -
+ +

A Workflow can use multiple containers and executes multiple actions or steps, outlined by one or more descriptors.

+
+ +
-
-
-
- -

A Tool uses a single container and performs a single action or step that is outlined by a descriptor

-
-
- -
- - -
-
-
-
- - -
+ +

A Tool uses a single container and performs a single action or step that is outlined by a descriptor.

+
+ +
-
-
-
- -

A Notebook is a document containing code and text that can be interactively edited and run

-
-
- -
- - -
-
-
-
- - -
+ +

A Notebook is a document containing code and text that can be interactively edited and run.

+
+ +
diff --git a/src/app/search/search-results/search-results.component.scss b/src/app/search/search-results/search-results.component.scss index 5c07db41e..e8466e190 100644 --- a/src/app/search/search-results/search-results.component.scss +++ b/src/app/search/search-results/search-results.component.scss @@ -15,39 +15,3 @@ */ @use '@angular/material' as mat; @import '/src/materialColorScheme.scss'; - -.mat-tab-content { - display: block; - background-color: #fff; - margin: 1rem 1rem 2rem; - padding: 2.5rem; - border: 0.0625rem solid mat.get-color-from-palette($dockstore-app-gray, 7); - border-radius: 0.3rem; -} - -.tag-cloud-dropdown { - position: relative; - display: inline-block; -} - -.tagCloud { - position: absolute; - right: 0; - top: 3.5rem; - z-index: 1; - border-radius: 4px; - background-color: #fff; - box-shadow: 0 0 10px 0px mat.get-color-from-palette($dockstore-app-gray, 7); -} - -.tagCloud-btn { - box-shadow: 1px 3px 5px 0px mat.get-color-from-palette($dockstore-app-gray, 7); - color: $header-color; - border: none; - &.tool { - background-color: #aadeee; - } - &.workflow { - background-color: #b0f8e6; - } -} diff --git a/src/app/search/search-results/search-results.component.spec.ts b/src/app/search/search-results/search-results.component.spec.ts index d98734405..b3c7c6879 100644 --- a/src/app/search/search-results/search-results.component.spec.ts +++ b/src/app/search/search-results/search-results.component.spec.ts @@ -17,14 +17,13 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { DateService } from '../../shared/date.service'; -import { DateStubService, ExtendedGA4GHStubService, QueryBuilderStubService, SearchStubService } from './../../test/service-stubs'; +import { QueryBuilderStubService, SearchStubService } from './../../test/service-stubs'; import { QueryBuilderService } from './../query-builder.service'; import { RouterTestingModule } from '@angular/router/testing'; -import { ExtendedGA4GHService } from 'app/shared/openapi'; import { SearchService } from '../state/search.service'; import { SearchResultsComponent } from './search-results.component'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; describe('SearchResultsComponent', () => { let component: SearchResultsComponent; @@ -34,12 +33,10 @@ describe('SearchResultsComponent', () => { waitForAsync(() => { TestBed.configureTestingModule({ schemas: [NO_ERRORS_SCHEMA], - imports: [RouterTestingModule, SearchResultsComponent, NoopAnimationsModule], + imports: [RouterTestingModule, SearchResultsComponent, NoopAnimationsModule, HttpClientTestingModule], providers: [ { provide: SearchService, useClass: SearchStubService }, { provide: QueryBuilderService, useClass: QueryBuilderStubService }, - { provide: ExtendedGA4GHService, useClass: ExtendedGA4GHStubService }, - { provide: DateService, useClass: DateStubService }, ], }).compileComponents(); }) diff --git a/src/app/search/search-results/search-results.component.ts b/src/app/search/search-results/search-results.component.ts index 5331d224b..a9c8b8adf 100644 --- a/src/app/search/search-results/search-results.component.ts +++ b/src/app/search/search-results/search-results.component.ts @@ -14,131 +14,38 @@ * limitations under the License. */ import { Component, OnInit } from '@angular/core'; -import { faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'; -import { CloudData, CloudOptions, TagCloudComponent } from 'angular-tag-cloud-module'; -import { EntryType, ExtendedGA4GHService } from 'app/shared/openapi'; +import { EntryType } from 'app/shared/openapi'; import { Observable } from 'rxjs'; import { Base } from '../../shared/base'; -import { QueryBuilderService } from '../query-builder.service'; import { SearchQuery } from '../state/search.query'; import { SearchService } from '../state/search.service'; -import { SearchNotebookTableComponent } from '../search-notebook-table/search-notebook-table.component'; -import { SearchToolTableComponent } from '../search-tool-table/search-tool-table.component'; -import { SearchWorkflowTableComponent } from '../search-workflow-table/search-workflow-table.component'; import { MatDividerModule } from '@angular/material/divider'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { FlexModule } from '@ngbracket/ngx-layout/flex'; import { NgIf, AsyncPipe } from '@angular/common'; +import { SearchEntryTableComponent } from '../search-entry-table.component'; @Component({ selector: 'app-search-results', templateUrl: './search-results.component.html', styleUrls: ['./search-results.component.scss'], standalone: true, - imports: [ - NgIf, - FlexModule, - FontAwesomeModule, - TagCloudComponent, - MatDividerModule, - SearchWorkflowTableComponent, - SearchToolTableComponent, - SearchNotebookTableComponent, - AsyncPipe, - ], + imports: [NgIf, FlexModule, FontAwesomeModule, MatDividerModule, SearchEntryTableComponent, AsyncPipe], }) export class SearchResultsComponent extends Base implements OnInit { - faPlus = faPlus; - faMinus = faMinus; public EntryType = EntryType; public noToolHits$: Observable; public noWorkflowHits$: Observable; public noNotebookHits$: Observable; - public showWorkflowTagCloud$: Observable; - public showToolTagCloud$: Observable; - public showNotebookTagCloud$: Observable; - toolTagCloudData: Array; - workflowTagCloudData: Array; - notebookTagCloudData: Array; - options: CloudOptions = { - width: 500, - height: 200, - overflow: false, - }; - constructor( - private searchService: SearchService, - private queryBuilderService: QueryBuilderService, - private searchQuery: SearchQuery, - private extendedGA4GHService: ExtendedGA4GHService - ) { + constructor(private searchService: SearchService, private searchQuery: SearchQuery) { super(); this.noWorkflowHits$ = this.searchQuery.noWorkflowHits$; this.noToolHits$ = this.searchQuery.noToolHits$; this.noNotebookHits$ = this.searchQuery.noNotebookHits$; - this.showToolTagCloud$ = this.searchQuery.showToolTagCloud$; - this.showWorkflowTagCloud$ = this.searchQuery.showWorkflowTagCloud$; - this.showNotebookTagCloud$ = this.searchQuery.showNotebookTagCloud$; } - ngOnInit() { - this.createTagCloud('tool'); - this.createTagCloud('workflow'); - this.createTagCloud('notebook'); - } - - createTagCloud(type: string) { - const toolQuery = this.queryBuilderService.getTagCloudQuery(type); - this.createToolTagCloud(toolQuery, type); - } - - clickTagCloudBtn(type: 'tool' | 'workflow' | 'notebook') { - this.searchService.setShowTagCloud(type); - } - - createToolTagCloud(toolQuery: string, type) { - this.extendedGA4GHService.toolsIndexSearch(toolQuery).subscribe( - (hits: any) => { - let weight = 10; - let count = 0; - if (hits && hits.aggregations && hits.aggregations.tagcloud) { - hits.aggregations.tagcloud.buckets.forEach((tag) => { - const theTag = { - text: tag.key, - weight: weight, - }; - if (weight === 10) { - /** just for fun...**/ - theTag['color'] = '#ffaaee'; - } - if (count % 2 !== 0) { - weight--; - } - if (type === 'tool') { - if (!this.toolTagCloudData) { - this.toolTagCloudData = new Array(); - } - this.toolTagCloudData.push(theTag); - } else if (type === 'workflow') { - if (!this.workflowTagCloudData) { - this.workflowTagCloudData = new Array(); - } - this.workflowTagCloudData.push(theTag); - } else { - if (!this.notebookTagCloudData) { - this.notebookTagCloudData = new Array(); - } - this.notebookTagCloudData.push(theTag); - } - count--; - }); - } - }, - (error) => { - console.log(error); - } - ); - } + ngOnInit() {} // Tells the search service to tell the search filters to save its data saveSearchFilter() { @@ -148,10 +55,4 @@ export class SearchResultsComponent extends Base implements OnInit { getTabIndex() { return this.searchQuery.getValue().currentTabIndex; } - - tagClicked(clicked: CloudData) { - this.searchService.searchTerm$.next(true); - this.searchService.setSearchText(clicked.text); - this.searchService.tagClicked$.next(true); - } } diff --git a/src/app/search/search-tool-table/search-tool-table.component.html b/src/app/search/search-tool-table/search-tool-table.component.html deleted file mode 100644 index 69110ecfe..000000000 --- a/src/app/search/search-tool-table/search-tool-table.component.html +++ /dev/null @@ -1,115 +0,0 @@ - -
-
- -
- - - Name and Description - - - - - - - Author - -
- -
-
-
- - Format - - -
- {{ tool?.source.descriptorType | uppercase }} -
-
- -
- - {{ language ? (language.toString() | uppercase) : '' }} - -
-
-
-
- - Stars - star_rate - {{ !tool?.source.starredUsers || tool?.source.starredUsers.length === 0 ? '' : tool?.source.starredUsers.length }} - - - - - - -
- - - - - -
{{ this.searchEverythingFriendlyNames.get(highlight.key.toString()) }}:
-
-
-
- - - - -
- -
diff --git a/src/app/search/search-tool-table/search-tool-table.component.scss b/src/app/search/search-tool-table/search-tool-table.component.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/app/search/search-tool-table/search-tool-table.component.ts b/src/app/search/search-tool-table/search-tool-table.component.ts deleted file mode 100644 index c6b6202c0..000000000 --- a/src/app/search/search-tool-table/search-tool-table.component.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { MatLegacyTableDataSource as MatTableDataSource, MatLegacyTableModule } from '@angular/material/legacy-table'; -import { Observable } from 'rxjs'; -import { DateService } from '../../shared/date.service'; -import { AppTool, DockstoreTool } from '../../shared/openapi'; -import { SearchEntryTable } from '../search-entry-table'; -import { SearchQuery, SearchResult } from '../state/search.query'; -import { SearchService } from '../state/search.service'; -import { IsAppToolPipe } from '../is-app-tool.pipe'; -import { JoinWithEllipsesPipe } from 'app/search/join-with-ellipses.pipe'; -import { SearchAuthorsHtmlPipe } from 'app/search/search-authors-html.pipe'; -import { MatLegacyPaginatorModule } from '@angular/material/legacy-paginator'; -import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { FlexModule } from '@ngbracket/ngx-layout/flex'; -import { MatIconModule } from '@angular/material/icon'; -import { ExtendedModule } from '@ngbracket/ngx-layout/extended'; -import { AiBubbleComponent } from '../../shared/ai-bubble/ai-bubble.component'; -import { RouterLink } from '@angular/router'; -import { MatLegacyTooltipModule } from '@angular/material/legacy-tooltip'; -import { PrivateIconComponent } from '../../shared/private-icon/private-icon.component'; -import { MatSortModule } from '@angular/material/sort'; -import { MatLegacyProgressBarModule } from '@angular/material/legacy-progress-bar'; -import { NgIf, NgFor, UpperCasePipe, KeyValuePipe } from '@angular/common'; -import TopicSelectionEnum = DockstoreTool.TopicSelectionEnum; - -/** - * this component refers to search page not tool listing search - */ - -@Component({ - selector: 'app-search-tool-table', - templateUrl: './search-tool-table.component.html', - styleUrls: ['../../shared/styles/entry-table.scss', './search-tool-table.component.scss'], - standalone: true, - imports: [ - NgIf, - MatLegacyProgressBarModule, - MatLegacyTableModule, - MatSortModule, - PrivateIconComponent, - MatLegacyTooltipModule, - RouterLink, - AiBubbleComponent, - ExtendedModule, - MatIconModule, - FlexModule, - NgFor, - FontAwesomeModule, - MatLegacyPaginatorModule, - UpperCasePipe, - KeyValuePipe, - SearchAuthorsHtmlPipe, - JoinWithEllipsesPipe, - IsAppToolPipe, - ], -}) -export class SearchToolTableComponent extends SearchEntryTable implements OnInit { - //readonly entryType = 'tool'; - public dataSource: MatTableDataSource>; - constructor(dateService: DateService, searchQuery: SearchQuery, searchService: SearchService) { - super(dateService, searchQuery, searchService); - } - - privateNgOnInit(): Observable>> { - return this.searchQuery.tools$; - } - protected readonly TopicSelectionEnum = TopicSelectionEnum; -} diff --git a/src/app/search/search-workflow-table/search-workflow-table.component.html b/src/app/search/search-workflow-table/search-workflow-table.component.html deleted file mode 100644 index 6ce43f00b..000000000 --- a/src/app/search/search-workflow-table/search-workflow-table.component.html +++ /dev/null @@ -1,93 +0,0 @@ -
-
- -
- - Sort by - - {{ sortOption.label }} - - - - - - - - -
- -
- star_rate - {{ - !workflow?.source.starredUsers || workflow?.source.starredUsers.length === 0 ? '' : workflow?.source.starredUsers.length - }} -
-
-
- {{ workflow?.source.topicAutomatic }} - -
- -
- account_circle -
- - -
- - - - - -
{{ this.searchEverythingFriendlyNames.get(highlight.key.toString()) }}:
-
- -
-
- Last updated {{ workflow?.source.last_modified_date ? (workflow?.source.last_modified_date | date) : 'n/a' }} - {{ - workflow?.source.descriptorType | descriptorLanguage - }} - - - {{ language ? language : '' }} - - -
- -
-
-
-
- - -
- -
diff --git a/src/app/search/search-workflow-table/search-workflow-table.component.scss b/src/app/search/search-workflow-table/search-workflow-table.component.scss deleted file mode 100644 index a0e857e97..000000000 --- a/src/app/search/search-workflow-table/search-workflow-table.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.mat-column-descriptorType { - max-width: 10rem; -} diff --git a/src/app/search/search-workflow-table/search-workflow-table.component.spec.ts b/src/app/search/search-workflow-table/search-workflow-table.component.spec.ts deleted file mode 100644 index 76cb256d5..000000000 --- a/src/app/search/search-workflow-table/search-workflow-table.component.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-disable no-unused-vars, @typescript-eslint/no-unused-vars */ -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; - -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { RouterTestingModule } from '@angular/router/testing'; -import { DateService } from '../../shared/date.service'; -import { DockstoreService } from '../../shared/dockstore.service'; -import { DateStubService, DockstoreStubService, SearchStubService } from '../../test/service-stubs'; -import { SearchService } from '../state/search.service'; -import { SearchWorkflowTableComponent } from './search-workflow-table.component'; - -describe('SearchWorkflowTableComponent', () => { - let component: SearchWorkflowTableComponent; - let fixture: ComponentFixture; - - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - schemas: [NO_ERRORS_SCHEMA], - imports: [BrowserAnimationsModule, RouterTestingModule, SearchWorkflowTableComponent], - providers: [ - { provide: DockstoreService, useClass: DockstoreStubService }, - { provide: DateService, useClass: DateStubService }, - { provide: SearchService, useClass: SearchStubService }, - ], - }).compileComponents(); - }) - ); - - beforeEach(() => { - fixture = TestBed.createComponent(SearchWorkflowTableComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/search/search-workflow-table/search-workflow-table.component.ts b/src/app/search/search-workflow-table/search-workflow-table.component.ts deleted file mode 100644 index ab2909a87..000000000 --- a/src/app/search/search-workflow-table/search-workflow-table.component.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2019 OICR - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { Component, OnInit } from '@angular/core'; -import { MatLegacyTableDataSource as MatTableDataSource, MatLegacyTableModule } from '@angular/material/legacy-table'; -import { DateService } from '../../shared/date.service'; -import { Workflow } from '../../shared/openapi'; -import { SearchEntryTable } from '../search-entry-table'; -import { SearchQuery, SearchResult } from '../state/search.query'; -import { SearchService } from '../state/search.service'; -import { JoinWithEllipsesPipe } from 'app/search/join-with-ellipses.pipe'; -import { SearchAuthorsHtmlPipe } from 'app/search/search-authors-html.pipe'; -import { DescriptorLanguagePipe } from '../../shared/entry/descriptor-language.pipe'; -import { MatLegacyPaginatorModule } from '@angular/material/legacy-paginator'; -import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { MatIconModule } from '@angular/material/icon'; -import { ExtendedModule } from '@ngbracket/ngx-layout/extended'; -import { AiBubbleComponent } from '../../shared/ai-bubble/ai-bubble.component'; -import { RouterLink } from '@angular/router'; -import { MatLegacyTooltipModule } from '@angular/material/legacy-tooltip'; -import { MatSortModule } from '@angular/material/sort'; -import { MatLegacyProgressBarModule } from '@angular/material/legacy-progress-bar'; -import { NgIf, NgFor, KeyValuePipe, DatePipe } from '@angular/common'; -import TopicSelectionEnum = Workflow.TopicSelectionEnum; -import { MatLegacyCardModule } from '@angular/material/legacy-card'; -import { FlexLayoutModule } from '@ngbracket/ngx-layout'; -import { DoiBadgeComponent } from 'app/shared/entry/doi/doi-badge/doi-badge.component'; -import { MatLegacyFormFieldModule } from '@angular/material/legacy-form-field'; -import { MatLegacyOptionModule } from '@angular/material/legacy-core'; -import { MatLegacySelectModule } from '@angular/material/legacy-select'; -import { EntryToDisplayNamePipe } from 'app/shared/entry-to-display-name.pipe'; -import { RouterLinkPipe } from 'app/entry/router-link.pipe'; -import { MatDividerModule } from '@angular/material/divider'; - -/** - * this component refers to search page not workflow listing search - */ - -@Component({ - selector: 'app-search-workflow-table', - templateUrl: './search-workflow-table.component.html', - styleUrls: ['../../shared/styles/entry-table.scss', './search-workflow-table.component.scss'], - standalone: true, - imports: [ - NgIf, - MatLegacyProgressBarModule, - MatLegacyTableModule, - MatSortModule, - MatLegacyTooltipModule, - RouterLink, - AiBubbleComponent, - ExtendedModule, - MatIconModule, - FontAwesomeModule, - NgFor, - MatLegacyPaginatorModule, - KeyValuePipe, - DescriptorLanguagePipe, - SearchAuthorsHtmlPipe, - JoinWithEllipsesPipe, - MatLegacyCardModule, - FlexLayoutModule, - DatePipe, - DoiBadgeComponent, - MatLegacyFormFieldModule, - MatLegacyOptionModule, - MatLegacySelectModule, - EntryToDisplayNamePipe, - RouterLinkPipe, - MatDividerModule, - ], -}) -export class SearchWorkflowTableComponent extends SearchEntryTable implements OnInit { - public dataSource: MatTableDataSource>; - constructor(dateService: DateService, searchQuery: SearchQuery, searchService: SearchService) { - super(dateService, searchQuery, searchService); - } - - protected readonly TopicSelectionEnum = TopicSelectionEnum; -} diff --git a/src/app/search/search.component.html b/src/app/search/search.component.html index fbf8f7871..382f76afa 100644 --- a/src/app/search/search.component.html +++ b/src/app/search/search.component.html @@ -40,21 +40,18 @@ workflow icon Workflows -
tool icon Tools -
notebook icon Notebooks -
diff --git a/src/app/search/search.component.spec.ts b/src/app/search/search.component.spec.ts index b19207df3..3dc9529db 100644 --- a/src/app/search/search.component.spec.ts +++ b/src/app/search/search.component.spec.ts @@ -22,7 +22,7 @@ import { MatLegacySnackBarModule as MatSnackBarModule } from '@angular/material/ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { ExtendedGA4GHService } from 'app/shared/openapi'; +import { EntryType, ExtendedGA4GHService } from 'app/shared/openapi'; import { of } from 'rxjs'; import { DateService } from '../shared/date.service'; import { ProviderService } from '../shared/provider.service'; @@ -107,9 +107,8 @@ describe('SearchComponent', () => { workflowhit: null, toolhit: null, notebookhit: null, - showToolTagCloud: false, - showWorkflowTagCloud: false, - showNotebookTagCloud: false, + showTagCloud: false, + currentEntryType: EntryType.WORKFLOW, searchText: '', filterKeys: [], autocompleteTerms: [], diff --git a/src/app/search/state/search.query.ts b/src/app/search/state/search.query.ts index 7d4b594c1..03fac92e3 100644 --- a/src/app/search/state/search.query.ts +++ b/src/app/search/state/search.query.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { Query } from '@datorama/akita'; import { combineLatest, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { AppTool, DockstoreTool, Workflow, Notebook } from '../../shared/openapi'; +import { AppTool, DockstoreTool, Workflow, Notebook, EntryType } from '../../shared/openapi'; import { SearchState, SearchStore } from './search.store'; import { parseTerms } from '../helpers'; @@ -34,9 +34,8 @@ export class SearchQuery extends Query { ); public searchText$: Observable = this.select((state) => state.searchText); public basicSearchText$: Observable = this.searchText$.pipe(map((searchText) => this.joinComma(parseTerms(searchText)))); - public showToolTagCloud$: Observable = this.select((state) => state.showToolTagCloud); - public showWorkflowTagCloud$: Observable = this.select((state) => state.showWorkflowTagCloud); - public showNotebookTagCloud$: Observable = this.select((state) => state.showNotebookTagCloud); + public showTagCloud$: Observable = this.select((state) => state.showTagCloud); + public currentEntryType$: Observable = this.select((state) => state.currentEntryType); public filterKeys$: Observable> = this.select((state) => state.filterKeys); public autoCompleteTerms$: Observable> = this.select((state) => state.autocompleteTerms); public hasAutoCompleteTerms$: Observable = this.autoCompleteTerms$.pipe(map((terms) => terms.length > 0)); diff --git a/src/app/search/state/search.service.spec.ts b/src/app/search/state/search.service.spec.ts index 8a77eb683..5c28cf8fa 100644 --- a/src/app/search/state/search.service.spec.ts +++ b/src/app/search/state/search.service.spec.ts @@ -20,7 +20,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { first } from 'rxjs/operators'; import { ImageProviderService } from '../../shared/image-provider.service'; import { ProviderService } from '../../shared/provider.service'; -import { Workflow } from '../../shared/openapi'; +import { EntryType, Workflow } from '../../shared/openapi'; import { elasticSearchResponse } from '../../test/mocked-objects'; import { ProviderStubService } from '../../test/service-stubs'; import { Hit, SearchService } from './search.service'; @@ -148,16 +148,16 @@ describe('SearchService', () => { const c: Workflow = { ...a, authors: [], full_workflow_path: null, descriptorType: Workflow.DescriptorTypeEnum.WDL }; c['all_authors'] = c['authors']; - expect(searchService.compareAttributes(a, b, 'all_authors', 'asc', 'workflow')).toEqual(-1); - expect(searchService.compareAttributes(a, b, 'all_authors', 'desc', 'workflow')).toEqual(1); - expect(searchService.compareAttributes(b, c, 'all_authors', 'asc', 'workflow')).toEqual(-1); - expect(searchService.compareAttributes(b, c, 'all_authors', 'desc', 'workflow')).toEqual(1); - expect(searchService.compareAttributes(a, c, 'descriptorType', 'asc', 'workflow')).toEqual(-1); - expect(searchService.compareAttributes(a, b, 'descriptorType', 'desc', 'workflow')).toEqual(-0); - expect(searchService.compareAttributes(a, b, 'starredUsers', 'asc', 'workflow')).toEqual(-1); - expect(searchService.compareAttributes(a, b, 'name', 'asc', 'workflow')).toEqual(-1); - expect(searchService.compareAttributes(a, b, 'name', 'desc', 'workflow')).toEqual(1); - expect(searchService.compareAttributes(b, c, 'name', 'asc', 'workflow')).toEqual(-1); - expect(searchService.compareAttributes(b, c, 'name', 'desc', 'workflow')).toEqual(-1); + expect(searchService.compareAttributes(a, b, 'all_authors', 'asc', EntryType.WORKFLOW)).toEqual(-1); + expect(searchService.compareAttributes(a, b, 'all_authors', 'desc', EntryType.WORKFLOW)).toEqual(1); + expect(searchService.compareAttributes(b, c, 'all_authors', 'asc', EntryType.WORKFLOW)).toEqual(-1); + expect(searchService.compareAttributes(b, c, 'all_authors', 'desc', EntryType.WORKFLOW)).toEqual(1); + expect(searchService.compareAttributes(a, c, 'descriptorType', 'asc', EntryType.WORKFLOW)).toEqual(-1); + expect(searchService.compareAttributes(a, b, 'descriptorType', 'desc', EntryType.WORKFLOW)).toEqual(-0); + expect(searchService.compareAttributes(a, b, 'starredUsers', 'asc', EntryType.WORKFLOW)).toEqual(-1); + expect(searchService.compareAttributes(a, b, 'name', 'asc', EntryType.WORKFLOW)).toEqual(-1); + expect(searchService.compareAttributes(a, b, 'name', 'desc', EntryType.WORKFLOW)).toEqual(1); + expect(searchService.compareAttributes(b, c, 'name', 'asc', EntryType.WORKFLOW)).toEqual(-1); + expect(searchService.compareAttributes(b, c, 'name', 'desc', EntryType.WORKFLOW)).toEqual(-1); })); }); diff --git a/src/app/search/state/search.service.ts b/src/app/search/state/search.service.ts index 840dc0e96..a254353dd 100644 --- a/src/app/search/state/search.service.ts +++ b/src/app/search/state/search.service.ts @@ -322,32 +322,14 @@ export class SearchService { this.setSearchText(suggestTerm); } - setShowTagCloud(entryType: 'tool' | 'workflow' | 'notebook') { - if (entryType === 'tool') { - const showTagCloud: boolean = this.searchQuery.getValue().showToolTagCloud; - this.searchStore.update((state) => { - return { - ...state, - showToolTagCloud: !showTagCloud, - }; - }); - } else if (entryType === 'workflow') { - const showTagCloud: boolean = this.searchQuery.getValue().showWorkflowTagCloud; - this.searchStore.update((state) => { - return { - ...state, - showWorkflowTagCloud: !showTagCloud, - }; - }); - } else { - const showTagCloud: boolean = this.searchQuery.getValue().showNotebookTagCloud; - this.searchStore.update((state) => { - return { - ...state, - showNotebookTagCloud: !showTagCloud, - }; - }); - } + setShowTagCloud(entryType: EntryType) { + this.searchStore.update((state) => { + return { + ...state, + showTagCloud: !(this.searchQuery.getValue().showTagCloud && this.searchQuery.getValue().currentEntryType === entryType), + currentEntryType: entryType, + }; + }); } /** diff --git a/src/app/search/state/search.store.ts b/src/app/search/state/search.store.ts index 48894ff72..f0965e32f 100644 --- a/src/app/search/state/search.store.ts +++ b/src/app/search/state/search.store.ts @@ -16,15 +16,15 @@ import { Injectable } from '@angular/core'; import { Store, StoreConfig } from '@datorama/akita'; import { AdvancedSearchObject, initialAdvancedSearchObject } from 'app/shared/models/AdvancedSearchObject'; +import { EntryType } from 'app/shared/openapi'; export interface SearchState { shortUrl: string; workflowhit: any; toolhit: any; notebookhit: any; - showToolTagCloud: boolean; - showWorkflowTagCloud: boolean; - showNotebookTagCloud: boolean; + showTagCloud: boolean; + currentEntryType: EntryType; searchText: string; filterKeys: Array; autocompleteTerms: Array; @@ -42,9 +42,8 @@ export function createInitialState(): SearchState { workflowhit: null, toolhit: null, notebookhit: null, - showToolTagCloud: false, - showWorkflowTagCloud: false, - showNotebookTagCloud: false, + showTagCloud: false, + currentEntryType: EntryType.WORKFLOW, searchText: '', filterKeys: [], autocompleteTerms: [], From 4327835714f360a6aed8d49c6ec1806bd4606dd2 Mon Sep 17 00:00:00 2001 From: Kathy Tran Date: Thu, 16 Jan 2025 10:06:52 -0500 Subject: [PATCH 04/10] Add entryTypeMetadata to search hits in tests --- .../e2e/immutableDatabaseTests/searchTable.ts | 40 +++++++++++++++ cypress/fixtures/searchTableResponse.json | 50 +++++++++++++++++++ src/app/test/mocked-objects.ts | 20 ++++++++ 3 files changed, 110 insertions(+) diff --git a/cypress/e2e/immutableDatabaseTests/searchTable.ts b/cypress/e2e/immutableDatabaseTests/searchTable.ts index e71196d1e..cecbaaeb8 100644 --- a/cypress/e2e/immutableDatabaseTests/searchTable.ts +++ b/cypress/e2e/immutableDatabaseTests/searchTable.ts @@ -37,6 +37,16 @@ describe('Dockstore tool/workflow search table', () => { _id: '52', _score: 1.0, _source: { + entryTypeMetadata: { + termPlural: 'tools', + sitePath: 'containers', + trsSupported: true, + trsPrefix: '', + term: 'tool', + searchSupported: true, + type: 'TOOL', + searchEntryType: 'tools', + }, tool_maintainer_email: '', aliases: {}, default_dockerfile_path: '/Dockerfile', @@ -102,6 +112,16 @@ describe('Dockstore tool/workflow search table', () => { _id: '5', _score: 1.0, _source: { + entryTypeMetadata: { + termPlural: 'tools', + sitePath: 'containers', + trsSupported: true, + trsPrefix: '', + term: 'tool', + searchSupported: true, + type: 'TOOL', + searchEntryType: 'tools', + }, tool_maintainer_email: '', aliases: {}, default_dockerfile_path: '/Dockerfile', @@ -190,6 +210,16 @@ describe('Dockstore tool/workflow search table', () => { _id: '4', _score: 1.0, _source: { + entryTypeMetadata: { + termPlural: 'tools', + sitePath: 'containers', + trsSupported: true, + trsPrefix: '', + term: 'tool', + searchSupported: true, + type: 'TOOL', + searchEntryType: 'tools', + }, tool_maintainer_email: '', aliases: {}, default_dockerfile_path: '/Dockerfile', @@ -278,6 +308,16 @@ describe('Dockstore tool/workflow search table', () => { _id: '11', _score: 1.0, _source: { + entryTypeMetadata: { + termPlural: 'workflows', + sitePath: 'workflows', + trsSupported: true, + trsPrefix: '#workflow/', + term: 'workflow', + searchSupported: true, + type: 'WORKFLOW', + searchEntryType: 'workflows', + }, aliases: {}, is_published: true, last_modified_date: null, diff --git a/cypress/fixtures/searchTableResponse.json b/cypress/fixtures/searchTableResponse.json index 21c4f2975..6c9439216 100644 --- a/cypress/fixtures/searchTableResponse.json +++ b/cypress/fixtures/searchTableResponse.json @@ -17,6 +17,16 @@ "_id": "52", "_score": 1.0, "_source": { + "entryTypeMetadata": { + "termPlural": "tools", + "sitePath": "containers", + "trsSupported": true, + "trsPrefix": "", + "term": "tool", + "searchSupported": true, + "type": "TOOL", + "searchEntryType": "tools" + }, "tool_maintainer_email": "", "aliases": {}, "default_dockerfile_path": "/Dockerfile", @@ -89,6 +99,16 @@ "_id": "5", "_score": 1.0, "_source": { + "entryTypeMetadata": { + "termPlural": "tools", + "sitePath": "containers", + "trsSupported": true, + "trsPrefix": "", + "term": "tool", + "searchSupported": true, + "type": "TOOL", + "searchEntryType": "tools" + }, "tool_maintainer_email": "", "aliases": {}, "default_dockerfile_path": "/Dockerfile", @@ -179,6 +199,16 @@ "_id": "4", "_score": 1.0, "_source": { + "entryTypeMetadata": { + "termPlural": "tools", + "sitePath": "containers", + "trsSupported": true, + "trsPrefix": "", + "term": "tool", + "searchSupported": true, + "type": "TOOL", + "searchEntryType": "tools" + }, "tool_maintainer_email": "", "aliases": {}, "default_dockerfile_path": "/Dockerfile", @@ -270,6 +300,16 @@ "_id": "11", "_score": 1.0, "_source": { + "entryTypeMetadata": { + "termPlural": "workflows", + "sitePath": "workflows", + "trsSupported": true, + "trsPrefix": "#workflow/", + "term": "workflow", + "searchSupported": true, + "type": "WORKFLOW", + "searchEntryType": "workflows" + }, "aliases": {}, "is_published": true, "last_modified_date": null, @@ -333,6 +373,16 @@ "_id": "61", "_score": 1.0, "_source": { + "entryTypeMetadata": { + "termPlural": "notebooks", + "sitePath": "notebooks", + "trsSupported": true, + "trsPrefix": "#notebook/", + "term": "notebook", + "searchSupported": true, + "type": "NOTEBOOK", + "searchEntryType": "notebooks" + }, "aliases": {}, "is_published": true, "last_modified_date": null, diff --git a/src/app/test/mocked-objects.ts b/src/app/test/mocked-objects.ts index 9ddc2a1a8..1f354e3c9 100644 --- a/src/app/test/mocked-objects.ts +++ b/src/app/test/mocked-objects.ts @@ -639,6 +639,16 @@ export const elasticSearchResponse: Hit[] = [ _id: '2313', _score: 1, _source: { + entryTypeMetadata: { + termPlural: 'tools', + sitePath: 'containers', + trsSupported: true, + trsPrefix: '', + term: 'tool', + searchSupported: true, + type: 'TOOL', + searchEntryType: 'tools', + }, tool_maintainer_email: '', aliases: {}, default_dockerfile_path: '/delly_docker/Dockerfile', @@ -722,6 +732,16 @@ export const elasticSearchResponse: Hit[] = [ _id: '2210', _score: 1, _source: { + entryTypeMetadata: { + termPlural: 'workflows', + sitePath: 'workflows', + trsSupported: true, + trsPrefix: '#workflow/', + term: 'workflow', + searchSupported: true, + type: 'WORKFLOW', + searchEntryType: 'workflows', + }, aliases: {}, is_published: true, last_modified_date: null, From fcd779f540b4f71e5be949f147bc4e6bb74207df Mon Sep 17 00:00:00 2001 From: Kathy Tran Date: Thu, 16 Jan 2025 11:40:16 -0500 Subject: [PATCH 05/10] Fix tests --- .../e2e/immutableDatabaseTests/searchTable.ts | 19 ++++++++++--------- .../search/search-entry-table.component.html | 13 ++++++++----- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/cypress/e2e/immutableDatabaseTests/searchTable.ts b/cypress/e2e/immutableDatabaseTests/searchTable.ts index cecbaaeb8..3c2f54009 100644 --- a/cypress/e2e/immutableDatabaseTests/searchTable.ts +++ b/cypress/e2e/immutableDatabaseTests/searchTable.ts @@ -378,7 +378,7 @@ describe('Dockstore tool/workflow search table', () => { if (type === 'workflow') { goToTab('Workflows'); } - cy.get('.mat-icon.star-icon').should('not.exist'); + cy.get('[data-cy=starredUsers]').should('not.exist'); // cy.visit(url); // cy.get('#starringButton') // .click(); @@ -394,7 +394,7 @@ describe('Dockstore tool/workflow search table', () => { if (type === 'workflow') { goToTab('Workflows'); } - cy.get('.mat-icon.star-icon').should('exist'); + cy.get('[data-cy=starredUsers]').should('exist'); // cy.visit(url); // cy.get('#starringButton') // .click(); @@ -422,11 +422,15 @@ describe('search table items per page', () => { it('tool items per page', () => { cy.visit('/search'); - cy.get('#mat-select-0 ').click(); - cy.get('#mat-option-1 ').click(); + // Modify the number of items per page + cy.get('[data-cy=search-entry-table-paginator]').contains(10).should('be.visible').click(); + cy.get('mat-option').contains(20).click(); + cy.get('[data-cy=search-entry-table-paginator]').contains(20); + // Click an entry then go back to the search page cy.contains('A/l').click(); - cy.get('.flex-toolbar ').contains(' Search ').click(); - cy.get('.mat-select-value-text ').contains('20'); + cy.get('a').contains('Search').click(); + // The number of items should remain the same + cy.get('[data-cy=search-entry-table-paginator]').contains(20); }); it('tool items per page after advanced search', () => { @@ -494,9 +498,6 @@ describe('check search table and tabs for notebooks', () => { // Select notebooks tab goToTab('Notebooks'); cy.url().should('contain', 'notebooks'); - // Check that the notebooks variations are in the table header - cy.get('.mat-header-cell').contains('Language'); - cy.get('.mat-header-cell').contains('Format'); // Check that the notebooks variations are in the table body cy.get('.mat-cell').contains('jupyter', { matchCase: false }); cy.get('.mat-cell').contains('python', { matchCase: false }); diff --git a/src/app/search/search-entry-table.component.html b/src/app/search/search-entry-table.component.html index 30adbd6ba..e6a39a0eb 100644 --- a/src/app/search/search-entry-table.component.html +++ b/src/app/search/search-entry-table.component.html @@ -56,7 +56,7 @@ >{{ entry?.source | entryToDisplayName: true }}
-
+
star_rate {{ !entry?.source.starredUsers || entry?.source.starredUsers.length === 0 ? '' : entry?.source.starredUsers.length @@ -87,18 +87,21 @@
-
- + Last updated {{ entry?.source.last_modified_date ? (entry?.source.last_modified_date | date) : 'n/a' }} {{ entry?.source.descriptorType | descriptorLanguage }} - - + + {{ language ? language : '' }} + {{ + entry?.source.descriptorTypeSubclass + }}
Date: Thu, 16 Jan 2025 13:09:49 -0500 Subject: [PATCH 06/10] Fix test --- src/app/search/search-entry-table.component.html | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/app/search/search-entry-table.component.html b/src/app/search/search-entry-table.component.html index e6a39a0eb..40575732d 100644 --- a/src/app/search/search-entry-table.component.html +++ b/src/app/search/search-entry-table.component.html @@ -56,11 +56,9 @@ >{{ entry?.source | entryToDisplayName: true }}
-
- star_rate - {{ - !entry?.source.starredUsers || entry?.source.starredUsers.length === 0 ? '' : entry?.source.starredUsers.length - }} +
+ star_rate + {{ entry?.source.starredUsers.length }}
From da5dab813d9ebd8d808deae840368ddfaf88f345 Mon Sep 17 00:00:00 2001 From: Kathy Tran Date: Thu, 16 Jan 2025 13:26:11 -0500 Subject: [PATCH 07/10] Fix smoke tests --- .../smokeTests/sharedTests/basic-enduser.ts | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/cypress/e2e/smokeTests/sharedTests/basic-enduser.ts b/cypress/e2e/smokeTests/sharedTests/basic-enduser.ts index cdd5a217a..b797a4b75 100644 --- a/cypress/e2e/smokeTests/sharedTests/basic-enduser.ts +++ b/cypress/e2e/smokeTests/sharedTests/basic-enduser.ts @@ -11,12 +11,11 @@ describe('run stochastic smoke test', () => { function testEntry(tab: string) { function goToRandomEntry() { cy.visit('/search'); - cy.get('[data-cy=workflowColumn] a'); + cy.get('[data-cy=entryColumn] a'); goToTab(tab); - const linkName = getLinkName(tab); // select a random entry on the first page and navigate to it let chosen_index = 0; - cy.get('[data-cy=' + linkName + ']') + cy.get('[data-cy=entryColumn]') .then(($list) => { chosen_index = Math.floor(Math.random() * $list.length); }) @@ -55,19 +54,6 @@ function testEntry(tab: string) { }); } -function getLinkName(tab: string): string { - switch (tab) { - case 'Tools': - return 'toolNames'; - case 'Workflows': - return 'workflowColumn'; - case 'Notebooks': - return 'notebookColumn'; - default: - throw new Error('unknown tab'); - } -} - function isStagingOrProd() { const baseUrl = Cypress.config('baseUrl'); return baseUrl === 'https://staging.dockstore.org' || baseUrl === 'https://dockstore.org'; @@ -104,7 +90,7 @@ describe('Test logged out home page', () => { describe('Test search page functionality', () => { it('displays tools', () => { cy.visit('/search'); - cy.get('[data-cy=workflowColumn]').should('have.length.of.at.least', 1); + cy.get('[data-cy=entryColumn]').should('have.length.of.at.least', 1); }); it('has working tag cloud', () => { cy.visit('/search'); @@ -123,7 +109,7 @@ describe('Test search page functionality', () => { cy.visit('/search'); cy.wait(2500); // Wait less than ideal, facets keep getting rerendered is the problem cy.contains('mat-checkbox', 'Nextflow').click(); - cy.get('[data-cy=workflowColumn] a'); + cy.get('[data-cy=entryColumn] a'); cy.wait(2500); // Wait less than ideal, facets keep getting rerendered is the problem cy.contains('mat-checkbox', 'Nextflow'); // wait for the checkbox to reappear, indicating the filtering is almost complete cy.get('[data-cy=descriptorType]').each(($el, index, $list) => { @@ -139,7 +125,7 @@ describe('Test search page functionality', () => { cy.visit('/search'); cy.contains('mat-checkbox', /^[ ]*verified/).click(); cy.url().should('contain', 'verified=1'); - cy.get('[data-cy=workflowColumn] a'); + cy.get('[data-cy=entryColumn] a'); cy.contains('mat-checkbox', /^[ ]*verified/); }); }); @@ -148,11 +134,11 @@ describe('Test workflow page functionality', () => { it('find a WDL workflow', () => { cy.visit('/search'); cy.contains('.mat-tab-label', 'Workflows'); - cy.get('[data-cy=workflowColumn]').should('have.length.of.at.least', 1); + cy.get('[data-cy=entryColumn]').should('have.length.of.at.least', 1); // click twice to sort by descriptor type descending so WDL is at the top cy.get('[data-cy=descriptorTypeHeader]').click().click(); - cy.get('[data-cy=workflowColumn] a').first().click(); + cy.get('[data-cy=entryColumn] a').first().click(); }); }); From f2c5d327eae4135777e808ed2b7fe4b2a290d6cd Mon Sep 17 00:00:00 2001 From: Kathy Tran Date: Thu, 16 Jan 2025 13:51:16 -0500 Subject: [PATCH 08/10] Fix smoke test again --- .../smokeTests/sharedTests/basic-enduser.ts | 4 +-- .../search/search-entry-table.component.html | 25 ++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/cypress/e2e/smokeTests/sharedTests/basic-enduser.ts b/cypress/e2e/smokeTests/sharedTests/basic-enduser.ts index b797a4b75..5cd4dbc4f 100644 --- a/cypress/e2e/smokeTests/sharedTests/basic-enduser.ts +++ b/cypress/e2e/smokeTests/sharedTests/basic-enduser.ts @@ -136,8 +136,8 @@ describe('Test workflow page functionality', () => { cy.contains('.mat-tab-label', 'Workflows'); cy.get('[data-cy=entryColumn]').should('have.length.of.at.least', 1); - // click twice to sort by descriptor type descending so WDL is at the top - cy.get('[data-cy=descriptorTypeHeader]').click().click(); + // Use facet to find WDL workflow + cy.contains('mat-checkbox', 'WDL').click(); cy.get('[data-cy=entryColumn] a').first().click(); }); }); diff --git a/src/app/search/search-entry-table.component.html b/src/app/search/search-entry-table.component.html index 40575732d..7fb8ef004 100644 --- a/src/app/search/search-entry-table.component.html +++ b/src/app/search/search-entry-table.component.html @@ -89,17 +89,24 @@ Last updated {{ entry?.source.last_modified_date ? (entry?.source.last_modified_date | date) : 'n/a' }} - {{ - entry?.source.descriptorType | descriptorLanguage - }} - - - {{ language ? language : '' }} + + + {{ + entry?.source.descriptorType | descriptorLanguage + }} + + + {{ language ? language : '' }} + - {{ - entry?.source.descriptorTypeSubclass - }} + + {{ entry?.source.descriptorTypeSubclass }}
Date: Thu, 16 Jan 2025 14:49:29 -0500 Subject: [PATCH 09/10] Add categories --- src/app/search/query-builder.service.ts | 1 + .../search/search-entry-table.component.html | 20 ++++++++++++------- .../search/search-entry-table.component.ts | 5 ++++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/app/search/query-builder.service.ts b/src/app/search/query-builder.service.ts index de98fd40e..a11ef4b4d 100644 --- a/src/app/search/query-builder.service.ts +++ b/src/app/search/query-builder.service.ts @@ -81,6 +81,7 @@ export class QueryBuilderService { return body.rawOption('_source', [ 'all_authors', 'approvedAITopic', + 'categories', 'descriptorType', 'descriptorTypeSubclass', 'entryTypeMetadata', diff --git a/src/app/search/search-entry-table.component.html b/src/app/search/search-entry-table.component.html index 7fb8ef004..342d31b2c 100644 --- a/src/app/search/search-entry-table.component.html +++ b/src/app/search/search-entry-table.component.html @@ -85,17 +85,23 @@
-
- + Last updated {{ entry?.source.last_modified_date ? (entry?.source.last_modified_date | date) : 'n/a' }} - - - {{ + + + + + {{ entry?.source.descriptorType | descriptorLanguage }} - - + + {{ language ? language : '' }} diff --git a/src/app/search/search-entry-table.component.ts b/src/app/search/search-entry-table.component.ts index 7c1d03186..84085511a 100644 --- a/src/app/search/search-entry-table.component.ts +++ b/src/app/search/search-entry-table.component.ts @@ -51,6 +51,8 @@ import { CloudData, CloudOptions, TagCloudComponent } from 'angular-tag-cloud-mo import { faPlus, faMinus } from '@fortawesome/free-solid-svg-icons'; import { QueryBuilderService } from './query-builder.service'; import { MatLegacyButtonModule } from '@angular/material/legacy-button'; +import { CategoryButtonComponent } from 'app/categories/button/category-button.component'; +import { MatLegacyChipsModule } from '@angular/material/legacy-chips'; export interface SortOption { label: string; @@ -97,6 +99,8 @@ export interface SortOption { AsyncPipe, MatLegacyButtonModule, LowerCasePipe, + CategoryButtonComponent, + MatLegacyChipsModule, ], }) export class SearchEntryTableComponent extends Base implements OnInit { @@ -241,7 +245,6 @@ export class SearchEntryTableComponent extends Base implements OnInit { let weight = 10; let count = 0; if (hits && hits.aggregations && hits.aggregations.tagcloud) { - console.log('createToolTagCloud'); hits.aggregations.tagcloud.buckets.forEach((tag) => { const theTag = { text: tag.key, From 602d16049ae5585fecfed704321a54bccef470c1 Mon Sep 17 00:00:00 2001 From: Kathy Tran Date: Thu, 16 Jan 2025 15:03:34 -0500 Subject: [PATCH 10/10] Match collection card to search card style --- src/app/organizations/collection/collection.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/organizations/collection/collection.component.html b/src/app/organizations/collection/collection.component.html index bd6039202..f2b74e79c 100644 --- a/src/app/organizations/collection/collection.component.html +++ b/src/app/organizations/collection/collection.component.html @@ -133,11 +133,11 @@
-
- +
{{ entry.topic }} +