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 diff --git a/cypress/e2e/group1/checkerWorkflowFromWorkflow.ts b/cypress/e2e/group1/checkerWorkflowFromWorkflow.ts index 3fac473ee..7950cb868 100644 --- a/cypress/e2e/group1/checkerWorkflowFromWorkflow.ts +++ b/cypress/e2e/group1/checkerWorkflowFromWorkflow.ts @@ -123,6 +123,9 @@ describe('Checker workflow test from my-workflows', () => { cy.url().should('eq', Cypress.config().baseUrl + '/my-workflows/github.com/A/l'); goToTab('Versions'); + cy.get('[data-cy=date-modified-header]').should('be.visible').click(); + cy.wait(1000); //waits for the sorting to finish to ensure Actions button is clicked for the right version (was encountering flakiness) + cy.contains('button', 'Actions').click(); cy.get('[data-cy=set-default-version-button]').should('be.visible').click(); goToTab('Info'); diff --git a/cypress/e2e/group2/myworkflows.ts b/cypress/e2e/group2/myworkflows.ts index bc1dbb5a2..5c12545bd 100644 --- a/cypress/e2e/group2/myworkflows.ts +++ b/cypress/e2e/group2/myworkflows.ts @@ -337,6 +337,9 @@ describe('Dockstore my workflows part 2', () => { cy.visit('/my-workflows/github.com/A/l'); cy.url().should('eq', Cypress.config().baseUrl + '/my-workflows/github.com/A/l'); goToTab('Versions'); + cy.get('[data-cy=date-modified-header]').should('be.visible').click(); + cy.wait(1000); + cy.get('td').contains('Actions').click(); } @@ -429,10 +432,19 @@ describe('Dockstore my workflows part 2', () => { cy.get('[data-cy=dockstore-request-doi-button]').click(); cy.get('[data-cy=export-button').should('be.enabled'); cy.get('[data-cy=export-button').click(); + + cy.fixture('versionWithDoi.json').then((json) => { + cy.intercept('GET', '/api/workflows/11/workflowVersions?limit=10&offset=0&sortCol=lastModified&sortOrder=asc', { + body: json, + statusCode: 200, + }).as('getVersionWithDoi'); + }); + // Should have DOI badges now cy.get('[data-cy=user-DOI-icon]').should('be.visible'); cy.get('[data-cy=concept-DOI-badge]').should('be.visible'); cy.get('[data-cy=version-DOI-badge]').should('be.visible'); + goToTab('Versions'); cy.get('td').contains('Actions').click(); cy.get('[data-cy=dockstore-request-doi-button').should('not.exist'); // Should not be able to request another DOI @@ -440,6 +452,13 @@ describe('Dockstore my workflows part 2', () => { cy.get('[data-cy=dockstore-export-orcid-button]').click(); cy.get('[data-cy=export-button').should('be.enabled'); cy.get('[data-cy=export-button').click(); + cy.fixture('versionAfterOrcidExport.json').then((json) => { + cy.intercept('GET', '/api/workflows/11/workflowVersions?limit=10&offset=0&sortCol=lastModified&sortOrder=asc', { + body: json, + statusCode: 200, + }).as('getVersionAfterOrcidExport'); + }); + goToTab('Versions'); cy.get('td').contains('Actions').click(); cy.get('[data-cy=dockstore-export-orcid-button]').should('not.exist'); // Should not be able to export to ORCID again }); @@ -485,6 +504,8 @@ describe('Dockstore my workflows part 2', () => { cy.visit('/my-workflows/github.com/A/l'); cy.url().should('eq', Cypress.config().baseUrl + '/my-workflows/github.com/A/l'); goToTab('Versions'); + cy.get('[data-cy=date-modified-header]').should('be.visible').click(); + cy.wait(1000); cy.contains('button', 'Actions').click(); cy.contains('button', 'Refresh Version').should('not.be.disabled'); }); @@ -502,7 +523,14 @@ describe('Dockstore my workflows part 2', () => { cy.get('[data-cy=refreshOrganization]:visible').should('be.visible').click(); cy.get('[data-cy=confirm-dialog-button] > .mat-button-wrapper').contains('Refresh').click(); cy.wait('@refreshWorkflow'); + + cy.fixture('sampleWorkflowVersion').then((json) => { + cy.intercept('GET', '/api/workflows/11/workflowVersions?limit=10&offset=0&sortOrder=desc', { + body: json, + }).as('getVersion'); + }); goToTab('Versions'); + cy.wait('@getVersion'); cy.get('table>tbody>tr').should('have.length', 1); // 2 Versions and no warning line }); @@ -633,6 +661,8 @@ describe('Dockstore my workflows part 3', () => { cy.get('#publishButton').should('contain', 'Publish').should('be.visible'); goToTab('Versions'); + cy.get('[data-cy=date-modified-header]').should('be.visible').click(); + cy.wait(1000); cy.contains('button', 'Actions').click(); cy.get('[data-cy=set-default-version-button]').should('be.visible').click(); cy.wait(1000); diff --git a/cypress/fixtures/sampleWorkflowVersion.json b/cypress/fixtures/sampleWorkflowVersion.json new file mode 100644 index 000000000..758b51992 --- /dev/null +++ b/cypress/fixtures/sampleWorkflowVersion.json @@ -0,0 +1,98 @@ +[ + { + "aliases": null, + "author": null, + "commitID": null, + "dbUpdateDate": 1480374119003, + "description": null, + "descriptionSource": null, + "dirtyBit": false, + "doiStatus": "NOT_REQUESTED", + "doiURL": null, + "dois": {}, + "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": "" + } +] diff --git a/cypress/fixtures/versionAfterOrcidExport.json b/cypress/fixtures/versionAfterOrcidExport.json new file mode 100644 index 000000000..914d2346e --- /dev/null +++ b/cypress/fixtures/versionAfterOrcidExport.json @@ -0,0 +1,94 @@ +[ + { + "aliases": null, + "author": null, + "authors": [], + "commitID": null, + "dbUpdateDate": 1622246514895, + "description": null, + "descriptionSource": null, + "dirtyBit": false, + "doiStatus": "CREATED", + "doiURL": "10.5072/zenodo.841014", + "dois": { + "USER": { + "type": "VERSION", + "name": "10.5072/zenodo.841014", + "initiator": "USER" + } + }, + "email": null, + "frozen": true, + "hidden": false, + "id": 13, + "images": null, + "input_file_formats": [], + "last_modified": 1480374117003, + "legacyVersion": true, + "name": "master", + "orcidAuthors": [], + "output_file_formats": [], + "reference": "master", + "referenceType": "UNSET", + "subClass": null, + "synced": false, + "valid": true, + "validations": null, + "verified": false, + "verifiedSource": null, + "verifiedSources": [], + "versionEditor": null, + "versionMetadata": { + "id": 13, + "parsedInformationSet": null, + "userIdToOrcidPutCode": { + "1": { + "orcidPutCode":"1234567" + } + } + }, + "workflow_path": "/1st-workflow.cwl", + "workingDirectory": "" + }, + { + "aliases": null, + "author": null, + "authors": [], + "commitID": null, + "dbUpdateDate": 1480374117003, + "description": null, + "descriptionSource": null, + "dirtyBit": false, + "doiStatus": "NOT_REQUESTED", + "doiURL": null, + "dois": {}, + "email": null, + "frozen": false, + "hidden": true, + "id": 14, + "images": null, + "input_file_formats": [], + "last_modified": 1480374117003, + "legacyVersion": true, + "name": "test", + "orcidAuthors": [], + "output_file_formats": [], + "reference": "test", + "referenceType": "UNSET", + "subClass": null, + "synced": false, + "valid": true, + "validations": null, + "verified": false, + "verifiedSource": null, + "verifiedSources": [], + "versionEditor": null, + "versionMetadata": { + "id": 14, + "parsedInformationSet": null, + "userIdToOrcidPutCode": {} + }, + "workflow_path": "/1st-workflow.cwl", + "workingDirectory": "" + } +] diff --git a/cypress/fixtures/versionWithDoi.json b/cypress/fixtures/versionWithDoi.json new file mode 100644 index 000000000..1641be232 --- /dev/null +++ b/cypress/fixtures/versionWithDoi.json @@ -0,0 +1,113 @@ +[ + { + "aiTopicProcessed": false, + "aliases": null, + "author": "Muhammed Lee", + "authors": [ + { + "affiliation": null, + "email": "Muhammad.Lee@oicr.on.ca", + "name": "Muhammed Lee", + "role": null + } + ], + "commitID": null, + "dbUpdateDate": 1722026014356, + "descriptionSource": null, + "dirtyBit": false, + "doiStatus": "NOT_REQUESTED", + "doiURL": null, + "dois": { + "USER": { + "type": "VERSION", + "name": "10.5072/zenodo.841014", + "initiator": "USER" + } + }, + "email": "Muhammad.Lee@oicr.on.ca", + "frozen": true, + "hidden": false, + "id": 13, + "images": null, + "input_file_formats": [], + "kernelImagePath": null, + "last_modified": 1480345317003, + "legacyVersion": true, + "metricsByPlatform": null, + "name": "master", + "orcidAuthors": null, + "output_file_formats": [], + "readMePath": null, + "reference": "master", + "referenceType": "UNSET", + "synced": false, + "userFiles": [], + "valid": true, + "validations": null, + "verified": false, + "verifiedPlatforms": [], + "verifiedSource": null, + "verifiedSources": [], + "versionEditor": null, + "versionMetadata": { + "descriptorTypeVersions": [], + "dois": {}, + "engineVersions": [], + "id": 13, + "parsedInformationSet": [], + "publicAccessibleTestParameterFile": null, + "userIdToOrcidPutCode": {} + }, + "workflow_path": "/1st-workflow.cwl", + "workingDirectory": "" + }, + { + "aiTopicProcessed": false, + "aliases": null, + "author": null, + "authors": [], + "commitID": null, + "dbUpdateDate": 1480345317003, + "descriptionSource": null, + "dirtyBit": false, + "doiStatus": "NOT_REQUESTED", + "doiURL": null, + "dois": {}, + "email": null, + "frozen": false, + "hidden": true, + "id": 14, + "images": null, + "input_file_formats": [], + "kernelImagePath": null, + "last_modified": 1480345317003, + "legacyVersion": true, + "metricsByPlatform": null, + "name": "test", + "orcidAuthors": null, + "output_file_formats": [], + "readMePath": null, + "reference": "test", + "referenceType": "UNSET", + "synced": true, + "userFiles": [], + "valid": true, + "validations": null, + "verified": false, + "verifiedPlatforms": [], + "verifiedSource": null, + "verifiedSources": [], + "versionEditor": null, + "versionMetadata": { + "descriptorTypeVersions": [], + "dois": {}, + "engineVersions": [], + "id": 14, + "parsedInformationSet": [], + "publicAccessibleTestParameterFile": null, + "userIdToOrcidPutCode": {} + }, + "workflow_path": "/1st-workflow.cwl", + "workingDirectory": "" + } +] diff --git a/package.json b/package.json index 0e864d683..14707abd8 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,9 @@ "version": "2.13.0", "license": "Apache License 2.0", "config": { - "webservice_version_prefix": "1.16.0", - "webservice_version": "1.16.0", - "use_snapshot": false + "webservice_version_prefix": "1.17.0", + "webservice_version": "1.17.0", + "use_snapshot": true }, "scripts": { "ng": "npx ng", diff --git a/src/app/shared/state/paginator.query.ts b/src/app/shared/state/paginator.query.ts index b913677e1..17980377a 100644 --- a/src/app/shared/state/paginator.query.ts +++ b/src/app/shared/state/paginator.query.ts @@ -25,9 +25,11 @@ export class PaginatorQuery extends Query { toolPageSize$: Observable = this.select((state) => (this.router.url === '/' ? 10 : state.tool.pageSize)); workflowPageSize$: Observable = this.select((state) => (this.router.url === '/' ? 10 : state.workflow.pageSize)); eventPageSize$: Observable = this.select((state) => (this.router.url === '/' ? 10 : state.lambdaEvent.pageSize)); + versionPageSize$: Observable = this.select((state) => (this.router.url === '/' ? 10 : state.version.pageSize)); toolPageIndex$: Observable = this.select((state) => (this.router.url === '/' ? 0 : state.tool.pageIndex)); workflowPageIndex$: Observable = this.select((state) => (this.router.url === '/' ? 0 : state.workflow.pageIndex)); eventPageIndex$: Observable = this.select((state) => (this.router.url === '/' ? 0 : state.lambdaEvent.pageIndex)); + versionPageIndex$: Observable = this.select((state) => (this.router.url === '/' ? 0 : state.version.pageIndex)); constructor(protected store: PaginatorStore, private router: Router) { super(store); } diff --git a/src/app/shared/state/paginator.service.ts b/src/app/shared/state/paginator.service.ts index 6d684aaa1..7ad55e401 100644 --- a/src/app/shared/state/paginator.service.ts +++ b/src/app/shared/state/paginator.service.ts @@ -5,7 +5,7 @@ import { PaginatorInfo, PaginatorStore } from './paginator.store'; export class PaginatorService { constructor(private paginatorStore: PaginatorStore) {} - setPaginator(type: 'tool' | 'workflow' | 'lambdaEvent', pageSize: number, pageNumber: number): void { + setPaginator(type: 'tool' | 'workflow' | 'lambdaEvent' | 'version', pageSize: number, pageNumber: number): void { const paginatorInfo: PaginatorInfo = { pageSize: pageSize, pageIndex: pageNumber, @@ -14,8 +14,10 @@ export class PaginatorService { this.setToolPaginatorSize(paginatorInfo); } else if (type === 'workflow') { this.setWorkflowPaginatorSize(paginatorInfo); - } else { + } else if (type === 'lambdaEvent') { this.setLambdaEventPaginatorSize(paginatorInfo); + } else { + this.setVersionPaginatorSize(paginatorInfo); } } @@ -44,4 +46,12 @@ export class PaginatorService { }; }); } + setVersionPaginatorSize(paginatorInfo: PaginatorInfo) { + this.paginatorStore.update((state) => { + return { + ...state, + version: paginatorInfo, + }; + }); + } } diff --git a/src/app/shared/state/paginator.store.ts b/src/app/shared/state/paginator.store.ts index ed503eb77..b6001a9aa 100644 --- a/src/app/shared/state/paginator.store.ts +++ b/src/app/shared/state/paginator.store.ts @@ -5,6 +5,7 @@ export interface PaginatorState { tool: PaginatorInfo; workflow: PaginatorInfo; lambdaEvent: PaginatorInfo; + version: PaginatorInfo; } export interface PaginatorInfo { @@ -16,6 +17,7 @@ const initialState: PaginatorState = { tool: { pageSize: 10, pageIndex: 0 }, workflow: { pageSize: 10, pageIndex: 0 }, lambdaEvent: { pageSize: 10, pageIndex: 0 }, + version: { pageSize: 10, pageIndex: 0 }, }; @Injectable({ providedIn: 'root' }) diff --git a/src/app/workflow/versions/versions-datasource.ts b/src/app/workflow/versions/versions-datasource.ts new file mode 100644 index 000000000..47ce04f55 --- /dev/null +++ b/src/app/workflow/versions/versions-datasource.ts @@ -0,0 +1,80 @@ +/* + * Copyright 2024 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 { CollectionViewer, DataSource } from '@angular/cdk/collections'; +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { finalize } from 'rxjs/operators'; +import { WorkflowsService, WorkflowVersion } from '../../shared/openapi'; +import { HttpResponse } from '@angular/common/http'; + +@Injectable() +export class VersionsDataSource implements DataSource { + private versionsSubject$ = new BehaviorSubject([]); + private loadingSubject$ = new BehaviorSubject(false); + public versionsLengthSubject$ = new BehaviorSubject(0); + public loading$ = this.loadingSubject$.asObservable(); + + constructor(private workflowsService: WorkflowsService) {} + + /** + * Updates the datasource from the endpoint + * @param {boolean} publicPage Boolean for if we are loading versions for the public page + * @param {workflowId} workflowId Id of parent workflow + * @param {string} sortDirection "asc" or "desc" + * @param {number} pageIndex The entry number to start listing from (page size * page number) + * @param {number} pageSize The page size (number of tools to return) + * @param {string} sortCol The column to sort by ("stars") + * @memberof PublishedWorkflowsDataSource + */ + loadVersions( + publicPage: boolean, + workflowId: number, + sortDirection: 'asc' | 'desc', + pageIndex: number, + pageSize: number, + sortCol: string + ) { + this.loadingSubject$.next(true); + let workflowVersions: Observable>; + + if (publicPage) { + workflowVersions = this.workflowsService.getPublicWorkflowVersions( + workflowId, + pageSize, + pageIndex, + sortCol, + sortDirection, + 'response' + ); + } else { + workflowVersions = this.workflowsService.getWorkflowVersions(workflowId, pageSize, pageIndex, sortCol, sortDirection, 'response'); + } + + workflowVersions.pipe(finalize(() => this.loadingSubject$.next(false))).subscribe((versions) => { + this.versionsSubject$.next(versions.body); + this.versionsLengthSubject$.next(Number(versions.headers.get('X-total-count'))); + }); + } + + connect(collectionViewer: CollectionViewer): Observable { + return this.versionsSubject$.asObservable(); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.versionsSubject$.complete(); + this.loadingSubject$.complete(); + } +} diff --git a/src/app/workflow/versions/versions.component.html b/src/app/workflow/versions/versions.component.html index 993f040f0..e5d6237fa 100644 --- a/src/app/workflow/versions/versions.component.html +++ b/src/app/workflow/versions/versions.component.html @@ -88,6 +88,7 @@ disableClear matTooltip="Date of last update to Git reference" matTooltipPosition="above" + data-cy="date-modified-header" > Date Modified @@ -207,7 +208,6 @@ scope="col" mat-header-cell *matHeaderCellDef - mat-sort-header matTooltip="The version has has execution and/or validation metrics." matTooltipPosition="above" > @@ -296,4 +296,11 @@ data-cy="versionRow" > + diff --git a/src/app/workflow/versions/versions.component.ts b/src/app/workflow/versions/versions.component.ts index ca825a2a9..6f6425dcb 100644 --- a/src/app/workflow/versions/versions.component.ts +++ b/src/app/workflow/versions/versions.component.ts @@ -15,14 +15,14 @@ */ import { AfterViewInit, Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core'; import { MatSort, MatSortModule } from '@angular/material/sort'; -import { MatLegacyTableDataSource as MatTableDataSource, MatLegacyTableModule } from '@angular/material/legacy-table'; +import { MatLegacyTableModule } from '@angular/material/legacy-table'; import { faCodeBranch, faTag } from '@fortawesome/free-solid-svg-icons'; -import { takeUntil } from 'rxjs/operators'; +import { distinctUntilChanged, takeUntil, tap } from 'rxjs/operators'; import { AlertService } from '../../shared/alert/state/alert.service'; import { DateService } from '../../shared/date.service'; import { Dockstore } from '../../shared/dockstore.model'; import { DockstoreService } from '../../shared/dockstore.service'; -import { Doi, EntryType, VersionVerifiedPlatform } from '../../shared/openapi'; +import { Doi, EntryType, VersionVerifiedPlatform, WorkflowsService } from '../../shared/openapi'; import { ExtendedWorkflow } from '../../shared/models/ExtendedWorkflow'; import { SessionQuery } from '../../shared/session/session.query'; import { ExtendedWorkflowQuery } from '../../shared/state/extended-workflow.query'; @@ -36,11 +36,15 @@ import { ExtendedModule } from '@ngbracket/ngx-layout/extended'; import { ViewWorkflowComponent } from '../view/view.component'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { MatLegacyChipsModule } from '@angular/material/legacy-chips'; -import { NgIf, NgClass, NgFor, JsonPipe, DatePipe, KeyValuePipe, KeyValue } from '@angular/common'; +import { NgIf, NgClass, NgFor, JsonPipe, DatePipe, KeyValuePipe, KeyValue, AsyncPipe } from '@angular/common'; import { FlexModule } from '@ngbracket/ngx-layout/flex'; import { MatIconModule } from '@angular/material/icon'; import { MatLegacyTooltipModule } from '@angular/material/legacy-tooltip'; import { DoiBadgeComponent } from 'app/shared/entry/doi/doi-badge/doi-badge.component'; +import { PaginatorService } from '../../shared/state/paginator.service'; +import { merge, Observable } from 'rxjs'; +import { MatLegacyPaginator as MatPaginator, MatLegacyPaginatorModule } from '@angular/material/legacy-paginator'; +import { VersionsDataSource } from './versions-datasource'; @Component({ selector: 'app-versions-workflow', @@ -67,14 +71,17 @@ import { DoiBadgeComponent } from 'app/shared/entry/doi/doi-badge/doi-badge.comp CommitUrlPipe, KeyValuePipe, DoiBadgeComponent, + AsyncPipe, + MatLegacyPaginatorModule, ], }) export class VersionsWorkflowComponent extends Versions implements OnInit, OnChanges, AfterViewInit { faTag = faTag; faCodeBranch = faCodeBranch; - @Input() versions: Array; + versions: Array; @Input() workflowId: number; @Input() verifiedVersionPlatforms: Array; + @Input() publicPage: boolean; _selectedVersion: WorkflowVersion; Dockstore = Dockstore; @Input() set selectedVersion(value: WorkflowVersion) { @@ -82,13 +89,20 @@ export class VersionsWorkflowComponent extends Versions implements OnInit, OnCha this._selectedVersion = value; } } - dataSource = new MatTableDataSource(this.versions); + dataSource: VersionsDataSource; @Output() selectedVersionChange = new EventEmitter(); - @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatSort, { static: true }) sort: MatSort; + @ViewChild(MatPaginator, { static: true }) protected paginator: MatPaginator; public WorkflowType = Workflow; workflow: ExtendedWorkflow; entryType = EntryType; DoiInitiatorEnum = Doi.InitiatorEnum; + type: 'workflow' | 'tool' | 'lambdaEvent' | 'version' = 'version'; + public pageSize$: Observable; + public pageIndex$: Observable; + public versionsLength$: Observable; + private sortCol: string; + setNoOrderCols(): Array { return [4, 5]; } @@ -97,6 +111,8 @@ export class VersionsWorkflowComponent extends Versions implements OnInit, OnCha dockstoreService: DockstoreService, dateService: DateService, private alertService: AlertService, + private paginatorService: PaginatorService, + private workflowsService: WorkflowsService, private extendedWorkflowQuery: ExtendedWorkflowQuery, protected sessionQuery: SessionQuery ) { @@ -135,11 +151,39 @@ export class VersionsWorkflowComponent extends Versions implements OnInit, OnCha } ngAfterViewInit(): void { - this.dataSource.sort = this.sort; + setTimeout(() => { + // Handle paginator changes + merge(this.paginator.page) + .pipe( + distinctUntilChanged(), + tap(() => this.loadVersions(this.publicPage)), + takeUntil(this.ngUnsubscribe) + ) + .subscribe(() => this.paginatorService.setPaginator(this.type, this.paginator.pageSize, this.paginator.pageIndex)); + + // Handle sort changes + this.sort.sortChange + .pipe( + tap(() => { + this.paginator.pageIndex = 0; // go back to first page after changing sort + if (this.sort.active === 'last_modified') { + this.sortCol = 'lastModified'; + } else { + this.sortCol = this.sort.active; + } + if (this.sort.direction === '') { + this.sortCol = null; + } + this.loadVersions(this.publicPage); + }), + takeUntil(this.ngUnsubscribe) + ) + .subscribe(); + }); } ngOnChanges() { - this.dataSource.data = this.versions; + this.loadVersions(this.publicPage); } ngOnInit() { @@ -148,18 +192,35 @@ export class VersionsWorkflowComponent extends Versions implements OnInit, OnCha if (workflow) { this.defaultVersion = workflow.defaultVersion; } - this.dtOptions = { - bFilter: false, - bPaginate: false, - columnDefs: [ - { - orderable: false, - targets: this.setNoOrderCols(), - }, - ], - }; this.publicPageSubscription(); }); + this.dataSource = new VersionsDataSource(this.workflowsService); + this.versionsLength$ = this.dataSource.versionsLengthSubject$; + } + + loadVersions(publicPage: boolean) { + let direction: 'asc' | 'desc'; + switch (this.sort.direction) { + case 'asc': { + direction = 'asc'; + break; + } + case 'desc': { + direction = 'desc'; + break; + } + default: { + direction = 'desc'; + } + } + this.dataSource.loadVersions( + publicPage, + this.workflowId, + direction, + this.paginator.pageIndex * this.paginator.pageSize, + this.paginator.pageSize, + this.sortCol + ); } /** diff --git a/src/app/workflow/workflow.component.html b/src/app/workflow/workflow.component.html index 575d09099..972154f9b 100644 --- a/src/app/workflow/workflow.component.html +++ b/src/app/workflow/workflow.component.html @@ -334,11 +334,11 @@