diff --git a/frontend/src/__tests__/cypress/cypress/e2e/pipelines/PipelineRuns.cy.ts b/frontend/src/__tests__/cypress/cypress/e2e/pipelines/PipelineRuns.cy.ts index 1e6a7e2d6e..94128cf585 100644 --- a/frontend/src/__tests__/cypress/cypress/e2e/pipelines/PipelineRuns.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/e2e/pipelines/PipelineRuns.cy.ts @@ -32,6 +32,7 @@ import { ProjectModel, RouteModel, } from '~/__tests__/cypress/cypress/utils/models'; +import { tablePagination } from '~/__tests__/cypress/cypress/pages/components/Pagination'; const projectName = 'test-project-filters'; const pipelineId = 'test-pipeline'; @@ -170,6 +171,112 @@ describe('Pipeline runs', () => { }); }); + describe('table pagination', () => { + it('Active run table pagination', () => { + const mockRuns = Array.from({ length: 15 }, (_, i) => + buildMockRunKF({ + display_name: `Test active run-${i}`, + run_id: `run-${i}`, + pipeline_version_reference: { + pipeline_id: pipelineId, + pipeline_version_id: `test-version-${i}`, + }, + experiment_id: `test-experiment-${i}`, + created_at: '2024-02-05T00:00:00Z', + state: RuntimeStateKF.SUCCEEDED, + }), + ); + + cy.intercept( + { + method: 'GET', + pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/runs`, + }, + { + runs: mockRuns.slice(0, 10), + total_size: 15, + next_page_token: 'page-2-token', + }, + ).as('getActiveRuns'); + + pipelineRunsGlobal.visit(projectName, 'active'); + + cy.wait('@getActiveRuns').then((interception) => { + expect(interception.request.query).to.eql({ + sort_by: 'created_at desc', + page_size: '10', + filter: + '{"predicates":[{"key":"storage_state","operation":"EQUALS","string_value":"AVAILABLE"}]}', + }); + }); + activeRunsTable.findRows().should('have.length', 10); + activeRunsTable.getRowByName('Test active run-0').find().should('exist'); + + const pagination = tablePagination.top; + + // test Next button + pagination.findPreviousButton().should('be.disabled'); + cy.intercept( + { + method: 'GET', + pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/runs`, + }, + { + runs: mockRuns.slice(10, 15), + total_size: 15, + }, + ).as('refreshActiveRuns'); + pagination.findNextButton().click(); + + cy.wait('@refreshActiveRuns').then((interception) => { + expect(interception.request.query).to.eql({ + sort_by: 'created_at desc', + page_size: '10', + filter: + '{"predicates":[{"key":"storage_state","operation":"EQUALS","string_value":"AVAILABLE"}]}', + page_token: 'page-2-token', + }); + }); + activeRunsTable.getRowByName('Test active run-14').find().should('exist'); + activeRunsTable.findRows().should('have.length', 5); + + // test Previous button + pagination.findNextButton().should('be.disabled'); + cy.intercept( + { + method: 'GET', + pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/runs`, + }, + { + runs: mockRuns.slice(0, 10), + total_size: 15, + }, + ); + pagination.findPreviousButton().click(); + activeRunsTable.getRowByName('Test active run-0').find().should('exist'); + activeRunsTable.findRows().should('have.length', 10); + + // 20 per page + cy.intercept( + { + method: 'GET', + pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/runs`, + }, + { + runs: mockRuns.slice(0, 15), + total_size: 15, + }, + ); + pagination.selectToggleOption('20 per page'); + activeRunsTable.findRows().should('have.length', 15); + activeRunsTable.getRowByName('Test active run-0').find().should('exist'); + activeRunsTable.getRowByName('Test active run-14').find().should('exist'); + pagination.findNextButton().should('be.disabled'); + pagination.findPreviousButton().should('be.disabled'); + pagination.selectToggleOption('10 per page'); + }); + }); + describe('with data', () => { beforeEach(() => { activeRunsTable.mockGetActiveRuns(mockActiveRuns, projectName); @@ -528,14 +635,14 @@ describe('Pipeline runs', () => { // Mock runs (filtered by typed run name) archivedRunsTable.mockGetArchivedRuns( - mockActiveRuns.filter((mockRun) => mockRun.display_name.includes('run 1')), + mockArchivedRuns.filter((mockRun) => mockRun.display_name.includes('run 1')), projectName, 1, ); // Verify only rows with the typed run name exist archivedRunsTable.findRows().should('have.length', 1); - archivedRunsTable.getRowByName('Test active run 1').find().should('exist'); + archivedRunsTable.getRowByName('Test archived run 1').find().should('exist'); }); it('filter by experiment', () => { @@ -713,6 +820,160 @@ describe('Pipeline runs', () => { pipelineRunJobTable.findEmptyState().should('exist'); }); + describe('table pagination', () => { + it('Scheduled run table pagination', () => { + const mockJobRuns = Array.from({ length: 15 }, (_, i) => + buildMockJobKF({ + display_name: `another-pipeline-${i}`, + recurring_run_id: `another-test-pipeline-${i}`, + experiment_id: `test-experiment-${i}`, + pipeline_version_reference: { + pipeline_id: pipelineId, + pipeline_version_id: `test-version-${i}`, + }, + }), + ); + + cy.intercept( + { + method: 'GET', + pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/recurringruns`, + }, + { + recurringRuns: mockJobRuns.slice(0, 10), + total_size: 15, + next_page_token: 'page-2-token', + }, + ).as('getScheduledRuns'); + pipelineRunsGlobal.visit(projectName, 'scheduled'); + + cy.wait('@getScheduledRuns').then((interception) => { + expect(interception.request.query).to.eql({ + sort_by: 'created_at desc', + page_size: '10', + }); + }); + + pipelineRunJobTable.getRowByName('another-pipeline-0').find().should('exist'); + pipelineRunJobTable.findRows().should('have.length', 10); + + const pagination = tablePagination.top; + + // test Next button + pagination.findFirstButton().should('be.disabled'); + pagination.findPreviousButton().should('be.disabled'); + cy.intercept( + { + method: 'GET', + pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/recurringruns`, + }, + { + recurringRuns: mockJobRuns.slice(10, 15), + total_size: 15, + }, + ).as('refreshScheduledRuns'); + pagination.findNextButton().click(); + + cy.wait('@refreshScheduledRuns').then((interception) => { + expect(interception.request.query).to.eql({ + sort_by: 'created_at desc', + page_size: '10', + page_token: 'page-2-token', + }); + }); + + pagination.findInput().should('have.value', '2'); + pipelineRunJobTable.getRowByName('another-pipeline-14').find().should('exist'); + pipelineRunJobTable.findRows().should('have.length', 5); + + //test first button + pagination.findLastButton().should('be.disabled'); + pagination.findNextButton().should('be.disabled'); + cy.intercept( + { + method: 'GET', + pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/recurringruns`, + }, + { + recurringRuns: mockJobRuns.slice(0, 10), + total_size: 15, + next_page_token: 'new-page-token', + }, + ); + pagination.findFirstButton().click(); + pagination.findInput().should('have.value', '1'); + pipelineRunJobTable.getRowByName('another-pipeline-0').find().should('exist'); + pipelineRunJobTable.findRows().should('have.length', 10); + + //test last button + pagination.findFirstButton().should('be.disabled'); + pagination.findPreviousButton().should('be.disabled'); + cy.intercept( + { + method: 'GET', + pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/recurringruns`, + }, + { + recurringRuns: mockJobRuns.slice(10, 15), + total_size: 15, + }, + ).as('refreshPipelineRunJobs'); + + pagination.findLastButton().click(); + pagination.findInput().should('have.value', Math.ceil(15 / 10)); + pipelineRunJobTable.getRowByName('another-pipeline-14').find().should('exist'); + pipelineRunJobTable.findRows().should('have.length', 5); + + cy.wait('@refreshPipelineRunJobs').then((interception) => { + expect(interception.request.query).to.eql({ + sort_by: 'created_at desc', + page_size: '10', + page_token: 'new-page-token', + }); + }); + + // test Previous button + pagination.findLastButton().should('be.disabled'); + pagination.findNextButton().should('be.disabled'); + cy.intercept( + { + method: 'GET', + pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/recurringruns`, + }, + { + recurringRuns: mockJobRuns.slice(0, 10), + total_size: 15, + }, + ); + pagination.findPreviousButton().click(); + pagination.findInput().should('have.value', '1'); + pipelineRunJobTable.getRowByName('another-pipeline-0').find().should('exist'); + pipelineRunJobTable.findRows().should('have.length', 10); + + // 20 per page + cy.intercept( + { + method: 'GET', + pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/recurringruns`, + }, + { + recurringRuns: mockJobRuns.slice(0, 15), + total_size: 15, + }, + ); + pagination.selectToggleOption('20 per page'); + + pipelineRunJobTable.getRowByName('another-pipeline-0').find().should('exist'); + pipelineRunJobTable.getRowByName('another-pipeline-14').find().should('exist'); + pipelineRunJobTable.findRows().should('have.length', 15); + pagination.findLastButton().should('be.disabled'); + pagination.findNextButton().should('be.disabled'); + pagination.findPreviousButton().should('be.disabled'); + pagination.findFirstButton().should('be.disabled'); + pagination.findInput().should('have.value', Math.ceil(15 / 20)); + }); + }); + describe('with data', () => { beforeEach(() => { pipelineRunJobTable.mockGetJobs(mockJobs, projectName); diff --git a/frontend/src/__tests__/cypress/cypress/e2e/pipelines/Pipelines.cy.ts b/frontend/src/__tests__/cypress/cypress/e2e/pipelines/Pipelines.cy.ts index bf658375ca..9d5a655be3 100644 --- a/frontend/src/__tests__/cypress/cypress/e2e/pipelines/Pipelines.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/e2e/pipelines/Pipelines.cy.ts @@ -29,6 +29,7 @@ import { asProductAdminUser } from '~/__tests__/cypress/cypress/utils/users'; import { mockSecretK8sResource } from '~/__mocks__/mockSecretK8sResource'; import { PipelineKFv2 } from '~/concepts/pipelines/kfTypes'; import { be } from '~/__tests__/cypress/cypress/utils/should'; +import { tablePagination } from '~/__tests__/cypress/cypress/pages/components/Pagination'; const projectName = 'test-project-name'; const initialMockPipeline = buildMockPipelineV2({ display_name: 'Test pipeline' }); @@ -991,16 +992,101 @@ describe('Pipelines', () => { .click(); verifyRelativeURL(`/pipelineRuns/${projectName}?runType=scheduled`); }); + + it('Table pagination', () => { + const mockPipelinesv2 = Array.from({ length: 25 }, (_, i) => + buildMockPipelineV2({ + display_name: `Test pipeline-${i}`, + }), + ); + initIntercepts({ + mockPipelines: mockPipelinesv2.slice(0, 10), + totalSize: 25, + nextPageToken: 'page-2-token', + }); + pipelinesGlobal.visit(projectName); + + cy.wait('@getPipelines').then((interception) => { + expect(interception.request.query).to.eql({ + sort_by: 'created_at desc', + page_size: '10', + }); + }); + + pipelinesTable.getRowByName('Test pipeline-0').find().should('exist'); + pipelinesTable.findRows().should('have.length', '10'); + + const pagination = tablePagination.top; + + // test Next button + pagination.findPreviousButton().should('be.disabled'); + cy.intercept( + { + method: 'GET', + pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/pipelines`, + }, + buildMockPipelines(mockPipelinesv2.slice(10, 20), 25), + ).as('refreshPipelines'); + pagination.findNextButton().click(); + + cy.wait('@refreshPipelines').then((interception) => { + expect(interception.request.query).to.eql({ + sort_by: 'created_at desc', + page_size: '10', + page_token: 'page-2-token', + }); + }); + + pipelinesTable.getRowByName('Test pipeline-10').find().should('exist'); + pipelinesTable.findRows().should('have.length', '10'); + + // test Previous button + cy.intercept( + { + method: 'GET', + pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/pipelines`, + }, + buildMockPipelines(mockPipelinesv2.slice(0, 10), 25), + ).as('getFirstTenPipelines'); + + pagination.findPreviousButton().click(); + + cy.wait('@getFirstTenPipelines').then((interception) => { + expect(interception.request.query).to.eql({ + sort_by: 'created_at desc', + page_size: '10', + }); + }); + + pipelinesTable.getRowByName('Test pipeline-0').find().should('exist'); + + // 20 per page + cy.intercept( + { + method: 'GET', + pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/pipelines`, + }, + buildMockPipelines(mockPipelinesv2.slice(0, 20), 22), + ); + pagination.selectToggleOption('20 per page'); + pagination.findPreviousButton().should('be.disabled'); + pipelinesTable.getRowByName('Test pipeline-19').find().should('exist'); + pipelinesTable.findRows().should('have.length', '20'); + }); }); type HandlersProps = { isEmpty?: boolean; mockPipelines?: PipelineKFv2[]; + totalSize?: number; + nextPageToken?: string | undefined; }; const initIntercepts = ({ isEmpty = false, mockPipelines = [initialMockPipeline], + totalSize = mockPipelines.length, + nextPageToken, }: HandlersProps) => { cy.interceptK8sList( DataSciencePipelineApplicationModel, @@ -1034,8 +1120,8 @@ const initIntercepts = ({ { pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/pipelines`, }, - buildMockPipelines(mockPipelines), - ); + buildMockPipelines(mockPipelines, totalSize, nextPageToken), + ).as('getPipelines'); cy.intercept( {