diff --git a/cypress/e2e/group2/myworkflows.ts b/cypress/e2e/group2/myworkflows.ts index 7c41e946ec..9d97fe7953 100644 --- a/cypress/e2e/group2/myworkflows.ts +++ b/cypress/e2e/group2/myworkflows.ts @@ -180,6 +180,7 @@ describe('Dockstore my workflows', () => { cy.contains('Close').click(); }); it('Should contain the extended properties and be able to edit the info tab', () => { + cy.intercept('PUT', 'api/workflows/*').as('updateWorkflow'); // The seemingly unnecessary visits are due to a detached-from-dom error even using cy.get().click(); cy.visit('/my-workflows/github.com/A/l'); cy.contains('github.com'); @@ -191,6 +192,7 @@ describe('Dockstore my workflows', () => { const workflowPathInput = '[data-cy=workflowPathInput]'; cy.get(workflowPathInput).clear().type('/Dockstore2.cwl'); cy.contains('button', ' Save ').click(); + cy.wait('@updateWorkflow'); cy.visit('/my-workflows/github.com/A/g'); cy.contains('/Dockstore2.cwl'); // Change the file path back @@ -198,6 +200,7 @@ describe('Dockstore my workflows', () => { const dockstoreCwlPath = '/Dockstore.cwl'; cy.get(workflowPathInput).clear().type(dockstoreCwlPath); cy.contains('button', ' Save ').click(); + cy.wait('@updateWorkflow'); cy.visit('/my-workflows/github.com/A/g'); const workflowPathSpan = '[data-cy=workflowPathSpan]'; cy.get(workflowPathSpan).contains(dockstoreCwlPath); @@ -215,25 +218,55 @@ describe('Dockstore my workflows', () => { // Topic Editing const privateEntryURI = '/my-workflows/github.com/A/l'; cy.visit(privateEntryURI); + // Add an AI topic for testing + invokeSql("update workflow set topicai = 'test AI topic sentence' where id = 11"); + // Modify the manual topic, but don't save it cy.get('[data-cy=topicEditButton]').click(); - cy.get('[data-cy=topicInput]').clear().type('badTopic'); + cy.get('[data-cy=topicInput]').clear(); // Unsafe to chain clear() + cy.get('[data-cy=topicInput]').type('badTopic'); cy.get('[data-cy=topicCancelButton]').click(); - cy.contains('badTopic').should('not.exist'); + cy.get('[data-cy=selected-topic]').should('not.contain.text', 'badTopic'); + // Modify the manual topic and save it cy.get('[data-cy=topicEditButton]').click(); - cy.get('[data-cy=topicInput]').clear().type('goodTopic'); + cy.get('[data-cy=topicInput]').clear(); // Unsafe to chain clear() + cy.get('[data-cy=topicInput]').type('goodTopic'); cy.get('[data-cy=topicSaveButton]').click(); - cy.contains('goodTopic').should('exist'); + cy.wait('@updateWorkflow'); + // Check that the manual topic is saved + cy.get('[data-cy=topicEditButton]').click(); + cy.get('[data-cy=topicInput]').should('have.value', 'goodTopic'); + cy.get('[data-cy=topicCancelButton]').click(); - // Check public view - cy.visit(privateEntryURI); + // Check public view. Manual topic should not be displayed because it's not the selected topic cy.get('[data-cy=viewPublicWorkflowButton]').should('be.visible').click(); - cy.contains('goodTopic').should('not.exist'); + cy.get('[data-cy=selected-topic]').should('not.contain.text', 'goodTopic'); + // Select the manual topic and verify that it's displayed publicly cy.visit(privateEntryURI); - cy.contains('mat-radio-button', 'Manual').find('input').should('not.be.disabled').click({ force: true }); + cy.get('[data-cy=topicEditButton]').click(); + cy.get('.mat-radio-label').contains('Manual').click(); + cy.get('[data-cy=topicSaveButton]').click(); + cy.wait('@updateWorkflow'); + cy.get('[data-cy=selected-topic]').should('contain.text', 'goodTopic'); + // Topic selection bubble should be visible on private page + cy.get('[data-cy=topic-selection-bubble]').should('be.visible'); + // Topic selection bubble should not exist on public page + cy.get('[data-cy=viewPublicWorkflowButton]').should('be.visible').click(); + cy.get('[data-cy=selected-topic]').should('contain.text', 'goodTopic'); + cy.get('[data-cy=topic-selection-bubble]').should('not.exist'); + + // Select the AI topic and verify that it's displayed publicly with an AI bubble cy.visit(privateEntryURI); + cy.get('[data-cy=topicEditButton]').click(); + cy.get('.mat-radio-label').contains('AI').click(); + cy.get('[data-cy=topicSaveButton]').click(); + cy.wait('@updateWorkflow'); + cy.get('[data-cy=selected-topic]').should('contain.text', 'test AI topic sentence'); + cy.get('[data-cy=ai-bubble]').should('be.visible'); + // AI bubble should be displayed on public page too cy.get('[data-cy=viewPublicWorkflowButton]').should('be.visible').click(); - cy.contains('goodTopic').should('exist'); + cy.get('[data-cy=selected-topic]').should('contain.text', 'test AI topic sentence'); + cy.get('[data-cy=ai-bubble]').should('be.visible'); }); it('should have mode tooltip', () => { cy.visit('/my-workflows/github.com/A/g'); @@ -639,19 +672,6 @@ describe('Version Dropdown should have search capabilities', () => { cy.get('mat-option').should('not.contain', 'master'); cy.get('mat-option').should('contain', 'test').should('be.visible'); }); - it('Test AI topic sentences', () => { - cy.fixture('workflowWithTopicAI.json').then((json) => { - cy.intercept('GET', '/api/workflows/11*', { - body: json, - statusCode: 200, - }).as('request'); - }); - - cy.visit('/my-workflows'); - cy.get('[data-cy=topic-ai-selection-button]').should('be.visible'); - cy.get('[data-cy=topicAI-text]').should('contain.text', 'test AI topic sentence'); - cy.get('[data-cy=ai-bubble]').should('be.visible'); - }); }); describe('Should handle no workflows correctly', () => { resetDB(); diff --git a/cypress/e2e/group2/sharedWorkflows.ts b/cypress/e2e/group2/sharedWorkflows.ts index b84acdf2d4..ced9dc0fec 100644 --- a/cypress/e2e/group2/sharedWorkflows.ts +++ b/cypress/e2e/group2/sharedWorkflows.ts @@ -21,7 +21,7 @@ import { resetDB, setTokenUserViewPort, } from '../../support/commands'; - +import { workflowEntryTypeMetadata } from '../../../src/app/test/mocked-objects'; import { BioWorkflow } from '../../../src/app/shared/openapi/model/bioWorkflow'; describe('Shared with me workflow test from my-workflows', () => { resetDB(); @@ -82,6 +82,7 @@ describe('Shared with me workflow test from my-workflows', () => { description: undefined, descriptorType: 'WDL', email: undefined, + entryTypeMetadata: workflowEntryTypeMetadata, full_workflow_path: 'dockstore.org/user_B/' + name, gitUrl: '', has_checker: false, diff --git a/cypress/e2e/group3/mytools.ts b/cypress/e2e/group3/mytools.ts index 1924298e66..0122732223 100644 --- a/cypress/e2e/group3/mytools.ts +++ b/cypress/e2e/group3/mytools.ts @@ -79,6 +79,7 @@ describe('Dockstore my tools', () => { it('visit another page then come back', () => { // The seemingly unnecessary visits are due to a detached-from-dom error even using cy.get().click(); cy.intercept('api/containers/*?include=validations').as('getTool'); + cy.intercept('PUT', 'api/containers/*').as('updateTool'); cy.visit('/my-tools'); cy.wait('@getTool'); selectUnpublishedTab('A2'); @@ -96,6 +97,7 @@ describe('Dockstore my tools', () => { cy.contains('button', ' Edit ').click(); cy.get('input').first().should('be.visible').clear().type('/thing/Dockerfile'); cy.contains('button', ' Save ').click(); + cy.wait('@updateTool'); cy.visit('/my-tools/quay.io/A2/b1'); cy.contains('/thing/Dockerfile'); // Change the dockerfile path back @@ -105,28 +107,38 @@ describe('Dockstore my tools', () => { cy.visit('/my-tools/quay.io/A2/b1'); cy.contains('/Dockerfile'); - // // Topic Editing + // Topic Editing let privateEntryURI = '/my-tools/github.com/A2/a'; cy.visit(privateEntryURI); + // Modify the manual topic, but don't save it cy.get('[data-cy=topicEditButton]').click(); - cy.get('[data-cy=topicInput]').clear().type('badTopic'); + cy.get('[data-cy=topicInput]').clear(); // Unsafe to chain commands after clear() + cy.get('[data-cy=topicInput]').type('badTopic'); cy.get('[data-cy=topicCancelButton]').click(); - cy.contains('badTopic').should('not.exist'); + cy.get('[data-cy=selected-topic]').should('not.contain.text', 'badTopic'); + // Modify the manual topic and save it cy.get('[data-cy=topicEditButton]').click(); - cy.get('[data-cy=topicInput]').clear().type('goodTopic'); + cy.get('[data-cy=topicInput]').clear(); // Unsafe to chain commands after clear() + cy.get('[data-cy=topicInput]').type('goodTopic'); + cy.get('[data-cy=topicSaveButton]').click(); + cy.wait('@updateTool'); + // Check that the manual topic is saved cy.get('[data-cy=topicEditButton]').click(); - cy.contains('goodTopic').should('exist'); + cy.get('[data-cy=topicInput]').should('have.value', 'goodTopic'); + cy.get('[data-cy=topicCancelButton]').click(); - // Check public view - cy.visit(privateEntryURI); + // Check public view. Manual topic should not be displayed because it's not the selected topic cy.get('[data-cy=viewPublicToolButton]').should('be.visible').click(); - cy.contains('goodTopic').should('not.exist'); + cy.get('[data-cy=selected-topic]').should('not.contain.text', 'goodTopic'); + // Select the manual topic and verify that it's displayed publicly cy.visit(privateEntryURI); + cy.get('[data-cy=topicEditButton]').click(); cy.get('.mat-radio-label').contains('Manual').click(); - cy.visit(privateEntryURI); + cy.get('[data-cy=topicSaveButton]').click(); + cy.wait('@updateTool'); cy.get('[data-cy=viewPublicToolButton]').should('be.visible').click(); - cy.contains('goodTopic').should('exist'); + cy.get('[data-cy=selected-topic]').should('contain.text', 'goodTopic'); }); it('should be able to add labels', () => { cy.contains('quay.io/A2/a:latest'); diff --git a/cypress/fixtures/workflowWithTopicAI.json b/cypress/fixtures/workflowWithTopicAI.json deleted file mode 100644 index 7023a81b02..0000000000 --- a/cypress/fixtures/workflowWithTopicAI.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "type": "BioWorkflow", - "descriptorType": "CWL", - "aliases": {}, - "author": null, - "checker_id": null, - "conceptDoi": null, - "dbCreateDate": 1465419996000, - "dbUpdateDate": 1465419996000, - "defaultTestParameterFilePath": "/test.json", - "defaultVersion": null, - "description": null, - "descriptorTypeSubclass": "NOT_APPLICABLE", - "email": null, - "full_workflow_path": "github.com/A/l", - "gitUrl": "git@github.com:A/l.git", - "has_checker": false, - "id": 11, - "input_file_formats": [], - "isChecker": false, - "is_published": true, - "labels": [], - "lastUpdated": 1480374057688, - "last_modified": null, - "last_modified_date": null, - "mode": "FULL", - "organization": "A", - "output_file_formats": [], - "parent_id": null, - "path": "github.com/A/l", - "repository": "l", - "sourceControl": "github.com", - "source_control_provider": "GITHUB", - "starredUsers": [], - "topicId": 1234, - "topicAutomatic": null, - "topicManual": null, - "topicAI": "test AI topic sentence", - "topicSelection": "AI", - "users": [ - { - "avatarUrl": null, - "curator": true, - "id": 1, - "isAdmin": true, - "name": "user_A", - "orcid": null, - "privacyPolicyVersion": "PRIVACY_POLICY_VERSION_2_5", - "privacyPolicyVersionAcceptanceDate": null, - "setupComplete": false, - "tosacceptanceDate": null, - "tosversion": "TOS_VERSION_1", - "userProfiles": null, - "username": "user_A" - } - ], - "workflowName": null, - "workflowVersions": [ - { - "aliases": null, - "author": null, - "commitID": null, - "dbUpdateDate": 1480374119003, - "description": null, - "descriptionSource": null, - "dirtyBit": false, - "doiStatus": "NOT_REQUESTED", - "doiURL": null, - "email": null, - "frozen": false, - "hidden": false, - "id": 13, - "images": null, - "input_file_formats": [], - "last_modified": 1480374117003, - "legacyVersion": true, - "name": "master", - "output_file_formats": [], - "reference": "master", - "referenceType": "UNSET", - "sourceFiles": [ - { - "absolutePath": "/1st-workflow.cwl", - "checksums": null, - "content": "cwlVersion: v1.0\nclass: Workflow\ninputs:\n inp: File\n ex: string\n\noutputs:\n classout:\n type: File\n outputSource: compile/classfile\n\nsteps:\n untar:\n run: tar-param.cwl\n in:\n tarfile: inp\n extractfile: ex\n out: [example_out]\n\n compile:\n run: arguments.cwl\n in:\n src: untar/example_out\n out: [classfile]\n\n wrkflow:\n run: grep-and-count.cwl\n in:\n infiles: inp\n pattern: \"hello\"\n out: [outfile]\n", - "frozen": false, - "id": 28, - "path": "/1st-workflow.cwl", - "type": "DOCKSTORE_CWL", - "verifiedBySource": {} - }, - { - "absolutePath": "/arguments.cwl", - "checksums": null, - "content": "cwlVersion: v1.0\nclass: CommandLineTool\nlabel: Example trivial wrapper for Java 7 compiler\nbaseCommand: javac\nhints:\n - DockerRequirement:\n dockerPull: java:7\nbaseCommand: javac\narguments: [\"-d\", $(runtime.outdir)]\ninputs:\n src:\n type: File\n inputBinding:\n position: 1\noutputs:\n classfile:\n type: File\n outputBinding:\nglob: \"*.class\"\n", - "frozen": false, - "id": 31, - "path": "arguments.cwl", - "type": "DOCKSTORE_CWL", - "verifiedBySource": {} - }, - { - "absolutePath": "/grep-and-count.cwl", - "checksums": null, - "content": "class: Workflow\ncwlVersion: v1.0\n\nrequirements:\n - class: ScatterFeatureRequirement\n - class: DockerRequirement\n dockerPull: java:7\n\ninputs:\n pattern: string\n infiles: File[]\n\noutputs:\n outfile:\n type: File\n outputSource: wc/outfile\n\nsteps:\n grep:\n run: grep.cwl\n in:\n pattern: pattern\n infile: infiles\n scatter: infile\n out: [outfile]\n\n wc:\n run: wc.cwl\n in:\n infiles: grep/outfile\nout: [outfile]\n", - "frozen": false, - "id": 27, - "path": "grep-and-count.cwl", - "type": "DOCKSTORE_CWL", - "verifiedBySource": {} - }, - { - "absolutePath": "/grep.cwl", - "checksums": null, - "content": "#!/usr/bin/env cwl-runner\nclass: CommandLineTool\ncwlVersion: v1.0\n\ninputs:\n pattern:\n type: string\n inputBinding: {position: 0}\n infile:\n type: File\n inputBinding: {position: 1}\n\noutputs:\n outfile:\n type: stdout\n\nbaseCommand: grep\n", - "frozen": false, - "id": 29, - "path": "grep.cwl", - "type": "DOCKSTORE_CWL", - "verifiedBySource": {} - }, - { - "absolutePath": "/tar-param.cwl", - "checksums": null, - "content": "cwlVersion: v1.0\nclass: CommandLineTool\nbaseCommand: [tar, xf]\ninputs:\n tarfile:\n type: File\n inputBinding:\n position: 1\n extractfile:\n type: string\n inputBinding:\n position: 2\noutputs:\n example_out:\n type: File\n outputBinding:\nglob: $(inputs.extractfile)\n", - "frozen": false, - "id": 32, - "path": "tar-param.cwl", - "type": "DOCKSTORE_CWL", - "verifiedBySource": {} - }, - { - "absolutePath": "/wc.cwl", - "checksums": null, - "content": "#!/usr/bin/env cwl-runner\nclass: CommandLineTool\ncwlVersion: v1.0\n\ninputs:\n infiles:\n type: File[]\n inputBinding: {position: 1}\n\noutputs:\n outfile:\n type: stdout\n\nbaseCommand: [wc, -l]\n", - "frozen": false, - "id": 30, - "path": "wc.cwl", - "type": "DOCKSTORE_CWL", - "verifiedBySource": {} - } - ], - "subClass": null, - "userIdToOrcidPutCode": {}, - "valid": true, - "validations": null, - "verified": false, - "verifiedSource": null, - "verifiedSources": [], - "versionEditor": null, - "workflow_path": "/1st-workflow.cwl", - "workingDirectory": "" - } - ], - "workflow_path": "/1st-workflow.cwl" -} diff --git a/src/app/container/info-tab/info-tab.component.html b/src/app/container/info-tab/info-tab.component.html index 760c14c7a9..a595e3d0d7 100644 --- a/src/app/container/info-tab/info-tab.component.html +++ b/src/app/container/info-tab/info-tab.component.html @@ -264,91 +264,9 @@ - -
  • - Topic - {{ tool?.topicAutomatic }} - {{ tool?.topicManual }} - - {{ tool?.topicAI }} - - +
  • +
  • - - -
  • - Topic Automatic - {{ tool?.topicAutomatic }} -
  • -
  • - AI Generated Topic - {{ tool?.topicAI }} - -
  • - -
  • -
    - Topic - {{ tool?.topicManual || 'n/a' }} - - - - - -
    -
  • -
  • -
    - Topic Selection - - - Automatic - Manual - AI Generated - - -
    -
  • -
    = []; public displayedColumns: string[] = ['name', 'role', 'affiliation', 'email']; @@ -156,7 +157,6 @@ export class InfoTabComponent extends Base implements OnInit, OnChanges { this.infoTabService.wdlPathEditing$.pipe(takeUntil(this.ngUnsubscribe)).subscribe((editing) => (this.wdlPathEditing = editing)); this.infoTabService.cwlTestPathEditing$.pipe(takeUntil(this.ngUnsubscribe)).subscribe((editing) => (this.cwlTestPathEditing = editing)); this.infoTabService.wdlTestPathEditing$.pipe(takeUntil(this.ngUnsubscribe)).subscribe((editing) => (this.wdlTestPathEditing = editing)); - this.infoTabService.topicEditing$.pipe(takeUntil(this.ngUnsubscribe)).subscribe((topicEditing) => (this.topicEditing = topicEditing)); this.sessionQuery.isPublic$.pipe(takeUntil(this.ngUnsubscribe)).subscribe((publicPage) => (this.isPublic = publicPage)); } @@ -168,25 +168,6 @@ export class InfoTabComponent extends Base implements OnInit, OnChanges { }); } - toggleEditTopic() { - if (this.topicEditing) { - this.infoTabService.saveTopic(this.tool, this.revertTopic.bind(this)); - } - this.infoTabService.setTopicEditing(!this.topicEditing); - } - - revertTopic() { - this.tool.topicManual = this.extendedDockstoreTool.topicManual; - } - - revertTopicSelection() { - this.tool.topicSelection = this.extendedDockstoreTool.topicSelection; - } - - topicSelectionChange() { - this.infoTabService.saveTopicSelection(this.tool, this.revertTopicSelection.bind(this)); - } - toggleEditDockerFile() { if (this.dockerFileEditing) { this.infoTabService.updateAndRefresh(this.tool); @@ -222,14 +203,7 @@ export class InfoTabComponent extends Base implements OnInit, OnChanges { } somethingIsBeingEdited(): boolean { - return ( - this.dockerFileEditing || - this.cwlPathEditing || - this.wdlPathEditing || - this.cwlTestPathEditing || - this.wdlTestPathEditing || - this.topicEditing - ); + return this.dockerFileEditing || this.cwlPathEditing || this.wdlPathEditing || this.cwlTestPathEditing || this.wdlTestPathEditing; } /** @@ -239,7 +213,6 @@ export class InfoTabComponent extends Base implements OnInit, OnChanges { */ cancelEditing(): void { this.infoTabService.cancelEditing(); - this.revertTopic(); } /** diff --git a/src/app/container/info-tab/info-tab.service.ts b/src/app/container/info-tab/info-tab.service.ts index 7673ea7540..10c9372d35 100644 --- a/src/app/container/info-tab/info-tab.service.ts +++ b/src/app/container/info-tab/info-tab.service.ts @@ -31,7 +31,6 @@ export class InfoTabService extends Base { public wdlPathEditing$: BehaviorSubject = new BehaviorSubject(false); public cwlTestPathEditing$: BehaviorSubject = new BehaviorSubject(false); public wdlTestPathEditing$: BehaviorSubject = new BehaviorSubject(false); - public topicEditing$: BehaviorSubject = new BehaviorSubject(false); /** * The original tool that should be in sync with the database @@ -86,46 +85,6 @@ export class InfoTabService extends Base { this.wdlTestPathEditing$.next(editing); } - setTopicEditing(editing: boolean) { - this.topicEditing$.next(editing); - } - - saveTopic(tool: DockstoreTool, errorCallback: () => void) { - this.alertService.start('Updating topic'); - const partialTool = this.getPartialToolForUpdate(tool); - this.containersService.updateContainer(this.tool.id, partialTool).subscribe( - (response) => { - this.alertService.detailedSuccess(); - const newTopic = response.topicManual; - this.containerService.updateActiveTopic(newTopic); - }, - (error) => { - this.alertService.detailedError(error); - errorCallback(); - } - ); - } - - /** - * Warning, this could potentially update a few other properties - * @param entry - */ - saveTopicSelection(entry: DockstoreTool, errorCallback: () => void) { - this.alertService.start('Updating topic selection'); - const partialEntryForUpdate = this.getPartialToolForUpdate(entry); - this.containersService.updateContainer(this.originalTool.id, partialEntryForUpdate).subscribe( - (response) => { - this.alertService.detailedSuccess(); - const newTopicSelection = response.topicSelection; - this.containerService.updateActiveTopicSelection(newTopicSelection); - }, - (error) => { - this.alertService.detailedError(error); - errorCallback(); - } - ); - } - updateAndRefresh(tool: ExtendedDockstoreTool) { const message = 'Tool Info'; const partialTool = this.getPartialToolForUpdate(tool); @@ -198,7 +157,6 @@ export class InfoTabService extends Base { this.wdlPathEditing$.next(false); this.wdlTestPathEditing$.next(false); this.cwlTestPathEditing$.next(false); - this.topicEditing$.next(false); this.restoreTool(); } diff --git a/src/app/home-page/home-logged-out/home.component.scss b/src/app/home-page/home-logged-out/home.component.scss index a82ee0ad70..33610751ee 100644 --- a/src/app/home-page/home-logged-out/home.component.scss +++ b/src/app/home-page/home-logged-out/home.component.scss @@ -115,10 +115,6 @@ h3 { border-radius: 1rem; } -.primary-3 { - color: mat.get-color-from-palette($dockstore-app-primary, 3); -} - .warn-1 { color: mat.get-color-from-palette($dockstore-app-error, darker); } diff --git a/src/app/shared/entry-actions/entry-actions.service.spec.ts b/src/app/shared/entry-actions/entry-actions.service.spec.ts index a56dfd7878..e54824bf53 100644 --- a/src/app/shared/entry-actions/entry-actions.service.spec.ts +++ b/src/app/shared/entry-actions/entry-actions.service.spec.ts @@ -49,6 +49,7 @@ describe('Service: EntryActionsService', () => { expect(service).toBeTruthy(); })); it('should know if entry is hosted', inject([EntryActionsService], (service: EntryActionsService) => { + expect(DockstoreTool.ModeEnum.HOSTED).toBe(Workflow.ModeEnum.HOSTED); // Both values should be the same expect(service.isEntryHosted(null)).toBeTruthy(); const dockstoreTool = {}; dockstoreTool.mode = DockstoreTool.ModeEnum.HOSTED; diff --git a/src/app/shared/entry/info-tab-topic/display-topic/display-topic.component.html b/src/app/shared/entry/info-tab-topic/display-topic/display-topic.component.html new file mode 100644 index 0000000000..e071a83a00 --- /dev/null +++ b/src/app/shared/entry/info-tab-topic/display-topic/display-topic.component.html @@ -0,0 +1,37 @@ +
    +
    + Topic: + {{ selectedTopic || 'n/a' }} + + + + {{ entry.topicSelection === TopicSelectionEnum.MANUAL ? 'edit' : 'sync' }} + {{ entry.topicSelection | titlecase }} + +
    + +
    diff --git a/src/app/shared/entry/info-tab-topic/display-topic/display-topic.component.spec.ts b/src/app/shared/entry/info-tab-topic/display-topic/display-topic.component.spec.ts new file mode 100644 index 0000000000..2d4bfe6f39 --- /dev/null +++ b/src/app/shared/entry/info-tab-topic/display-topic/display-topic.component.spec.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2024 OICR and 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 { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { DisplayTopicComponent } from './display-topic.component'; +import { EntryTypeMetadataStubService } from 'app/test/service-stubs'; +import { MatLegacyDialogModule } from '@angular/material/legacy-dialog'; +import { EntryTypeMetadataService } from 'app/entry/type-metadata/entry-type-metadata.service'; +import { sampleWorkflow1 } from 'app/test/mocked-objects'; + +describe('DisplayTopicComponent', () => { + let component: DisplayTopicComponent; + let fixture: ComponentFixture; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + providers: [{ provide: EntryTypeMetadataService, useClass: EntryTypeMetadataStubService }], + imports: [MatLegacyDialogModule, DisplayTopicComponent], + }).compileComponents(); + }) + ); + + beforeEach(() => { + fixture = TestBed.createComponent(DisplayTopicComponent); + component = fixture.componentInstance; + component.entry = sampleWorkflow1; + component.disableEditing = false; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/entry/info-tab-topic/display-topic/display-topic.component.ts b/src/app/shared/entry/info-tab-topic/display-topic/display-topic.component.ts new file mode 100644 index 0000000000..afe061932e --- /dev/null +++ b/src/app/shared/entry/info-tab-topic/display-topic/display-topic.component.ts @@ -0,0 +1,61 @@ +import { AsyncPipe, NgIf, TitleCasePipe } from '@angular/common'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { MatIconModule } from '@angular/material/icon'; +import { MatLegacyButtonModule } from '@angular/material/legacy-button'; +import { MatLegacyDialog } from '@angular/material/legacy-dialog'; +import { MatLegacyTooltipModule } from '@angular/material/legacy-tooltip'; +import { Base } from 'app/shared/base'; +import { DockstoreTool, Entry, EntryTypeMetadata, Workflow } from 'app/shared/openapi'; +import { SessionQuery } from 'app/shared/session/session.query'; +import { Observable } from 'rxjs'; +import { EditTopicDialogComponent } from '../edit-topic/edit-topic-dialog.component'; +import { bootstrap4largeModalSize } from 'app/shared/constants'; +import { AiBubbleComponent } from 'app/shared/ai-bubble/ai-bubble.component'; +import { FlexModule } from '@ngbracket/ngx-layout'; + +@Component({ + selector: 'app-display-topic', + templateUrl: './display-topic.component.html', + styleUrls: ['../../../styles/info-tab.component.scss'], + standalone: true, + imports: [NgIf, MatLegacyTooltipModule, MatLegacyButtonModule, MatIconModule, AsyncPipe, AiBubbleComponent, FlexModule, TitleCasePipe], +}) +export class DisplayTopicComponent extends Base implements OnInit, OnDestroy { + TopicSelectionEnum = Entry.TopicSelectionEnum; + isPublic$: Observable; + isGitHubAppEntry: boolean; + entryTypeMetadata: EntryTypeMetadata; + selectedTopic: string; + @Input() entry: DockstoreTool | Workflow; + @Input() disableEditing: boolean; + constructor(private sessionQuery: SessionQuery, private dialog: MatLegacyDialog) { + super(); + } + + ngOnInit(): void { + this.isPublic$ = this.sessionQuery.isPublic$; + this.entryTypeMetadata = this.entry.entryTypeMetadata; + this.isGitHubAppEntry = (this.entry as Workflow).mode === Workflow.ModeEnum.DOCKSTOREYML; // Only Workflow has DOCKSTOREYML ModeEnum + } + + ngOnChanges(): void { + this.selectedTopic = this.getSelectedTopic(); + } + + editTopic() { + this.dialog.open(EditTopicDialogComponent, { width: bootstrap4largeModalSize, data: { entry: this.entry } }); + } + + private getSelectedTopic(): string | null { + switch (this.entry.topicSelection) { + case this.TopicSelectionEnum.MANUAL: + return this.entry.topicManual; + case this.TopicSelectionEnum.AUTOMATIC: + return this.entry.topicAutomatic; + case this.TopicSelectionEnum.AI: + return this.entry.topicAI; + default: + return null; + } + } +} diff --git a/src/app/shared/entry/info-tab-topic/edit-topic/edit-topic-dialog.component.html b/src/app/shared/entry/info-tab-topic/edit-topic/edit-topic-dialog.component.html new file mode 100644 index 0000000000..141fccc903 --- /dev/null +++ b/src/app/shared/entry/info-tab-topic/edit-topic/edit-topic-dialog.component.html @@ -0,0 +1,78 @@ +

    Select a Topic

    + +

    + A topic is a short description of your {{ entryTypeMetadata.term }}. There are {{ isHostedEntry ? 'two' : 'three' }} types of topics + that you can choose from: +

    +
    + + + +
    + edit + Manual +
    +
    + Entered manually by the user{{ isGitHubAppEntry ? ' in the .dockstore.yml file on GitHub' : '' }}. +
    +
    + {{ entry.topicManual || 'Not Available' }} +
    + + + +
    +
    + + +
    + sync + Automatic +
    +
    Retrieved automatically from the GitHub repository description.
    +
    + {{ entry.topicAutomatic || 'Not Available' }} +
    +
    +
    + + +
    + AI generation icon + AI +
    +
    Generated by AI using the content of your {{ entryTypeMetadata.term }}.
    +
    {{ entry.topicAI || 'Not Available' }}
    +
    +
    +
    +
    +
    +
    + + +
    diff --git a/src/app/shared/entry/info-tab-topic/edit-topic/edit-topic-dialog.component.scss b/src/app/shared/entry/info-tab-topic/edit-topic/edit-topic-dialog.component.scss new file mode 100644 index 0000000000..7a9b2dbddd --- /dev/null +++ b/src/app/shared/entry/info-tab-topic/edit-topic/edit-topic-dialog.component.scss @@ -0,0 +1,9 @@ +// This style ensures that the mat radio content text fills up the whole mat-card +:host ::ng-deep .mat-radio-label-content { + width: 100%; +} + +// Wraps the text because by default, mat-radio-label doesn't wrap +:host ::ng-deep .mat-radio-label { + white-space: normal; +} diff --git a/src/app/shared/entry/info-tab-topic/edit-topic/edit-topic-dialog.component.spec.ts b/src/app/shared/entry/info-tab-topic/edit-topic/edit-topic-dialog.component.spec.ts new file mode 100644 index 0000000000..ad5efac85e --- /dev/null +++ b/src/app/shared/entry/info-tab-topic/edit-topic/edit-topic-dialog.component.spec.ts @@ -0,0 +1,65 @@ +/* + * Copyright 2024 OICR and 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 { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { EditTopicDialogStubService, EntryActionsStubService, EntryTypeMetadataStubService } from 'app/test/service-stubs'; +import { MAT_LEGACY_DIALOG_DATA, MatLegacyDialogModule, MatLegacyDialogRef } from '@angular/material/legacy-dialog'; +import { EntryTypeMetadataService } from 'app/entry/type-metadata/entry-type-metadata.service'; +import { sampleWorkflow1 } from 'app/test/mocked-objects'; +import { EditTopicDialogComponent } from './edit-topic-dialog.component'; +import { EditTopicDialogService } from './edit-topic-dialog.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { EntryActionsService } from 'app/shared/entry-actions/entry-actions.service'; + +describe('EditTopicDialogComponent', () => { + let component: EditTopicDialogComponent; + let fixture: ComponentFixture; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: EntryTypeMetadataService, useClass: EntryTypeMetadataStubService }, + { provide: EditTopicDialogService, useClass: EditTopicDialogStubService }, + { provide: EntryActionsService, useClass: EntryActionsStubService }, + { + provide: MatLegacyDialogRef, + useValue: { + close: (dialogResult: any) => {}, + }, + }, + { + provide: MAT_LEGACY_DIALOG_DATA, + useValue: { entry: sampleWorkflow1 }, + }, + ], + imports: [EditTopicDialogComponent, BrowserAnimationsModule], + }).compileComponents(); + }) + ); + + beforeEach(() => { + fixture = TestBed.createComponent(EditTopicDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/entry/info-tab-topic/edit-topic/edit-topic-dialog.component.ts b/src/app/shared/entry/info-tab-topic/edit-topic/edit-topic-dialog.component.ts new file mode 100644 index 0000000000..613585065b --- /dev/null +++ b/src/app/shared/entry/info-tab-topic/edit-topic/edit-topic-dialog.component.ts @@ -0,0 +1,91 @@ +/* + * Copyright 2024 OICR and 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, Inject } from '@angular/core'; +import { MatLegacyDialogRef, MatLegacyDialogModule, MAT_LEGACY_DIALOG_DATA } from '@angular/material/legacy-dialog'; +import { MatLegacyButtonModule } from '@angular/material/legacy-button'; +import { FlexModule } from '@ngbracket/ngx-layout/flex'; +import { MatLegacyInputModule } from '@angular/material/legacy-input'; +import { MatLegacyFormFieldModule } from '@angular/material/legacy-form-field'; +import { NgIf } from '@angular/common'; +import { MatDividerModule } from '@angular/material/divider'; +import { AlertComponent } from 'app/shared/alert/alert.component'; +import { DockstoreTool, Entry, EntryType, EntryTypeMetadata, Workflow } from 'app/shared/openapi'; +import { MatLegacyTooltipModule } from '@angular/material/legacy-tooltip'; +import { AiBubbleComponent } from 'app/shared/ai-bubble/ai-bubble.component'; +import { FormsModule } from '@angular/forms'; +import { MatLegacyRadioModule } from '@angular/material/legacy-radio'; +import { MatIconModule } from '@angular/material/icon'; +import { MatLegacyCardModule } from '@angular/material/legacy-card'; +import { EditTopicDialogService } from './edit-topic-dialog.service'; +import { EntryActionsService } from 'app/shared/entry-actions/entry-actions.service'; + +export interface EditTopicDialogData { + entry: DockstoreTool | Workflow; +} + +@Component({ + selector: 'app-edit-topic-dialog', + templateUrl: './edit-topic-dialog.component.html', + styleUrls: ['./edit-topic-dialog.component.scss'], + standalone: true, + imports: [ + MatLegacyDialogModule, + AlertComponent, + MatDividerModule, + NgIf, + MatLegacyFormFieldModule, + MatLegacyInputModule, + FlexModule, + MatLegacyButtonModule, + MatLegacyTooltipModule, + AiBubbleComponent, + FormsModule, + MatLegacyRadioModule, + MatIconModule, + MatLegacyCardModule, + ], +}) +export class EditTopicDialogComponent { + TopicSelectionEnum = Entry.TopicSelectionEnum; + entry: Workflow | DockstoreTool; + entryType: EntryType; + entryTypeMetadata: EntryTypeMetadata; + isGitHubAppEntry: boolean; + isHostedEntry: boolean; + topicEditing: boolean; + selectedOption: Entry.TopicSelectionEnum; + editedTopicManual: string; + + constructor( + public dialogRef: MatLegacyDialogRef, + public editTopicDialogService: EditTopicDialogService, + public entryActionsService: EntryActionsService, + @Inject(MAT_LEGACY_DIALOG_DATA) public data: EditTopicDialogData + ) { + this.entry = data.entry; + this.entryType = data.entry.entryType; + this.entryTypeMetadata = data.entry.entryTypeMetadata; + this.isGitHubAppEntry = (data.entry as Workflow).mode === Workflow.ModeEnum.DOCKSTOREYML; // Only Workflow has DOCKSTOREYML ModeEnum + this.isHostedEntry = this.entryActionsService.isEntryHosted(data.entry); + this.selectedOption = data.entry.topicSelection; + this.editedTopicManual = data.entry.topicManual; + } + + saveTopic() { + this.editTopicDialogService.saveTopicChanges(this.entry, this.editedTopicManual, this.selectedOption); + } +} diff --git a/src/app/shared/entry/info-tab-topic/edit-topic/edit-topic-dialog.service.ts b/src/app/shared/entry/info-tab-topic/edit-topic/edit-topic-dialog.service.ts new file mode 100644 index 0000000000..59fd114250 --- /dev/null +++ b/src/app/shared/entry/info-tab-topic/edit-topic/edit-topic-dialog.service.ts @@ -0,0 +1,48 @@ +import { Injectable } from '@angular/core'; +import { AlertService } from '../../../alert/state/alert.service'; +import { ContainersService, DockstoreTool, EntryType, Workflow, WorkflowsService } from '../../../openapi'; +import { WorkflowService } from '../../../state/workflow.service'; +import { ContainerService } from '../../../container.service'; + +@Injectable() +export class EditTopicDialogService { + constructor( + private alertService: AlertService, + private workflowsService: WorkflowsService, + private workflowService: WorkflowService, + private containersService: ContainersService, + private containerService: ContainerService + ) {} + + saveTopicChanges(entry: Workflow | DockstoreTool, topicManual: string, topicSelection: Workflow.TopicSelectionEnum) { + this.alertService.start('Saving topic changes'); + const newEntryForUpdate = { ...entry, topicManual: topicManual, topicSelection: topicSelection }; + + if (entry.entryType === EntryType.TOOL) { + this.containersService.updateContainer(entry.id, newEntryForUpdate as DockstoreTool).subscribe( + (response) => { + this.alertService.detailedSuccess(); + const newTopicSelection = response.topicSelection; + this.containerService.updateActiveTopicSelection(newTopicSelection); + const newTopic = response.topicManual; + this.containerService.updateActiveTopic(newTopic); + }, + (error) => { + this.alertService.detailedError(error); + } + ); + } else { + this.workflowsService.updateWorkflow(entry.id, newEntryForUpdate as Workflow).subscribe( + (response) => { + this.alertService.detailedSuccess(); + const newTopicSelection = response.topicSelection; + const newTopic = response.topicManual; + this.workflowService.updateActiveTopicManualAndTopicSelection(newTopic, newTopicSelection); + }, + (error) => { + this.alertService.detailedError(error); + } + ); + } + } +} diff --git a/src/app/shared/state/workflow.service.ts b/src/app/shared/state/workflow.service.ts index 828710f68b..f9067a3a27 100644 --- a/src/app/shared/state/workflow.service.ts +++ b/src/app/shared/state/workflow.service.ts @@ -53,14 +53,8 @@ export class WorkflowService { } } - updateActiveTopic(topic: string) { - const newWorkflow = { ...this.workflowQuery.getActive(), topicManual: topic }; - this.workflowStore.upsert(newWorkflow.id, newWorkflow); - this.extendedWorkflowService.update(newWorkflow); - } - - updateActiveTopicSelection(topicSelection: Workflow.TopicSelectionEnum) { - const newWorkflow = { ...this.workflowQuery.getActive(), topicSelection: topicSelection }; + updateActiveTopicManualAndTopicSelection(topicManual: string, topicSelection: Workflow.TopicSelectionEnum) { + const newWorkflow = { ...this.workflowQuery.getActive(), topicManual: topicManual, topicSelection: topicSelection }; this.workflowStore.upsert(newWorkflow.id, newWorkflow); this.extendedWorkflowService.update(newWorkflow); } diff --git a/src/app/container/info-tab/info-tab.component.css b/src/app/shared/styles/info-tab.component.scss similarity index 96% rename from src/app/container/info-tab/info-tab.component.css rename to src/app/shared/styles/info-tab.component.scss index b5351685e0..874ec6db35 100644 --- a/src/app/container/info-tab/info-tab.component.css +++ b/src/app/shared/styles/info-tab.component.scss @@ -1,5 +1,5 @@ /* - * Copyright 2017 OICR + * Copyright 2024 OICR and UCSC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/app/test/mocked-objects.ts b/src/app/test/mocked-objects.ts index 9fb8b42348..992c7fac6b 100644 --- a/src/app/test/mocked-objects.ts +++ b/src/app/test/mocked-objects.ts @@ -18,7 +18,7 @@ import { OrgToolObject } from '../mytools/my-tool/my-tool.component'; import { Hit } from '../search/state/search.service'; import { ExtendedDockstoreTool } from '../shared/models/ExtendedDockstoreTool'; import { ExtendedWorkflow } from '../shared/models/ExtendedWorkflow'; -import { VersionVerifiedPlatform, Tag, WorkflowVersion, Author } from '../shared/openapi'; +import { VersionVerifiedPlatform, Tag, WorkflowVersion, Author, EntryTypeMetadata } from '../shared/openapi'; import { Notification } from '../shared/openapi/model/notification'; import { DockstoreTool } from './../shared/openapi/model/dockstoreTool'; import { SourceFile } from './../shared/openapi/model/sourceFile'; @@ -27,6 +27,61 @@ import { Workflow } from './../shared/openapi/model/workflow'; const DescriptorTypeEnum = Workflow.DescriptorTypeEnum; +export const toolEntryTypeMetadata: EntryTypeMetadata = { + searchEntryType: 'tools', + searchSupported: true, + sitePath: 'containers', + term: 'tool', + termPlural: 'tools', + trsPrefix: '', + trsSupported: true, + type: 'TOOL', +}; + +export const workflowEntryTypeMetadata: EntryTypeMetadata = { + searchEntryType: 'workflows', + searchSupported: true, + sitePath: 'workflows', + term: 'workflow', + termPlural: 'workflows', + trsPrefix: '#workflow/', + trsSupported: true, + type: 'WORKFLOW', +}; + +export const serviceEntryTypeMetadata: EntryTypeMetadata = { + searchEntryType: '', + searchSupported: false, + sitePath: 'services', + term: 'service', + termPlural: 'services', + trsPrefix: '#service/', + trsSupported: true, + type: 'SERVICE', +}; + +export const appToolEntryTypeMetadata: EntryTypeMetadata = { + searchEntryType: 'tools', + searchSupported: true, + sitePath: 'containers', + term: 'tool', + termPlural: 'tools', + trsPrefix: '', + trsSupported: true, + type: 'APPTOOL', +}; + +export const notebookEntryTypeMetadata: EntryTypeMetadata = { + searchEntryType: '', + searchSupported: false, + sitePath: 'notebooks', + term: 'notebook', + termPlural: 'notebooks', + trsPrefix: '#notebook/', + trsSupported: true, + type: 'NOTEBOOK', +}; + export const updatedWorkflow: Workflow = { type: '', descriptorType: DescriptorTypeEnum.CWL, @@ -40,6 +95,7 @@ export const updatedWorkflow: Workflow = { sourceControl: 'github.com', source_control_provider: 'GITHUB', descriptorTypeSubclass: 'n/a', + entryTypeMetadata: workflowEntryTypeMetadata, }; export const sampleWorkflow1: Workflow = { @@ -56,6 +112,7 @@ export const sampleWorkflow1: Workflow = { sourceControl: 'github.com', source_control_provider: 'GITHUB', descriptorTypeSubclass: 'n/a', + entryTypeMetadata: workflowEntryTypeMetadata, }; export const sampleWorkflow2: Workflow = { @@ -72,6 +129,7 @@ export const sampleWorkflow2: Workflow = { sourceControl: 'github.com', source_control_provider: 'GITHUB', descriptorTypeSubclass: 'n/a', + entryTypeMetadata: workflowEntryTypeMetadata, }; export const sampleWorkflow3: Workflow = { @@ -89,6 +147,7 @@ export const sampleWorkflow3: Workflow = { source_control_provider: 'GITHUB', full_workflow_path: 'github.com/sampleWorkflowPath', descriptorTypeSubclass: 'n/a', + entryTypeMetadata: workflowEntryTypeMetadata, }; export const sampleWdlWorkflow1: Workflow = { @@ -106,6 +165,7 @@ export const sampleWdlWorkflow1: Workflow = { source_control_provider: 'GITHUB', full_workflow_path: 'github.com/DataBiosphere/topmed-workflows/Functional_Equivalence', descriptorTypeSubclass: 'n/a', + entryTypeMetadata: workflowEntryTypeMetadata, }; export const sampleCwlExtendedWorkflow: ExtendedWorkflow = { @@ -123,6 +183,7 @@ export const sampleCwlExtendedWorkflow: ExtendedWorkflow = { source_control_provider: 'GITHUB', full_workflow_path: 'github.com/dockstore-testing/md5sum-checker', descriptorTypeSubclass: 'n/a', + entryTypeMetadata: workflowEntryTypeMetadata, }; export const sampleWdlWorkflow2: Workflow = { @@ -140,6 +201,7 @@ export const sampleWdlWorkflow2: Workflow = { source_control_provider: 'GITHUB', full_workflow_path: 'github.com/DataBiosphere/topmed-workflows/UM_aligner_wdl', descriptorTypeSubclass: 'n/a', + entryTypeMetadata: workflowEntryTypeMetadata, }; export const sampleWorkflowVersion: WorkflowVersion = { @@ -179,6 +241,7 @@ export const sampleTool1: DockstoreTool = { defaultCWLTestParameterFile: 'sampleDefaultCWLTestParameterFile', defaultWDLTestParameterFile: 'sampleDefaultWDLTestParameterFile', tool_path: '', + entryTypeMetadata: toolEntryTypeMetadata, }; export const sampleTool2: DockstoreTool = { @@ -196,6 +259,7 @@ export const sampleTool2: DockstoreTool = { toolname: 'sampleToolname', defaultCWLTestParameterFile: 'sampleDefaultCWLTestParameterFile', defaultWDLTestParameterFile: 'sampleDefaultWDLTestParameterFile', + entryTypeMetadata: toolEntryTypeMetadata, }; export const sampleTool3: DockstoreTool = { @@ -213,6 +277,7 @@ export const sampleTool3: DockstoreTool = { toolname: 'sampleToolname', defaultCWLTestParameterFile: 'sampleDefaultCWLTestParameterFile', defaultWDLTestParameterFile: 'sampleDefaultWDLTestParameterFile', + entryTypeMetadata: toolEntryTypeMetadata, }; // Case 1: sampleTool1 in published entries, unpublished doesn't matter diff --git a/src/app/test/service-stubs.ts b/src/app/test/service-stubs.ts index fe735af383..c2e4e26f66 100644 --- a/src/app/test/service-stubs.ts +++ b/src/app/test/service-stubs.ts @@ -38,7 +38,20 @@ import { TokenUser } from './../shared/openapi/model/tokenUser'; import { User } from './../shared/openapi/model/user'; import { Workflow } from './../shared/openapi/model/workflow'; import { WorkflowVersion } from './../shared/openapi/model/workflowVersion'; -import { bitbucketToken, gitHubToken, gitLabToken, quayToken, sampleTag, sampleWorkflow1, updatedWorkflow } from './mocked-objects'; +import { + appToolEntryTypeMetadata, + bitbucketToken, + gitHubToken, + gitLabToken, + notebookEntryTypeMetadata, + quayToken, + sampleTag, + sampleWorkflow1, + serviceEntryTypeMetadata, + toolEntryTypeMetadata, + updatedWorkflow, + workflowEntryTypeMetadata, +} from './mocked-objects'; import RoleEnum = Permission.RoleEnum; import DescriptorTypeEnum = Workflow.DescriptorTypeEnum; @@ -935,64 +948,19 @@ export class EntryTypeMetadataStubService { get(type: newEntryType): EntryTypeMetadata { switch (type) { case newEntryType.WORKFLOW: { - return { - searchEntryType: 'workflows', - searchSupported: true, - sitePath: 'workflows', - term: 'workflow', - termPlural: 'workflows', - trsPrefix: '#workflow/', - trsSupported: true, - type: 'WORKFLOW', - }; + return workflowEntryTypeMetadata; } case newEntryType.TOOL: { - return { - searchEntryType: 'tools', - searchSupported: true, - sitePath: 'containers', - term: 'tool', - termPlural: 'tools', - trsPrefix: '', - trsSupported: true, - type: 'TOOL', - }; + return toolEntryTypeMetadata; } case newEntryType.SERVICE: { - return { - searchEntryType: '', - searchSupported: false, - sitePath: 'services', - term: 'service', - termPlural: 'services', - trsPrefix: '#service/', - trsSupported: true, - type: 'SERVICE', - }; + return serviceEntryTypeMetadata; } case newEntryType.APPTOOL: { - return { - searchEntryType: 'tools', - searchSupported: true, - sitePath: 'containers', - term: 'tool', - termPlural: 'tools', - trsPrefix: '', - trsSupported: true, - type: 'APPTOOL', - }; + return appToolEntryTypeMetadata; } case newEntryType.NOTEBOOK: { - return { - searchEntryType: '', - searchSupported: false, - sitePath: 'notebooks', - term: 'notebook', - termPlural: 'notebooks', - trsPrefix: '#notebook/', - trsSupported: true, - type: 'NOTEBOOK', - }; + return notebookEntryTypeMetadata; } } return null; @@ -1010,4 +978,9 @@ export class MarkdownWrapperStubService { export class EntryActionsStubService { updateBackingEntry(entry) {} + isEntryHosted(entry) {} +} + +export class EditTopicDialogStubService { + saveTopicChanges(entry: Workflow | DockstoreTool, topicManual: string, topicSelection: Workflow.TopicSelectionEnum) {} } diff --git a/src/app/workflow/info-tab/info-tab.component.css b/src/app/workflow/info-tab/info-tab.component.css index efe631b0b2..71b2c025d5 100644 --- a/src/app/workflow/info-tab/info-tab.component.css +++ b/src/app/workflow/info-tab/info-tab.component.css @@ -27,19 +27,3 @@ .mat-column-orcid_id { width: 20rem; } - -/* Bootstrap adds a 5px bottom margin, this counteracts it. Remove once Bootstrap is removed */ -::ng-deep label { - margin-bottom: 0px; -} - -/* Better alignment for "save" icon instead of vertical-align: middle */ -.mat-icon-sm { - vertical-align: text-bottom; -} - -/* Disabled state for "edit" buttons */ -.mat-raised-button.mat-button-disabled.mat-button-disabled { - background-color: white; - filter: opacity(0.3); -} diff --git a/src/app/workflow/info-tab/info-tab.component.html b/src/app/workflow/info-tab/info-tab.component.html index c33827b346..a58db562c9 100644 --- a/src/app/workflow/info-tab/info-tab.component.html +++ b/src/app/workflow/info-tab/info-tab.component.html @@ -193,120 +193,12 @@ -
    - Topic - {{ workflow?.topicAutomatic }} - {{ workflow?.topicManual }} - - {{ workflow?.topicAI }} - - -
    -
    - Topic Automatic - {{ workflow?.topicAutomatic }} -
    -
    - AI Generated Topic - {{ workflow?.topicAI }} - -
    -
    - Topic Manual - {{ workflow?.topicManual }} - - - - - - -
    - -
  • -
    - Topic Selection - - - Automatic - Manual - AI Generated - - -
    +
  • (this.displayTextForButton = this.infoTabService.getTRSId(this.workflow, entryType))); this.infoTabService.forumUrlEditing$.pipe(takeUntil(this.ngUnsubscribe)).subscribe((editing) => (this.forumUrlEditing = editing)); - this.infoTabService.topicEditing$.pipe(takeUntil(this.ngUnsubscribe)).subscribe((topicEditing) => (this.topicEditing = topicEditing)); } /** * Handle restubbing a workflow @@ -244,13 +245,6 @@ export class InfoTabComponent extends EntryTab implements OnInit, OnChanges { this.infoTabService.setWorkflowPathEditing(!this.workflowPathEditing); } - toggleEditTopic() { - if (this.topicEditing) { - this.infoTabService.saveTopic(this.workflow, this.revertTopic.bind(this)); - } - this.infoTabService.setTopicEditing(!this.topicEditing); - } - toggleEditDefaultTestFilePath() { if (this.defaultTestFilePathEditing) { this.save(); @@ -265,18 +259,6 @@ export class InfoTabComponent extends EntryTab implements OnInit, OnChanges { this.infoTabService.setForumUrlEditing(!this.forumUrlEditing); } - revertTopic() { - this.workflow.topicManual = this.extendedWorkflow.topicManual; - } - - revertTopicSelection() { - this.workflow.topicSelection = this.extendedWorkflow.topicSelection; - } - - radioChange(event: MatRadioChange) { - this.infoTabService.saveTopicSelection(this.workflow, this.revertTopicSelection.bind(this)); - } - save() { this.infoTabService.updateAndRefresh(this.workflow); } diff --git a/src/app/workflow/info-tab/info-tab.service.ts b/src/app/workflow/info-tab/info-tab.service.ts index ad6e57d798..14da36f845 100644 --- a/src/app/workflow/info-tab/info-tab.service.ts +++ b/src/app/workflow/info-tab/info-tab.service.ts @@ -34,7 +34,6 @@ export class InfoTabService { public workflowPathEditing$: BehaviorSubject = new BehaviorSubject(false); public defaultTestFilePathEditing$: BehaviorSubject = new BehaviorSubject(false); public forumUrlEditing$: BehaviorSubject = new BehaviorSubject(false); - public topicEditing$: BehaviorSubject = new BehaviorSubject(false); public descriptorLanguageMap = []; constructor( @@ -54,10 +53,6 @@ export class InfoTabService { this.workflowPathEditing$.next(editing); } - setTopicEditing(editing: boolean) { - this.topicEditing$.next(editing); - } - setDefaultTestFilePathEditing(editing: boolean) { this.defaultTestFilePathEditing$.next(editing); } @@ -66,46 +61,6 @@ export class InfoTabService { this.forumUrlEditing$.next(editing); } - /** - * Warning, this could potentially update a few other properties - * @param workflow - */ - saveTopic(workflow: Workflow, errorCallback: () => void) { - this.alertService.start('Updating topic'); - const partialEntryForUpdate = this.getPartialEntryForUpdate(workflow); - this.workflowsService.updateWorkflow(workflow.id, partialEntryForUpdate).subscribe( - (response) => { - this.alertService.detailedSuccess(); - const newTopic = response.topicManual; - this.workflowService.updateActiveTopic(newTopic); - }, - (error) => { - this.alertService.detailedError(error); - errorCallback(); - } - ); - } - - /** - * Warning, this could potentially update a few other properties - * @param workflow - */ - saveTopicSelection(workflow: Workflow, errorCallback: () => void) { - this.alertService.start('Updating topic selection'); - const partialEntryForUpdate = this.getPartialEntryForUpdate(workflow); - this.workflowsService.updateWorkflow(workflow.id, partialEntryForUpdate).subscribe( - (response) => { - this.alertService.detailedSuccess(); - const newTopicSelection = response.topicSelection; - this.workflowService.updateActiveTopicSelection(newTopicSelection); - }, - (error) => { - this.alertService.detailedError(error); - errorCallback(); - } - ); - } - updateAndRefresh(workflow: Workflow) { const message = 'Workflow Info'; const partialEntryForUpdate = this.getPartialEntryForUpdate(workflow); @@ -201,7 +156,6 @@ export class InfoTabService { this.workflowPathEditing$.next(false); this.defaultTestFilePathEditing$.next(false); this.forumUrlEditing$.next(false); - this.topicEditing$.next(false); } /** diff --git a/src/main.ts b/src/main.ts index 45217e203b..c4814b4e37 100644 --- a/src/main.ts +++ b/src/main.ts @@ -76,6 +76,7 @@ import { RegisterWorkflowModalService } from './app/workflow/register-workflow-m import { VersionModalService } from './app/workflow/version-modal/version-modal.service'; import { ViewService } from './app/workflow/view/view.service'; import { environment } from './environments/environment'; +import { EditTopicDialogService } from 'app/shared/entry/info-tab-topic/edit-topic/edit-topic-dialog.service'; if (environment.production) { enableProdMode(); @@ -116,6 +117,7 @@ bootstrapApplication(AppComponent, { ContainerVersionModalService, DateService, DescriptorLanguageService, + EditTopicDialogService, EmailService, EntryActionsService, EntryTypeMetadataService, diff --git a/src/styles.scss b/src/styles.scss index bb5928da42..85b6354bff 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1428,3 +1428,11 @@ app-ai-bubble { .ai-bubble:hover { background-color: mat.get-color-from-palette($dockstore-app-gray, 7); } + +.primary-3 { + color: mat.get-color-from-palette($dockstore-app-primary, 3); +} + +.gray-caption { + color: mat.get-color-from-palette($kim-gray, 2); +}