diff --git a/projects/frontend/data-pipelines/gui/e2e/fixtures/e2e-cy-dp-failing.job.json b/projects/frontend/data-pipelines/gui/e2e/fixtures/e2e-cy-dp-failing.job.json
new file mode 100644
index 0000000000..b1019e21ee
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/fixtures/e2e-cy-dp-failing.job.json
@@ -0,0 +1,19 @@
+{
+ "job_name": "e2e-cp-$env-placeholder$-failing-job",
+ "description": "Testing failing job, that is used in data-pipelines CI/CD for UI e2e tests. It is intended to be always available in order to speed up deployment and if deleted will be automatically re-created by the e2e tests.",
+ "config": {
+ "db_default_type": "IMPALA",
+ "contacts": {
+ "notified_on_job_failure_user_error": [],
+ "notified_on_job_failure_platform_error": [],
+ "notified_on_job_success": [],
+ "notified_on_job_deploy": []
+ },
+ "schedule": {
+ "schedule_cron": "0 */12 * * *"
+ },
+ "generate_keytab": true,
+ "enable_execution_notifications": true
+ },
+ "team": "taurus"
+}
diff --git a/projects/frontend/data-pipelines/gui/e2e/fixtures/e2e-cy-dp-failing.job.zip b/projects/frontend/data-pipelines/gui/e2e/fixtures/e2e-cy-dp-failing.job.zip
new file mode 100644
index 0000000000..a6c08016ee
Binary files /dev/null and b/projects/frontend/data-pipelines/gui/e2e/fixtures/e2e-cy-dp-failing.job.zip differ
diff --git a/projects/frontend/data-pipelines/gui/e2e/fixtures/lib/explore/additional-test-job.json b/projects/frontend/data-pipelines/gui/e2e/fixtures/lib/explore/additional-test-job.json
new file mode 100644
index 0000000000..198165b0ee
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/fixtures/lib/explore/additional-test-job.json
@@ -0,0 +1,27 @@
+{
+ "config": {
+ "contacts": {
+ "notified_on_job_deploy": [
+ "auserov3@vmware.com"
+ ],
+ "notified_on_job_failure_platform_error": [
+ "buserov3@vmware.com"
+ ],
+ "notified_on_job_failure_user_error": [
+ "cuserov3@vmware.com"
+ ],
+ "notified_on_job_success": [
+ "duserov3@vmware.com"
+ ]
+ },
+ "db_default_type": "IMPALA",
+ "generate_keytab": false,
+ "schedule": {
+ "schedule_cron": "0 0 03 * 5"
+ }
+ },
+ "description": "Test description 3 with a long text on the line",
+ "job_name": "e2e-cp-$env-placeholder$-a-test-job-3",
+ "status": "disabled",
+ "team": "e2e-cp-$env-placeholder$-a-test-team-3"
+}
diff --git a/projects/frontend/data-pipelines/gui/e2e/fixtures/lib/explore/test-jobs.json b/projects/frontend/data-pipelines/gui/e2e/fixtures/lib/explore/test-jobs.json
new file mode 100644
index 0000000000..5ce9ba5d2b
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/fixtures/lib/explore/test-jobs.json
@@ -0,0 +1,56 @@
+[
+ {
+ "config": {
+ "contacts": {
+ "notified_on_job_deploy": [
+ "auserov1@vmware.com"
+ ],
+ "notified_on_job_failure_platform_error": [
+ "buserov1@vmware.com"
+ ],
+ "notified_on_job_failure_user_error": [
+ "cuserov1@vmware.com"
+ ],
+ "notified_on_job_success": [
+ "duserov1@vmware.com"
+ ]
+ },
+ "db_default_type": "IMPALA",
+ "generate_keytab": false,
+ "schedule": {
+ "schedule_cron": "0 0 01 * 5"
+ }
+ },
+ "description": "Test description 1 with a long text on the line",
+ "job_name": "e2e-cp-$env-placeholder$-a-test-job-1",
+ "status": "disabled",
+ "team": "e2e-cp-$env-placeholder$-a-test-team-1"
+ },
+ {
+ "config": {
+ "contacts": {
+ "notified_on_job_deploy": [
+ "auserov2@vmware.com"
+ ],
+ "notified_on_job_failure_platform_error": [
+ "buserov2@vmware.com"
+ ],
+ "notified_on_job_failure_user_error": [
+ "cuserov2@vmware.com"
+ ],
+ "notified_on_job_success": [
+ "duserov2@vmware.com"
+ ]
+ },
+ "db_default_type": "IMPALA",
+ "generate_keytab": false,
+ "schedule": {
+ "schedule_cron": "0 0 02 * 5"
+ }
+ },
+ "description": "Test description 2 with a long text on the line",
+ "job_name": "e2e-cp-$env-placeholder$-a-test-job-2",
+ "status": "disabled",
+ "team": "e2e-cp-$env-placeholder$-a-test-team-2"
+ }
+]
diff --git a/projects/frontend/data-pipelines/gui/e2e/fixtures/lib/manage/e2e-cypress-dp-test.json b/projects/frontend/data-pipelines/gui/e2e/fixtures/lib/manage/e2e-cypress-dp-test.json
new file mode 100644
index 0000000000..fd38f2664f
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/fixtures/lib/manage/e2e-cypress-dp-test.json
@@ -0,0 +1,19 @@
+{
+ "job_name": "e2e-cp-$env-placeholder$-test-job",
+ "description": "Testing job, that is used in data-pipelines CI/CD for UI e2e tests. It is intended to be always available in order to speed up deployment and if deleted will be automatically re-created by the e2e tests." ,
+ "config": {
+ "db_default_type": "IMPALA" ,
+ "contacts": {
+ "notified_on_job_failure_user_error": [] ,
+ "notified_on_job_failure_platform_error": [] ,
+ "notified_on_job_success": [] ,
+ "notified_on_job_deploy": []
+ } ,
+ "schedule": {
+ "schedule_cron": "11 23 5 8 1"
+ } ,
+ "generate_keytab": true ,
+ "enable_execution_notifications": true
+ } ,
+ "team": "taurus"
+}
diff --git a/projects/frontend/data-pipelines/gui/e2e/fixtures/lib/manage/e2e-cypress-dp-test.zip b/projects/frontend/data-pipelines/gui/e2e/fixtures/lib/manage/e2e-cypress-dp-test.zip
new file mode 100644
index 0000000000..a772e15b56
Binary files /dev/null and b/projects/frontend/data-pipelines/gui/e2e/fixtures/lib/manage/e2e-cypress-dp-test.zip differ
diff --git a/projects/frontend/data-pipelines/gui/e2e/integration/app/app.spec.js b/projects/frontend/data-pipelines/gui/e2e/integration/app/app.spec.js
new file mode 100644
index 0000000000..fa088eef37
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/integration/app/app.spec.js
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+///
+
+import { AppPage } from '../../support/pages/app/app.po';
+
+describe('App Page', { tags: ['@dataPipelines'] }, () => {
+ before(() => {
+ return AppPage.recordHarIfSupported()
+ .then(() => cy.clearLocalStorageSnapshot('app'))
+ .then(() => AppPage.login())
+ .then(() => cy.saveLocalStorage('app'));
+ });
+
+ after(() => {
+ AppPage.saveHarIfSupported();
+ });
+
+ beforeEach(() => {
+ cy.restoreLocalStorage('app');
+ });
+
+ it('App Page - Main Title Component have text: Data Pipelines', () => {
+ AppPage.navigateTo();
+
+ const page = AppPage.getPage();
+
+ page
+ .waitForInitialPageLoad();
+
+ page
+ .getMainTitle()
+ .should('have.text', 'Data Pipelines');
+ });
+});
diff --git a/projects/frontend/data-pipelines/gui/e2e/integration/app/getting-started/getting-started.spec.js b/projects/frontend/data-pipelines/gui/e2e/integration/app/getting-started/getting-started.spec.js
new file mode 100644
index 0000000000..6a3d354a5f
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/integration/app/getting-started/getting-started.spec.js
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+///
+import { DataPipelinesBasePO } from '../../../support/application/data-pipelines-base.po';
+import { GettingStartedPage } from '../../../support/pages/app/getting-started/getting-started.po';
+import { DataJobsHealthPanelComponentPO } from '../../../support/pages/app/getting-started/data-jobs-health-panel-component.po';
+import { DataJobManageDetailsPage } from '../../../support/pages/app/lib/manage/data-job-details.po';
+import { DataJobManageExecutionsPage } from '../../../support/pages/app/lib/manage/data-job-executions.po';
+import { applyGlobalEnvSettings } from '../../../support/helpers/commands.helpers';
+
+describe('Getting Started Page', { tags: ['@dataPipelines'] }, () => {
+ let testJob;
+
+ before(() => {
+ return DataPipelinesBasePO.recordHarIfSupported()
+ .then(() => cy.clearLocalStorageSnapshot('getting-started'))
+ .then(() => DataPipelinesBasePO.login())
+ .then(() => cy.saveLocalStorage('getting-started'))
+ .then(() => cy.prepareLongLivedFailingTestJob())
+ .then(() => cy.createExecutionsLongLivedFailingTestJob())
+ .then(() => cy.fixture('e2e-cy-dp-failing.job.json'))
+ .then((failingTestJob) => {
+ testJob = applyGlobalEnvSettings(failingTestJob);
+
+ return cy.wrap({ context: 'getting-started.spec::before()', action: 'continue' });
+ });
+ });
+
+ after(() => {
+ DataPipelinesBasePO.saveHarIfSupported();
+ });
+
+ beforeEach(() => {
+ cy.restoreLocalStorage('getting-started');
+
+ DataPipelinesBasePO.initBackendRequestInterceptor();
+ });
+
+ it('Main Title Component have text: Get Started with Data Pipelines', () => {
+ GettingStartedPage
+ .navigateTo()
+ .getMainTitle()
+ .should('have.text', 'Get Started with Data Pipelines');
+ });
+
+ describe('Data Jobs Health Overview Panel', () => {
+ it('Verify Widgets rendered correct data and failing jobs navigates correctly', () => {
+ GettingStartedPage
+ .navigateTo();
+
+ let dataJobsHealthPanel = DataJobsHealthPanelComponentPO
+ .getComponent();
+ dataJobsHealthPanel
+ .waitForViewToRender();
+ dataJobsHealthPanel
+ .getDataJobsHealthPanel()
+ .scrollIntoView();
+
+ dataJobsHealthPanel
+ .getExecutionsSuccessPercentage()
+ .should('be.gte', 0)
+ .should('be.lte', 100);
+ dataJobsHealthPanel
+ .getNumberOfFailedExecutions()
+ .should('be.gte', 2);
+ dataJobsHealthPanel
+ .getExecutionsTotal()
+ .should('be.gte', 2);
+
+ dataJobsHealthPanel
+ .getAllFailingJobs()
+ .should('have.length.gte', 1);
+
+ dataJobsHealthPanel
+ .getAllMostRecentFailingJobsExecutions()
+ .should('have.length.gte', 1);
+
+ // navigate to failing job details
+ dataJobsHealthPanel
+ .navigateToFailingJobDetails(testJob.job_name);
+
+ const dataJobManageDetailsPage = DataJobManageDetailsPage
+ .getPage();
+ dataJobManageDetailsPage
+ .getMainTitle()
+ .should('contain.text', testJob.job_name);
+ dataJobManageDetailsPage
+ .getDetailsTab()
+ .should('be.visible')
+ .should('have.class', 'active');
+ dataJobManageDetailsPage
+ .getExecutionsTab()
+ .should('exist')
+ .should('not.have.class', 'active');
+ dataJobManageDetailsPage
+ .showMoreDescription()
+ .getDescriptionFull()
+ .should('contain.text', testJob.description);
+ });
+
+ it('Verify most recent failing executions Widget navigates correctly', () => {
+ GettingStartedPage
+ .navigateTo();
+
+ let dataJobsHealthPanel = DataJobsHealthPanelComponentPO
+ .getComponent();
+ dataJobsHealthPanel
+ .waitForViewToRender();
+ dataJobsHealthPanel
+ .getDataJobsHealthPanel()
+ .scrollIntoView();
+
+ dataJobsHealthPanel
+ .getAllMostRecentFailingJobsExecutions()
+ .should('have.length.gte', 1);
+
+ // navigate to most recent failing job executions
+ dataJobsHealthPanel
+ .navigateToMostRecentFailingJobExecutions(testJob.job_name);
+
+ const dataJobManageExecutionsPage = DataJobManageExecutionsPage
+ .getPage();
+ dataJobManageExecutionsPage
+ .getMainTitle()
+ .should('contain.text', testJob.job_name);
+ dataJobManageExecutionsPage
+ .getDetailsTab()
+ .should('be.visible')
+ .should('not.have.class', 'active');
+ dataJobManageExecutionsPage
+ .getExecutionsTab()
+ .should('be.visible')
+ .should('have.class', 'active');
+ dataJobManageExecutionsPage
+ .getDataGridRows()
+ .should('have.length.gte', 1);
+ });
+ });
+});
diff --git a/projects/frontend/data-pipelines/gui/e2e/integration/lib/explore/data-job-details.spec.js b/projects/frontend/data-pipelines/gui/e2e/integration/lib/explore/data-job-details.spec.js
new file mode 100644
index 0000000000..e876c43134
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/integration/lib/explore/data-job-details.spec.js
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+///
+import { DataJobsExplorePage } from '../../../support/pages/app/lib/explore/data-jobs.po';
+import { DataJobExploreDetailsPage } from '../../../support/pages/app/lib/explore/data-job-details.po';
+import { applyGlobalEnvSettings } from '../../../support/helpers/commands.helpers';
+
+describe('Data Job Explore Details Page', { tags: ['@dataPipelines', '@exploreDataJobDetails'] }, () => {
+ let dataJobExploreDetailsPage;
+ let testJobs;
+
+ before(() => {
+ return DataJobExploreDetailsPage.recordHarIfSupported()
+ .then(() => cy.clearLocalStorageSnapshot('data-job-explore-details'))
+ .then(() => DataJobExploreDetailsPage.login())
+ .then(() => cy.saveLocalStorage('data-job-explore-details'))
+ .then(() => cy.cleanTestJobs())
+ .then(() => cy.prepareBaseTestJobs())
+ .then(() => cy.fixture('lib/explore/test-jobs.json'))
+ .then((loadedTestJobs) => {
+ testJobs = applyGlobalEnvSettings(loadedTestJobs);
+
+ return cy.wrap({ context: 'explore::data-job-details.spec::before()', action: 'continue' });
+ });
+ });
+
+ after(() => {
+ cy.cleanTestJobs();
+
+ DataJobExploreDetailsPage.saveHarIfSupported();
+ });
+
+ beforeEach(() => {
+ cy.restoreLocalStorage('data-job-explore-details');
+
+ DataJobExploreDetailsPage.initBackendRequestInterceptor();
+ });
+
+ it('Data Job Explore Details Page - should load and show job details', () => {
+ cy.log('Fixture for name: ' + testJobs[0].job_name);
+
+ const dataJobsExplorePage = DataJobsExplorePage.navigateTo();
+
+ dataJobsExplorePage
+ .openJobDetails(testJobs[0].team, testJobs[0].job_name);
+
+ dataJobExploreDetailsPage = DataJobExploreDetailsPage
+ .getPage();
+
+ dataJobExploreDetailsPage
+ .getDetailsTab()
+ .should('be.visible');
+
+ dataJobExploreDetailsPage
+ .getMainTitle()
+ .should('be.visible')
+ .should('contains.text', testJobs[0].job_name);
+
+ dataJobExploreDetailsPage
+ .getStatusField()
+ .should('be.visible')
+ .should('have.text', 'Not Deployed');
+
+ dataJobExploreDetailsPage
+ .getDescriptionField()
+ .should('be.visible')
+ .should('contain.text', testJobs[0].description);
+
+ dataJobExploreDetailsPage
+ .getTeamField()
+ .should('be.visible')
+ .should('have.text', testJobs[0].team);
+
+ dataJobExploreDetailsPage
+ .getScheduleField()
+ .should('be.visible')
+ .should('contains.text', 'At 12:00 AM, on day 01 of the month, and on Friday');
+
+ // DISABLED Because there is no Source at the moment.
+ // dataJobExploreDetailsPage
+ // .getSourceField()
+ // .should('be.visible')
+ // .should('contains.text', 'http://host/data-jobs/' + testJobs[0].job_name)
+ // .should("have.attr", "href", 'http://host/data-jobs/' + testJobs[0].job_name);
+
+ dataJobExploreDetailsPage
+ .getOnDeployedField()
+ .should('be.visible')
+ .should('contains.text', testJobs[0].config.contacts.notified_on_job_deploy);
+
+ dataJobExploreDetailsPage
+ .getOnPlatformErrorField()
+ .should('be.visible')
+ .should('contains.text', testJobs[0].config.contacts.notified_on_job_failure_platform_error);
+
+ dataJobExploreDetailsPage
+ .getOnUserErrorField()
+ .should('be.visible')
+ .should('contains.text', testJobs[0].config.contacts.notified_on_job_failure_user_error);
+
+ dataJobExploreDetailsPage
+ .getOnSuccessField()
+ .should('be.visible')
+ .should('contains.text', testJobs[0].config.contacts.notified_on_job_success);
+ });
+
+ it('Data Job Explore Details Page - should verify Details tab is visible and active', () => {
+ cy.log('Fixture for name: ' + testJobs[0].job_name);
+
+ const dataJobsExplorePage = DataJobsExplorePage.navigateTo();
+
+ dataJobsExplorePage
+ .openJobDetails(testJobs[0].team, testJobs[0].job_name);
+
+ const dataJobExploreDetailsPage = DataJobExploreDetailsPage
+ .getPage();
+
+ dataJobExploreDetailsPage
+ .getMainTitle()
+ .should('be.visible')
+ .should('contains.text', testJobs[0].job_name);
+
+ dataJobExploreDetailsPage
+ .getDetailsTab()
+ .should('be.visible')
+ .should('have.class', 'active');
+ });
+
+ it('Data Job Explore Details Page - should verify Action buttons are not displayed', () => {
+ cy.log('Fixture for name: ' + testJobs[0].job_name);
+
+ const dataJobsExplorePage = DataJobsExplorePage.navigateTo();
+
+ dataJobsExplorePage
+ .openJobDetails(testJobs[0].team, testJobs[0].job_name);
+
+ const dataJobExploreDetailsPage = DataJobExploreDetailsPage
+ .getPage();
+
+ dataJobExploreDetailsPage
+ .getMainTitle()
+ .should('be.visible')
+ .should('contains.text', testJobs[0].job_name);
+
+ dataJobExploreDetailsPage
+ .getExecuteNowButton()
+ .should('not.exist');
+
+ dataJobExploreDetailsPage
+ .getActionDropdownBtn()
+ .should('not.exist');
+ });
+});
diff --git a/projects/frontend/data-pipelines/gui/e2e/integration/lib/explore/data-job-executions.spec.js b/projects/frontend/data-pipelines/gui/e2e/integration/lib/explore/data-job-executions.spec.js
new file mode 100644
index 0000000000..b346dfb456
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/integration/lib/explore/data-job-executions.spec.js
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+///
+
+import { DataJobsExplorePage } from '../../../support/pages/app/lib/explore/data-jobs.po';
+import { DataJobBasePO } from '../../../support/application/data-job-base.po';
+import { applyGlobalEnvSettings } from '../../../support/helpers/commands.helpers';
+
+describe('Data Job Explore Executions Page', { tags: ['@dataPipelines', '@exploreDataJobExecutions'] }, () => {
+ let testJobs;
+
+ before(() => {
+ return DataJobBasePO.recordHarIfSupported()
+ .then(() => cy.clearLocalStorageSnapshot('data-job-explore-executions'))
+ .then(() => DataJobBasePO.login())
+ .then(() => cy.saveLocalStorage('data-job-explore-executions'))
+ .then(() => cy.cleanTestJobs())
+ .then(() => cy.prepareBaseTestJobs())
+ .then(() => cy.fixture('lib/explore/test-jobs.json'))
+ .then((loadedTestJobs) => {
+ testJobs = applyGlobalEnvSettings(loadedTestJobs);
+
+ return cy.wrap({ context: 'explore::data-job-executions.spec::before()', action: 'continue' });
+ });
+ });
+
+ after(() => {
+ cy.cleanTestJobs();
+
+ DataJobBasePO.saveHarIfSupported();
+ });
+
+ beforeEach(() => {
+ cy.restoreLocalStorage('data-job-explore-executions');
+
+ DataJobBasePO.initBackendRequestInterceptor();
+ });
+
+ it(`Data Job Explore Executions Page - should open Details and verify Executions tab is not displayed`, () => {
+ cy.log('Fixture for name: ' + testJobs[0].job_name);
+
+ const dataJobsExplorePage = DataJobsExplorePage.navigateTo();
+
+ dataJobsExplorePage
+ .openJobDetails(testJobs[0].team, testJobs[0].job_name);
+
+ const dataJobBasePage = DataJobBasePO
+ .getPage();
+
+ dataJobBasePage
+ .getMainTitle()
+ .should('be.visible')
+ .should('contains.text', testJobs[0].job_name);
+
+ dataJobBasePage
+ .getDetailsTab()
+ .should('have.class', 'active');
+
+ dataJobBasePage
+ .getExecutionsTab()
+ .should('not.exist');
+ });
+
+ it('Data Job Explore Executions Page - should verify on URL navigate to Executions will redirect to Details', () => {
+ const dataJobBasePage = DataJobBasePO
+ .navigateToUrl(`/explore/data-jobs/${ testJobs[0].team }/${ testJobs[0].job_name }/executions`);
+
+ dataJobBasePage
+ .getMainTitle()
+ .should('be.visible')
+ .should('contains.text', testJobs[0].job_name);
+
+ dataJobBasePage
+ .getCurrentUrl()
+ .should('match', new RegExp(`\\/explore\\/data-jobs\\/${testJobs[0].team}\\/${testJobs[0].job_name}\\/details$`));
+
+ dataJobBasePage
+ .getDetailsTab()
+ .should('have.class', 'active');
+ });
+});
diff --git a/projects/frontend/data-pipelines/gui/e2e/integration/lib/explore/data-jobs.spec.js b/projects/frontend/data-pipelines/gui/e2e/integration/lib/explore/data-jobs.spec.js
new file mode 100644
index 0000000000..9877cef132
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/integration/lib/explore/data-jobs.spec.js
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+///
+import { DataJobsExplorePage } from '../../../support/pages/app/lib/explore/data-jobs.po';
+import { DataJobExploreDetailsPage } from '../../../support/pages/app/lib/explore/data-job-details.po';
+import { applyGlobalEnvSettings } from '../../../support/helpers/commands.helpers';
+
+describe('Data Jobs Explore Page', { tags: ['@dataPipelines', '@exploreDataJobs'] }, () => {
+ let dataJobsExplorePage;
+ let testJobs;
+
+ before(() => {
+ return DataJobsExplorePage.recordHarIfSupported()
+ .then(() => cy.clearLocalStorageSnapshot('data-jobs-explore'))
+ .then(() => DataJobsExplorePage.login())
+ .then(() => cy.saveLocalStorage('data-jobs-explore'))
+ .then(() => cy.cleanTestJobs())
+ .then(() => cy.prepareBaseTestJobs())
+ .then(() => cy.fixture('lib/explore/test-jobs.json'))
+ .then((loadedTestJobs) => {
+ testJobs = applyGlobalEnvSettings(loadedTestJobs);
+
+ return cy.wrap({ context: 'explore::data-jobs.spec::before()', action: 'continue' });
+ });
+ });
+
+ after(() => {
+ cy.cleanTestJobs();
+
+ DataJobsExplorePage.saveHarIfSupported();
+ });
+
+ beforeEach(() => {
+ cy.restoreLocalStorage('data-jobs-explore');
+
+ DataJobsExplorePage.initBackendRequestInterceptor();
+ });
+
+ it('Main Title Component have text: Explore Data Jobs', () => {
+ dataJobsExplorePage = DataJobsExplorePage.navigateTo();
+
+ dataJobsExplorePage
+ .getMainTitle()
+ .should('be.visible')
+ .should('have.text', 'Explore Data Jobs');
+ });
+
+ it('Data Jobs Explore Page - loaded and shows data jobs', () => {
+ dataJobsExplorePage = DataJobsExplorePage.navigateTo();
+
+ dataJobsExplorePage
+ .getDataGrid()
+ .should('be.visible');
+
+ testJobs.forEach((testJob) => {
+ cy.log('Fixture for name: ' + testJob.job_name);
+
+ dataJobsExplorePage
+ .getDataGridCell(testJob.job_name)
+ .scrollIntoView()
+ .should('be.visible');
+
+ dataJobsExplorePage
+ .getDataGridCell(testJob.team)
+ .should('be.visible');
+ })
+ });
+
+ it('Data Jobs Explore Page - filters data jobs', () => {
+ cy.log('Fixture for name: ' + testJobs[0].job_name);
+
+ dataJobsExplorePage = DataJobsExplorePage.navigateTo();
+
+ dataJobsExplorePage
+ .filterByJobName(testJobs[0].job_name);
+
+ dataJobsExplorePage
+ .getDataGridCell(testJobs[0].job_name)
+ .should('be.visible');
+
+ dataJobsExplorePage
+ .getDataGridCell(testJobs[1].job_name)
+ .should('not.exist');
+ });
+
+ it('Data Jobs Explore Page - refreshes data jobs', () => {
+ dataJobsExplorePage = DataJobsExplorePage.navigateTo();
+
+ cy.fixture('lib/explore/additional-test-job.json').then((additionalTestJob) => {
+ const normalizedTestJob = applyGlobalEnvSettings(additionalTestJob);
+
+ cy.log('Fixture for name: ' + normalizedTestJob.job_name);
+
+ dataJobsExplorePage
+ .getDataGridCell(testJobs[0].job_name)
+ .should('have.text', testJobs[0].job_name);
+
+ dataJobsExplorePage
+ .getDataGridCell(normalizedTestJob.job_name)
+ .should('not.exist');
+
+ cy.prepareAdditionalTestJobs();
+
+ dataJobsExplorePage
+ .refreshDataGrid();
+
+ dataJobsExplorePage
+ .filterByJobName(normalizedTestJob.job_name);
+
+ dataJobsExplorePage
+ .getDataGridCell(normalizedTestJob.job_name)
+ .should('have.text', normalizedTestJob.job_name);
+ });
+ });
+
+ it('Data Jobs Explore Page - searches data jobs', () => {
+ cy.log('Fixture for name: ' + testJobs[0].job_name);
+
+ dataJobsExplorePage = DataJobsExplorePage.navigateTo();
+
+ dataJobsExplorePage
+ .getDataGridCell(testJobs[1].job_name)
+ .should('be.visible');
+
+ dataJobsExplorePage
+ .searchByJobName(testJobs[0].job_name);
+
+ dataJobsExplorePage
+ .getDataGridCell(testJobs[0].job_name)
+ .should('be.visible');
+
+ dataJobsExplorePage
+ .getDataGridCell(testJobs[1].job_name)
+ .should('not.exist');
+ });
+
+ it('Data Jobs Explore Page - searches data jobs, search parameter goes into URL', () => {
+ cy.log('Fixture for name: ' + testJobs[0].job_name);
+
+ dataJobsExplorePage = DataJobsExplorePage.navigateTo();
+
+ // verify 2 test rows visible
+ dataJobsExplorePage
+ .getDataGridCell(testJobs[0].job_name)
+ .should('be.visible');
+ dataJobsExplorePage
+ .getDataGridCell(testJobs[1].job_name)
+ .should('be.visible');
+
+ // do search
+ dataJobsExplorePage
+ .searchByJobName(testJobs[0].job_name);
+
+ // verify 1 test row visible
+ dataJobsExplorePage
+ .getDataGridCell(testJobs[0].job_name)
+ .should('be.visible');
+ dataJobsExplorePage
+ .getDataGridCell(testJobs[1].job_name)
+ .should('not.exist');
+
+ // verify url contains search value
+ dataJobsExplorePage
+ .getCurrentUrl()
+ .should('match', new RegExp(`\\/explore\\/data-jobs\\?search=${ testJobs[0].job_name }$`));
+
+ // clear search with clear() method
+ dataJobsExplorePage
+ .clearSearchField();
+
+ // verify 2 test rows visible
+ dataJobsExplorePage
+ .getDataGridCell(testJobs[0].job_name)
+ .should('be.visible');
+ dataJobsExplorePage
+ .getDataGridCell(testJobs[1].job_name)
+ .should('be.visible');
+
+ // verify url does not contain search value
+ dataJobsExplorePage
+ .getCurrentUrl()
+ .should('match', new RegExp(`\\/explore\\/data-jobs$`));
+ });
+
+ it('Data Jobs Explore Page - searches data jobs, perform search when URL contains search parameter', () => {
+ cy.log('Fixture for name: ' + testJobs[1].job_name);
+
+ // navigate with search value in URL
+ dataJobsExplorePage = DataJobsExplorePage.navigateToUrl(`/explore/data-jobs?search=${ testJobs[1].job_name }`);
+
+ // verify url contains search value
+ dataJobsExplorePage
+ .getCurrentUrl()
+ .should('match', new RegExp(`\\/explore\\/data-jobs\\?search=${ testJobs[1].job_name }$`));
+
+ // verify 1 test row visible
+ dataJobsExplorePage
+ .getDataGridCell(testJobs[0].job_name)
+ .should('not.exist');
+ dataJobsExplorePage
+ .getDataGridCell(testJobs[1].job_name)
+ .should('be.visible');
+
+ // clear search with button
+ dataJobsExplorePage
+ .clearSearchFieldWithButton();
+
+ // verify 2 test rows visible
+ dataJobsExplorePage
+ .getDataGridCell(testJobs[0].job_name)
+ .should('be.visible');
+ dataJobsExplorePage
+ .getDataGridCell(testJobs[1].job_name)
+ .should('be.visible');
+
+ // verify url does not contain search value
+ dataJobsExplorePage
+ .getCurrentUrl()
+ .should('match', new RegExp(`\\/explore\\/data-jobs$`));
+ });
+
+ it('Data Jobs Explore Page - show/hide column when toggling from menu', () => {
+ dataJobsExplorePage = DataJobsExplorePage.navigateTo();
+
+ // show panel for show/hide columns
+ dataJobsExplorePage
+ .toggleColumnShowHidePanel();
+
+ // verify correct options are rendered
+ dataJobsExplorePage
+ .getDataGridColumnShowHideOptionsValues()
+ .should('have.length', 9)
+ .invoke('join', ',')
+ .should('eq', 'Description,Deployment Status,Last Execution Duration,Success rate,Next run (UTC),Last Deployed (UTC),Last Deployed By,Source,Logs');
+
+ // verify column is not checked in toggling menu
+ dataJobsExplorePage
+ .getDataGridColumnShowHideOption('Last Execution Duration')
+ .should('exist')
+ .should('not.be.checked');
+
+ // verify header cell for column is not rendered
+ dataJobsExplorePage
+ .getDataGridHeaderCell('Last Execution Duration')
+ .should('have.length', 0);
+
+ // toggle column to render
+ dataJobsExplorePage
+ .checkColumnShowHideOption('Last Execution Duration');
+
+ // verify column is checked in toggling menu
+ dataJobsExplorePage
+ .getDataGridColumnShowHideOption('Last Execution Duration')
+ .should('exist')
+ .should('be.checked');
+
+ // verify header cell for column is rendered
+ dataJobsExplorePage
+ .getDataGridHeaderCell('Last Execution Duration')
+ .should('have.length', 1);
+
+ // toggle column to hide
+ dataJobsExplorePage
+ .uncheckColumnShowHideOption('Last Execution Duration');
+
+ // verify column is not checked in toggling menu
+ dataJobsExplorePage
+ .getDataGridColumnShowHideOption('Last Execution Duration')
+ .should('exist')
+ .should('not.be.checked');
+
+ // verify header cell for column is not rendered
+ dataJobsExplorePage
+ .getDataGridHeaderCell('Last Execution Duration')
+ .should('have.length', 0);
+
+ // hide panel for show/hide columns
+ dataJobsExplorePage
+ .toggleColumnShowHidePanel();
+ });
+
+ it('Data Jobs Explore Page - navigates to data job', () => {
+ cy.log('Fixture for name: ' + testJobs[0].job_name);
+
+ dataJobsExplorePage = DataJobsExplorePage.navigateTo();
+
+ dataJobsExplorePage
+ .openJobDetails(testJobs[0].team, testJobs[0].job_name);
+
+ const dataJobExploreDetailsPage = DataJobExploreDetailsPage
+ .getPage();
+
+ dataJobExploreDetailsPage
+ .getMainTitle()
+ .should('be.visible')
+ .should('contains.text', testJobs[0].job_name);
+
+ dataJobExploreDetailsPage
+ .getDescriptionField()
+ .should('be.visible')
+ .should('contain.text', testJobs[0].description);
+ });
+});
diff --git a/projects/frontend/data-pipelines/gui/e2e/integration/lib/manage/data-job-details.int.spec.js b/projects/frontend/data-pipelines/gui/e2e/integration/lib/manage/data-job-details.int.spec.js
new file mode 100644
index 0000000000..8af498d8b1
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/integration/lib/manage/data-job-details.int.spec.js
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+///
+
+import { DataJobManageDetailsPage } from '../../../support/pages/app/lib/manage/data-job-details.po';
+import { DataJobsManagePage } from '../../../support/pages/app/lib/manage/data-jobs.po';
+import { applyGlobalEnvSettings } from '../../../support/helpers/commands.helpers';
+
+describe('Data Job Manage Details Page', { tags: ['@dataPipelines', '@manageDataJobDetails'] }, () => {
+ const descriptionWordsBeforeTruncate = 12;
+
+ let dataJobManageDetailsPage;
+ let testJobs;
+ let longLivedTestJob;
+
+ before(() => {
+ return DataJobManageDetailsPage.recordHarIfSupported()
+ .then(() => cy.clearLocalStorageSnapshot('data-job-manage-details'))
+ .then(() => DataJobManageDetailsPage.login())
+ .then(() => cy.saveLocalStorage('data-job-manage-details'))
+ .then(() => cy.cleanTestJobs())
+ .then(() => cy.prepareLongLivedTestJob())
+ .then(() => cy.createTwoExecutionsLongLivedTestJob())
+ .then(() => cy.prepareBaseTestJobs())
+ .then(() => cy.fixture('lib/explore/test-jobs.json'))
+ .then((loadedTestJobs) => {
+ testJobs = applyGlobalEnvSettings(loadedTestJobs);
+
+ return cy.wrap({ context: 'manage::data-job-details.spec::1::before()', action: 'continue' });
+ })
+ .then(() => cy.fixture('lib/manage/e2e-cypress-dp-test.json'))
+ .then((loadedTestJob) => {
+ longLivedTestJob = applyGlobalEnvSettings(loadedTestJob);
+
+ return cy.wrap({ context: 'manage::data-job-details.spec::2::before()', action: 'continue' });
+ });
+ });
+
+ after(() => {
+ cy.cleanTestJobs();
+
+ DataJobManageDetailsPage.saveHarIfSupported();
+ });
+
+ beforeEach(() => {
+ cy.restoreLocalStorage('data-job-manage-details');
+
+ DataJobManageDetailsPage.initBackendRequestInterceptor();
+ });
+
+ it('Data Job Manage Details Page - disable/enable job', { tags: '@integration' }, () => {
+ dataJobManageDetailsPage = DataJobManageDetailsPage
+ .navigateTo(longLivedTestJob.team, longLivedTestJob.job_name);
+
+ dataJobManageDetailsPage.clickOnContentContainer();
+
+ //Toggle job status twice, enable to disable and vice versa.
+ dataJobManageDetailsPage.toggleJobStatus(longLivedTestJob.job_name);
+ dataJobManageDetailsPage.toggleJobStatus(longLivedTestJob.job_name);
+ });
+
+ it('Data Job Manage Details Page - edit job description', { tags: '@integration' }, () => {
+ let newDescription = 'Test if changing the description is working';
+
+ dataJobManageDetailsPage = DataJobManageDetailsPage
+ .navigateTo(testJobs[0].team, testJobs[0].job_name);
+
+ dataJobManageDetailsPage.clickOnContentContainer();
+
+ dataJobManageDetailsPage
+ .openDescription();
+
+ dataJobManageDetailsPage
+ .enterDescriptionDetails(newDescription);
+
+ dataJobManageDetailsPage
+ .saveDescription();
+
+ dataJobManageDetailsPage
+ .getDescription()
+ .should('be.visible')
+ .should('contain.text', newDescription.split(' ').slice(0, descriptionWordsBeforeTruncate).join(' '));
+ });
+
+ it('Data Job Manage Details Page - execute now', () => {
+ dataJobManageDetailsPage = DataJobManageDetailsPage
+ .navigateTo(longLivedTestJob.team, longLivedTestJob.job_name);
+
+ dataJobManageDetailsPage
+ .waitForTestJobExecutionCompletion();
+
+ dataJobManageDetailsPage
+ .executeNow();
+
+ dataJobManageDetailsPage
+ .confirmInConfirmDialog();
+
+ dataJobManageDetailsPage
+ .getToastTitle(10000)
+ .should('exist')
+ .contains(/Data job Queued for execution|Failed, Data job is already executing/);
+ });
+
+ it('Data Job Manage Details Page - download job key', () => {
+ dataJobManageDetailsPage = DataJobManageDetailsPage
+ .navigateTo(longLivedTestJob.team, longLivedTestJob.job_name);
+
+ dataJobManageDetailsPage
+ .openActionDropdown();
+
+ dataJobManageDetailsPage
+ .downloadJobKey();
+
+ dataJobManageDetailsPage
+ .readFile('downloadsFolder', `${ longLivedTestJob.job_name }.keytab`)
+ .should('exist');
+ });
+
+ it('Data Job Manage Details Page - delete job', { tags: '@integration' }, () => {
+ cy.fixture('lib/explore/additional-test-job.json').then((additionalTestJob) => {
+ const normalizedTestJob = applyGlobalEnvSettings(additionalTestJob);
+
+ cy.prepareAdditionalTestJobs();
+
+ dataJobManageDetailsPage = DataJobManageDetailsPage
+ .navigateTo(normalizedTestJob.team, normalizedTestJob.job_name);
+
+ dataJobManageDetailsPage.clickOnContentContainer();
+
+ dataJobManageDetailsPage
+ .openActionDropdown();
+
+ dataJobManageDetailsPage
+ .deleteJob();
+
+ dataJobManageDetailsPage
+ .confirmDeleteJob();
+
+ dataJobManageDetailsPage
+ .getToastTitle(20000) // Wait up to 20 seconds for the job to be deleted.
+ .should('contain.text', 'Data job delete completed')
+
+ dataJobManageDetailsPage
+ .waitForBackendRequestCompletion();
+
+ const dataJobsManagePage = DataJobsManagePage
+ .getPage();
+
+ dataJobsManagePage
+ .getDataGridCell(normalizedTestJob.job_name, 10000) // Wait up to 10 seconds for the jobs list to show.
+ .should('not.exist');
+ });
+ });
+
+ //TODO: Double-check and enable this test
+ it.skip('Data Job Manage Details Page - executions timeline', () => {
+ const jobName = longLivedTestJob.job_name;
+ const team = longLivedTestJob.team;
+
+ cy.intercept({
+ method: 'GET',
+ url: `/data-jobs/for-team/${ team }/jobs/${ jobName }/executions`
+ }).as('executionApiCall')
+
+ dataJobManageDetailsPage = DataJobManageDetailsPage
+ .navigateTo(team, jobName);
+
+ cy.wait('@executionApiCall').then((interception) => {
+ const response = interception.response.body;
+ const lastExecutions = response
+ .sort((left, right) => compareDatesAsc(left, right))
+ .slice(response.length > 5 ? response.length - 5 : 0);
+
+ const lastExecutionsSize = lastExecutions.length;
+ const lastExecution = lastExecutions.at(-1);
+
+ cy.get('.clr-timeline-step')
+ .should('have.length', lastExecutionsSize + 1) // +1 next execution
+
+ cy.get(`[data-cy=${ lastExecution.id }]`)
+ .as('lastExecution')
+
+ let statusIconMap = [];
+ statusIconMap['cancelled'] = 'times-circle';
+ statusIconMap['skipped'] = 'circle-arrow';
+ statusIconMap['submitted'] = 'circle';
+ statusIconMap['finished'] = 'success-standard';
+ statusIconMap['failed'] = 'error-standard';
+
+ if (lastExecution.type !== 'manual') {
+ cy.get('@lastExecution')
+ .find('.manual-execution-label')
+ .scrollIntoView()
+ .should('be.visible');
+ }
+
+ if (lastExecution.status !== 'running') {
+ cy.get('@lastExecution')
+ .find(`[shape=${ statusIconMap[lastExecution.status] }]`)
+ .should('exist')
+ }
+
+ cy.get('@lastExecution')
+ .find('.clr-timeline-step-header')
+ .invoke('attr', 'title')
+ .should('contain', 'Started ')
+
+ if (lastExecution.status !== 'running') {
+ cy.get('@lastExecution')
+ .find('u')
+ .last()
+ .invoke('attr', 'title')
+ .should('contain', 'Ended ')
+ }
+ })
+ })
+});
diff --git a/projects/frontend/data-pipelines/gui/e2e/integration/lib/manage/data-job-executions.int.spec.js b/projects/frontend/data-pipelines/gui/e2e/integration/lib/manage/data-job-executions.int.spec.js
new file mode 100644
index 0000000000..20563bbca5
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/integration/lib/manage/data-job-executions.int.spec.js
@@ -0,0 +1,644 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+///
+
+import { DataJobBasePO } from '../../../support/application/data-job-base.po';
+import { DataJobsManagePage } from '../../../support/pages/app/lib/manage/data-jobs.po';
+import { DataJobManageExecutionsPage } from '../../../support/pages/app/lib/manage/data-job-executions.po';
+import { applyGlobalEnvSettings } from '../../../support/helpers/commands.helpers';
+
+describe('Data Job Manage Executions Page', { tags: ['@dataPipelines', '@manageDataJobExecutions'] }, () => {
+ let longLivedTestJob;
+
+ before(() => {
+ return DataJobManageExecutionsPage.recordHarIfSupported()
+ .then(() => cy.clearLocalStorageSnapshot('data-job-manage-executions'))
+ .then(() => DataJobManageExecutionsPage.login())
+ .then(() => cy.saveLocalStorage('data-job-manage-executions'))
+ .then(() => cy.prepareLongLivedTestJob())
+ .then(() => cy.createTwoExecutionsLongLivedTestJob())
+ .then(() => cy.fixture('lib/manage/e2e-cypress-dp-test.json'))
+ .then((loadedTestJob) => {
+ longLivedTestJob = applyGlobalEnvSettings(loadedTestJob);
+
+ return cy.wrap({ context: 'manage::data-job-executions.spec::before()', action: 'continue' });
+ });
+ });
+
+ after(() => {
+ DataJobManageExecutionsPage.saveHarIfSupported();
+ });
+
+ beforeEach(() => {
+ cy.restoreLocalStorage('data-job-manage-executions');
+
+ DataJobManageExecutionsPage.initBackendRequestInterceptor();
+ });
+
+ describe('Sanity', { tags: '@integration' }, () => {
+ it(`Data Job Manage Executions Page - should open Details and verify Executions tab is displayed and navigates`, () => {
+ cy.log('Fixture for name: ' + longLivedTestJob.job_name);
+
+ const dataJobsManagePage = DataJobsManagePage.navigateTo();
+
+ dataJobsManagePage
+ .clickOnContentContainer();
+
+ dataJobsManagePage
+ .chooseQuickFilter(0);
+
+ dataJobsManagePage
+ .openJobDetails(longLivedTestJob.team, longLivedTestJob.job_name);
+
+ const dataJobBasePage = DataJobBasePO
+ .getPage();
+
+ dataJobBasePage
+ .getDetailsTab()
+ .should('exist')
+ .should('have.class', 'active');
+
+ dataJobBasePage
+ .getExecutionsTab()
+ .should('exist')
+ .should('not.have.class', 'active');
+
+ dataJobBasePage
+ .openExecutionsTab();
+
+ const dataJobExecutionsPage = DataJobManageExecutionsPage
+ .getPage();
+
+ dataJobExecutionsPage
+ .getDetailsTab()
+ .should('exist')
+ .should('not.have.class', 'active');
+
+ dataJobExecutionsPage
+ .getExecutionsTab()
+ .should('exist')
+ .should('have.class', 'active');
+
+ dataJobExecutionsPage
+ .getDataGrid()
+ .should('exist');
+ });
+
+ it('Data Job Manage Executions Page - should verify on URL navigate to Executions will open the page', () => {
+ const dataJobExecutionsPage = DataJobManageExecutionsPage
+ .navigateTo(longLivedTestJob.team, longLivedTestJob.job_name);
+
+ dataJobExecutionsPage
+ .getCurrentUrl()
+ .should('match', new RegExp(`\\/manage\\/data-jobs\\/${longLivedTestJob.team}\\/${longLivedTestJob.job_name}\\/executions$`));
+
+ dataJobExecutionsPage
+ .getDetailsTab()
+ .should('exist')
+ .should('not.have.class', 'active');
+
+ dataJobExecutionsPage
+ .getExecutionsTab()
+ .should('exist')
+ .should('have.class', 'active');
+
+ dataJobExecutionsPage
+ .getDataGrid()
+ .should('exist');
+ });
+
+ it('Data Job Manage Executions Page - should verify elements are rendered in DOM', () => {
+ const dataJobExecutionsPage = DataJobManageExecutionsPage
+ .navigateTo(longLivedTestJob.team, longLivedTestJob.job_name);
+
+ dataJobExecutionsPage
+ .getMainTitle()
+ .should('be.visible')
+ .should('contains.text', longLivedTestJob.job_name);
+
+ dataJobExecutionsPage
+ .getDetailsTab()
+ .should('exist')
+ .should('not.have.class', 'active');
+
+ dataJobExecutionsPage
+ .getExecutionsTab()
+ .should('exist')
+ .should('have.class', 'active');
+
+ dataJobExecutionsPage
+ .getExecuteNowButton()
+ .should('exist');
+
+ dataJobExecutionsPage
+ .getActionDropdownBtn()
+ .should('exist');
+
+ dataJobExecutionsPage
+ .openActionDropdown();
+
+ dataJobExecutionsPage
+ .getDeleteJobBtn()
+ .should('exist');
+
+ dataJobExecutionsPage
+ .clickOnContentContainer();
+
+ dataJobExecutionsPage
+ .waitForSmartDelay();
+
+ dataJobExecutionsPage
+ .getTimePeriod()
+ .should('exist');
+
+ dataJobExecutionsPage
+ .getStatusChart()
+ .should('exist');
+
+ dataJobExecutionsPage
+ .getDurationChart()
+ .should('exist');
+
+ dataJobExecutionsPage
+ .getDataGrid()
+ .should('exist');
+
+ dataJobExecutionsPage
+ .getDataGridRows()
+ .should('have.length.gt', 0);
+ });
+
+ it('Data Job Manage Executions Page - should verify cancel execution button works properly', () => {
+ const dataJobExecutionsPage = DataJobManageExecutionsPage
+ .navigateTo(longLivedTestJob.team, longLivedTestJob.job_name);
+
+ dataJobExecutionsPage
+ .initPostExecutionInterceptor();
+ dataJobExecutionsPage
+ .initDeleteExecutionInterceptor();
+ dataJobExecutionsPage
+ .initGetExecutionInterceptor();
+ dataJobExecutionsPage
+ .initGetExecutionsInterceptor();
+
+ dataJobExecutionsPage
+ .getExecutionsTab()
+ .should('exist')
+ .should('have.class', 'active');
+
+ // Execute data job and check if the execution status after that is Running or Submitted
+ dataJobExecutionsPage
+ .getExecuteNowButton()
+ .should('exist');
+ dataJobExecutionsPage
+ .executeNow();
+ dataJobExecutionsPage
+ .getConfirmDialogButton()
+ .should('exist')
+ .click({ force: true });
+ dataJobExecutionsPage
+ .waitForPostExecutionCompletion();
+ dataJobExecutionsPage
+ .waitForDataJobStartExecute();
+
+ dataJobExecutionsPage
+ .waitForViewToRenderShort();
+
+ dataJobExecutionsPage
+ .getExecutionStatus()
+ .first()
+ .contains(/Running|Submitted/);
+
+ // Cancel data job execution and check if the status after that is Cancelled
+ dataJobExecutionsPage
+ .getCancelExecutionButton()
+ .should('exist')
+ .click({ force: true })
+ dataJobExecutionsPage
+ .getConfirmDialogButton()
+ .should('exist')
+ .click({ force: true });
+ dataJobExecutionsPage
+ .waitForDeleteExecutionCompletion();
+ dataJobExecutionsPage
+ .waitForDataJobStopExecute();
+
+ dataJobExecutionsPage
+ .waitForViewToRenderShort();
+
+ dataJobExecutionsPage
+ .getExecutionStatus()
+ .first()
+ .should('contains.text', 'Canceled');
+ });
+ });
+
+ it('Data Job Manage Executions Page - should verify time period is in correct format', () => {
+ const dataJobExecutionsPage = DataJobManageExecutionsPage
+ .navigateTo(longLivedTestJob.team, longLivedTestJob.job_name);
+
+ dataJobExecutionsPage
+ .getTimePeriod()
+ .invoke('text')
+ .invoke('trim')
+ .should('match', new RegExp(`^\\w+\\s\\d+,\\s\\d+,\\s\\d+:\\d+:\\d+\\s(AM|PM)\\sto\\s\\w+\\s\\d+,\\s\\d+,\\s\\d+:\\d+:\\d+\\s(AM|PM)$`));
+ });
+
+ it('Data Job Manage Executions Page - should verify refresh button will show spinner and then load data', () => {
+ const dataJobExecutionsPage = DataJobManageExecutionsPage
+ .navigateTo(longLivedTestJob.team, longLivedTestJob.job_name);
+
+ dataJobExecutionsPage
+ .getDataGrid()
+ .should('exist');
+
+ dataJobExecutionsPage
+ .getExecLoadingSpinner()
+ .should('not.exist');
+
+ dataJobExecutionsPage
+ .getDataGridSpinner()
+ .should('not.exist');
+
+ dataJobExecutionsPage
+ .waitForActionThinkingTime();
+
+ dataJobExecutionsPage
+ .refreshExecData();
+
+ dataJobExecutionsPage
+ .getDataGrid()
+ .should('exist');
+
+ dataJobExecutionsPage
+ .getExecLoadingSpinner()
+ .should('exist');
+
+ dataJobExecutionsPage
+ .getDataGridSpinner()
+ .should('exist');
+
+ dataJobExecutionsPage
+ .waitForBackendRequestCompletion();
+
+ dataJobExecutionsPage
+ .waitForViewToRender();
+
+ dataJobExecutionsPage
+ .getDataGrid()
+ .should('exist');
+
+ dataJobExecutionsPage
+ .getExecLoadingSpinner()
+ .should('not.exist');
+
+ dataJobExecutionsPage
+ .getDataGridSpinner()
+ .should('not.exist');
+ });
+
+ describe('DataGrid Filters', () => {
+ it('Data Job Manage Executions Page - should verify status filter options are rendered', () => {
+ const dataJobExecutionsPage = DataJobManageExecutionsPage
+ .navigateTo(longLivedTestJob.team, longLivedTestJob.job_name);
+
+ dataJobExecutionsPage
+ .openStatusFilter();
+
+ dataJobExecutionsPage
+ .getDataGridPopupFilter()
+ .should('exist');
+
+ dataJobExecutionsPage
+ .getDataGridExecStatusFilters()
+ .then((elements) => Array.from(elements).map((el) => el.innerText))
+ .should('deep.equal', ['Success', 'Platform Error', 'User Error', 'Running', 'Submitted', 'Skipped', 'Canceled']);
+
+ dataJobExecutionsPage
+ .closeFilter();
+
+ dataJobExecutionsPage
+ .getDataGridPopupFilter()
+ .should('not.exist');
+ });
+
+ it('Data Job Manage Executions Page - should verify type filter options are rendered', () => {
+ const dataJobExecutionsPage = DataJobManageExecutionsPage
+ .navigateTo(longLivedTestJob.team, longLivedTestJob.job_name);
+
+ dataJobExecutionsPage
+ .openTypeFilter();
+
+ dataJobExecutionsPage
+ .getDataGridPopupFilter()
+ .should('exist');
+
+ dataJobExecutionsPage
+ .getDataGridExecTypeFilters()
+ .then((elements) => Array.from(elements).map((el) => el.innerText))
+ .should('deep.equal', ['Manual', 'Scheduled']);
+
+ dataJobExecutionsPage
+ .closeFilter();
+
+ dataJobExecutionsPage
+ .getDataGridPopupFilter()
+ .should('not.exist');
+ });
+
+ it('Data Job Manage Executions Page - should verify id filter render input and filters correctly', () => {
+ const dataJobExecutionsPage = DataJobManageExecutionsPage
+ .navigateTo(longLivedTestJob.team, longLivedTestJob.job_name);
+
+ dataJobExecutionsPage
+ .getDataGridRows()
+ .should('have.length.gt', 0);
+
+ dataJobExecutionsPage
+ .openIDFilter();
+
+ dataJobExecutionsPage
+ .getDataGridPopupFilter()
+ .should('exist');
+
+ dataJobExecutionsPage
+ .typeToFilterInput('xyxyxy');
+
+ dataJobExecutionsPage
+ .getDataGridRows()
+ .should('have.length', 0);
+
+ dataJobExecutionsPage
+ .clearFilterInput();
+
+ dataJobExecutionsPage
+ .getDataGridRows()
+ .should('have.length.gt', 0);
+
+ dataJobExecutionsPage
+ .closeFilter();
+
+ dataJobExecutionsPage
+ .getDataGridPopupFilter()
+ .should('not.exist');
+ });
+
+ it('Data Job Manage Executions Page - should verify version filter render input and filters correctly', () => {
+ const dataJobExecutionsPage = DataJobManageExecutionsPage
+ .navigateTo(longLivedTestJob.team, longLivedTestJob.job_name);
+
+ dataJobExecutionsPage
+ .getDataGridRows()
+ .should('have.length.gt', 0);
+
+ dataJobExecutionsPage
+ .openVersionFilter();
+
+ dataJobExecutionsPage
+ .getDataGridPopupFilter()
+ .should('exist');
+
+ dataJobExecutionsPage
+ .typeToFilterInput('xyxyxy');
+
+ dataJobExecutionsPage
+ .getDataGridRows()
+ .should('have.length', 0);
+
+ dataJobExecutionsPage
+ .clearFilterInput();
+
+ dataJobExecutionsPage
+ .getDataGridRows()
+ .should('have.length.gt', 0);
+
+ dataJobExecutionsPage
+ .closeFilter();
+
+ dataJobExecutionsPage
+ .getDataGridPopupFilter()
+ .should('not.exist');
+ });
+ });
+
+ describe('DataGrid Sort', () => {
+ it('Data Job Manage Executions Page - should verify duration sort works', () => {
+ const dataJobExecutionsPage = DataJobManageExecutionsPage
+ .navigateTo(longLivedTestJob.team, longLivedTestJob.job_name);
+
+ dataJobExecutionsPage
+ .getDataGridRows()
+ .should('have.length.gt', 0);
+
+ dataJobExecutionsPage
+ .getDataGridExecDurationHeader()
+ .should('exist')
+ .invoke('attr', 'aria-sort')
+ .should('eq', 'none');
+
+ dataJobExecutionsPage
+ .sortByExecDuration();
+
+ dataJobExecutionsPage
+ .getDataGridExecDurationHeader()
+ .should('exist')
+ .invoke('attr', 'aria-sort')
+ .should('eq', 'ascending');
+
+ dataJobExecutionsPage
+ .getDataGridRows()
+ .should('have.length.gt', 0);
+
+ dataJobExecutionsPage
+ .getDataGridExecDurationCell(1)
+ .invoke('text')
+ .invoke('trim')
+ .then((elText1) => {
+ return dataJobExecutionsPage
+ .getDataGridExecDurationCell(2)
+ .invoke('text')
+ .invoke('trim')
+ .then((elText2) => {
+ return dataJobExecutionsPage.convertStringContentToSeconds(elText1) -
+ dataJobExecutionsPage.convertStringContentToSeconds(elText2);
+ });
+ })
+ .should('be.lte', 0);
+
+ dataJobExecutionsPage
+ .sortByExecDuration();
+
+ dataJobExecutionsPage
+ .getDataGridExecDurationHeader()
+ .should('exist')
+ .invoke('attr', 'aria-sort')
+ .should('eq', 'descending');
+
+ dataJobExecutionsPage
+ .getDataGridRows()
+ .should('have.length.gt', 0);
+
+ dataJobExecutionsPage
+ .getDataGridExecDurationCell(1)
+ .invoke('text')
+ .invoke('trim')
+ .then((elText1) => {
+ return dataJobExecutionsPage
+ .getDataGridExecDurationCell(2)
+ .invoke('text')
+ .invoke('trim')
+ .then((elText2) => {
+ return dataJobExecutionsPage.convertStringContentToSeconds(elText1) -
+ dataJobExecutionsPage.convertStringContentToSeconds(elText2);
+ });
+ })
+ .should('be.gte', 0);
+ });
+
+ it('Data Job Manage Executions Page - should verify execution start sort works', () => {
+ const dataJobExecutionsPage = DataJobManageExecutionsPage
+ .navigateTo(longLivedTestJob.team, longLivedTestJob.job_name);
+
+ dataJobExecutionsPage
+ .getDataGridRows()
+ .should('have.length.gt', 0);
+
+ dataJobExecutionsPage
+ .getDataGridExecStartHeader()
+ .should('exist')
+ .invoke('attr', 'aria-sort')
+ .should('eq', 'descending');
+
+ dataJobExecutionsPage
+ .getDataGridExecStartCell(1)
+ .invoke('text')
+ .invoke('trim')
+ .then((elText1) => {
+ return dataJobExecutionsPage
+ .getDataGridExecStartCell(2)
+ .invoke('text')
+ .invoke('trim')
+ .then((elText2) => {
+ return new Date(elText1) - new Date(elText2)
+ });
+ })
+ .should('be.gte', 0);
+
+ dataJobExecutionsPage
+ .sortByExecStart();
+
+ dataJobExecutionsPage
+ .getDataGridExecStartHeader()
+ .should('exist')
+ .invoke('attr', 'aria-sort')
+ .should('eq', 'ascending');
+
+ dataJobExecutionsPage
+ .getDataGridRows()
+ .should('have.length.gt', 0);
+
+ dataJobExecutionsPage
+ .getDataGridExecStartCell(1)
+ .invoke('text')
+ .invoke('trim')
+ .then((elText1) => {
+ return dataJobExecutionsPage
+ .getDataGridExecStartCell(2)
+ .invoke('text')
+ .invoke('trim')
+ .then((elText2) => {
+ return new Date(elText1) - new Date(elText2)
+ });
+ })
+ .should('be.lte', 0);
+ });
+
+ it('Data Job Manage Executions Page - should verify execution end sort works', () => {
+ const dataJobExecutionsPage = DataJobManageExecutionsPage
+ .navigateTo(longLivedTestJob.team, longLivedTestJob.job_name);
+
+ dataJobExecutionsPage
+ .getDataGridRows()
+ .should('have.length.gt', 0);
+
+ dataJobExecutionsPage
+ .getDataGridExecEndHeader()
+ .should('exist')
+ .invoke('attr', 'aria-sort')
+ .should('eq', 'none');
+
+ dataJobExecutionsPage
+ .sortByExecEnd();
+
+ dataJobExecutionsPage
+ .getDataGridExecEndHeader()
+ .should('exist')
+ .invoke('attr', 'aria-sort')
+ .should('eq', 'ascending');
+
+ dataJobExecutionsPage
+ .getDataGridRows()
+ .should('have.length.gt', 0);
+
+ dataJobExecutionsPage
+ .getDataGridExecEndCell(1)
+ .invoke('text')
+ .invoke('trim')
+ .then((elText1) => {
+ return dataJobExecutionsPage
+ .getDataGridExecEndCell(2)
+ .invoke('text')
+ .invoke('trim')
+ .then((elText2) => {
+ const d1 = elText1
+ ? new Date(elText1)
+ : new Date();
+ const d2 = elText2
+ ? new Date(elText2)
+ : new Date();
+
+ return d1 - d2
+ });
+ })
+ .should('be.lte', 0);
+
+ dataJobExecutionsPage
+ .sortByExecEnd();
+
+ dataJobExecutionsPage
+ .getDataGridExecEndHeader()
+ .should('exist')
+ .invoke('attr', 'aria-sort')
+ .should('eq', 'descending');
+
+ dataJobExecutionsPage
+ .getDataGridRows()
+ .should('have.length.gt', 0);
+
+ dataJobExecutionsPage
+ .getDataGridExecEndCell(1)
+ .invoke('text')
+ .invoke('trim')
+ .then((elText1) => {
+ return dataJobExecutionsPage
+ .getDataGridExecEndCell(2)
+ .invoke('text')
+ .invoke('trim')
+ .then((elText2) => {
+ const d1 = elText1
+ ? new Date(elText1)
+ : new Date();
+ const d2 = elText2
+ ? new Date(elText2)
+ : new Date();
+
+ return d1 - d2
+ });
+ })
+ .should('be.gte', 0);
+ });
+ });
+});
diff --git a/projects/frontend/data-pipelines/gui/e2e/integration/lib/manage/data-jobs.int.spec.js b/projects/frontend/data-pipelines/gui/e2e/integration/lib/manage/data-jobs.int.spec.js
new file mode 100644
index 0000000000..9fd6c538a6
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/integration/lib/manage/data-jobs.int.spec.js
@@ -0,0 +1,494 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+///
+// Contain tests on this page: http://localhost.vmware.com:4200/manage/data-jobs
+// 1. Is search working
+// When you click on details does a modal pop-up?
+
+import { DataJobsManagePage } from '../../../support/pages/app/lib/manage/data-jobs.po';
+import { DataJobManageDetailsPage } from '../../../support/pages/app/lib/manage/data-job-details.po';
+import { applyGlobalEnvSettings } from '../../../support/helpers/commands.helpers';
+
+describe('Data Jobs Manage Page', { tags: ['@dataPipelines', '@manageDataJobs'] }, () => {
+ const descriptionWordsBeforeTruncate = 12;
+
+ /**
+ * @type {DataJobsManagePage}
+ */
+ let dataJobsManagePage;
+ let testJobs;
+ let longLivedTestJob;
+ let longLivedFailingTestJob;
+
+ before(() => {
+ return DataJobsManagePage.recordHarIfSupported()
+ .then(() => cy.clearLocalStorageSnapshot('data-jobs-manage'))
+ .then(() => DataJobsManagePage.login())
+ .then(() => cy.saveLocalStorage('data-jobs-manage'))
+ .then(() => cy.cleanTestJobs())
+ .then(() => cy.prepareBaseTestJobs())
+ .then(() => cy.prepareLongLivedTestJob())
+ .then(() => cy.createTwoExecutionsLongLivedTestJob())
+ .then(() => cy.prepareLongLivedFailingTestJob())
+ .then(() =>
+ cy.fixture('lib/explore/test-jobs.json')
+ .then((loadedTestJobs) => {
+ testJobs = applyGlobalEnvSettings(loadedTestJobs);
+
+ return cy.wrap({ context: 'manage::1::data-jobs.int.spec::before()', action: 'continue' });
+ })
+ )
+ .then(() =>
+ cy.fixture('lib/manage/e2e-cypress-dp-test.json')
+ .then((loadedTestJob) => {
+ longLivedTestJob = applyGlobalEnvSettings(loadedTestJob);
+
+ return cy.wrap({ context: 'manage::2::data-jobs.int.spec::before()', action: 'continue' });
+ })
+ )
+ .then(() =>
+ cy.fixture('e2e-cy-dp-failing.job.json')
+ .then((failingTestJob) => {
+ longLivedFailingTestJob = applyGlobalEnvSettings(failingTestJob);
+
+ return cy.wrap({ context: 'manage::3::data-jobs.int.spec::before()', action: 'continue' });
+ })
+ )
+ .then(() =>
+ // Enable data job after job end
+ DataJobsManagePage.changeJobStatus(longLivedTestJob.team, longLivedTestJob.job_name, true)
+ );
+ });
+
+ beforeEach(() => {
+ cy.restoreLocalStorage('data-jobs-manage');
+
+ DataJobsManagePage.initBackendRequestInterceptor();
+
+ dataJobsManagePage = DataJobsManagePage.navigateTo();
+ });
+
+ after(() => {
+ cy.cleanTestJobs();
+
+ // Enable data job after job end
+ DataJobsManagePage.changeJobStatus(longLivedTestJob.team, longLivedTestJob.job_name, true)
+
+ DataJobsManagePage.saveHarIfSupported();
+ });
+
+ it('Data Jobs Manage Page - loads title', () => {
+ dataJobsManagePage
+ .getPageTitle()
+ .should('be.visible');
+ });
+
+ it('Data Jobs Manage Page - grid contains test jobs', () => {
+ dataJobsManagePage
+ .chooseQuickFilter(0);
+
+ dataJobsManagePage
+ .sortByJobName();
+
+ dataJobsManagePage
+ .getDataGrid()
+ .should('be.visible');
+
+ testJobs.forEach((testJob) => {
+ cy.log('Fixture for name: ' + testJob.job_name);
+
+ dataJobsManagePage
+ .getDataGridCell(testJob.job_name)
+ .scrollIntoView()
+ .should('be.visible');
+ })
+ });
+
+ it('Data Jobs Manage Page - grid filter by job name', { tags: '@integration' }, () => {
+ dataJobsManagePage
+ .chooseQuickFilter(0);
+
+ dataJobsManagePage
+ .filterByJobName(testJobs[0].job_name);
+
+ dataJobsManagePage
+ .getDataGridCell(testJobs[0].job_name)
+ .should('be.visible');
+
+ dataJobsManagePage
+ .getDataGridCell(testJobs[1].job_name)
+ .should('not.exist');
+ });
+
+ it('Data Jobs Manage Page - grid search by job name', { tags: '@integration' }, () => {
+ dataJobsManagePage
+ .clickOnContentContainer();
+
+ dataJobsManagePage
+ .chooseQuickFilter(0);
+
+ dataJobsManagePage
+ .searchByJobName(testJobs[1].job_name);
+
+ dataJobsManagePage
+ .getDataGridCell(testJobs[0].job_name)
+ .should('not.exist');
+
+ dataJobsManagePage
+ .getDataGridCell(testJobs[1].job_name)
+ .should('be.visible');
+ });
+
+ it('Data Jobs Manage Page - grid search parameter goes into URL', () => {
+ dataJobsManagePage
+ .chooseQuickFilter(0);
+
+ dataJobsManagePage
+ .sortByJobName();
+
+ // verify 2 test rows visible
+ dataJobsManagePage
+ .getDataGridCell(testJobs[0].job_name)
+ .scrollIntoView()
+ .should('be.visible');
+ dataJobsManagePage
+ .getDataGridCell(testJobs[1].job_name)
+ .scrollIntoView()
+ .should('be.visible');
+
+ // do search
+ dataJobsManagePage
+ .searchByJobName(testJobs[0].job_name);
+
+ dataJobsManagePage
+ .waitForViewToRender();
+
+ // verify 1 test row visible
+ dataJobsManagePage
+ .getDataGridCell(testJobs[0].job_name)
+ .scrollIntoView()
+ .should('be.visible');
+ dataJobsManagePage
+ .getDataGridCell(testJobs[1].job_name)
+ .should('not.exist');
+
+ // verify url contains search value
+ dataJobsManagePage
+ .getCurrentUrl()
+ .should('match', new RegExp(`\\/manage\\/data-jobs\\?search=${ testJobs[0].job_name }$`));
+
+ // clear search with clear() method
+ dataJobsManagePage
+ .clearSearchField();
+
+ dataJobsManagePage
+ .waitForViewToRender();
+
+ // verify 2 test rows visible
+ dataJobsManagePage
+ .getDataGridCell(testJobs[0].job_name)
+ .scrollIntoView()
+ .should('be.visible');
+ dataJobsManagePage
+ .getDataGridCell(testJobs[1].job_name)
+ .scrollIntoView()
+ .should('be.visible');
+
+ // verify url does not contain search value
+ dataJobsManagePage
+ .getCurrentUrl()
+ .should('match', new RegExp(`\\/manage\\/data-jobs$`));
+ });
+
+ it('Data Jobs Manage Page - grid search perform search when URL contains search parameter', () => {
+ // navigate with search value in URL
+ dataJobsManagePage = DataJobsManagePage.navigateToUrl(`/manage/data-jobs?search=${ testJobs[1].job_name }`);
+
+ dataJobsManagePage
+ .chooseQuickFilter(0);
+
+ dataJobsManagePage
+ .sortByJobName();
+
+ // verify url contains search value
+ dataJobsManagePage
+ .getCurrentUrl()
+ .should('match', new RegExp(`\\/manage\\/data-jobs\\?search=${ testJobs[1].job_name }$`));
+
+ // verify 1 test row visible
+ dataJobsManagePage
+ .getDataGridCell(testJobs[0].job_name)
+ .should('not.exist');
+ dataJobsManagePage
+ .getDataGridCell(testJobs[1].job_name)
+ .scrollIntoView()
+ .should('be.visible');
+
+ // clear search with button
+ dataJobsManagePage
+ .clearSearchFieldWithButton();
+
+ // verify 2 test rows visible
+ dataJobsManagePage
+ .getDataGridCell(testJobs[0].job_name)
+ .scrollIntoView()
+ .should('be.visible');
+ dataJobsManagePage
+ .getDataGridCell(testJobs[1].job_name)
+ .scrollIntoView()
+ .should('be.visible');
+
+ // verify url does not contain search value
+ dataJobsManagePage
+ .getCurrentUrl()
+ .should('match', new RegExp(`\\/manage\\/data-jobs$`));
+ });
+
+ it('Data Jobs Manage Page - refresh shows newly created job', () => {
+ cy.fixture('lib/explore/additional-test-job.json').then((additionalTestJob) => {
+ const normalizedTestJob = applyGlobalEnvSettings(additionalTestJob);
+
+ cy.log('Fixture for name: ' + normalizedTestJob.job_name);
+
+ dataJobsManagePage
+ .chooseQuickFilter(0);
+
+ dataJobsManagePage
+ .sortByJobName();
+
+ dataJobsManagePage
+ .getDataGridCell(testJobs[0].job_name)
+ .should('have.text', testJobs[0].job_name);
+
+ dataJobsManagePage
+ .getDataGridCell(normalizedTestJob.job_name)
+ .should('not.exist');
+
+ dataJobsManagePage
+ .prepareAdditionalTestJob();
+
+ dataJobsManagePage
+ .refreshDataGrid();
+
+ dataJobsManagePage
+ .filterByJobName(normalizedTestJob.job_name);
+
+ dataJobsManagePage
+ .getDataGridCell(normalizedTestJob.job_name)
+ .should('have.text', normalizedTestJob.job_name);
+ });
+ });
+
+ it('Data Jobs Manage Page - click on edit button opens new page with Job details', { tags: '@integration' }, () => {
+ dataJobsManagePage
+ .clickOnContentContainer();
+
+ dataJobsManagePage
+ .chooseQuickFilter(0);
+
+ dataJobsManagePage
+ .sortByJobName();
+
+ dataJobsManagePage
+ .openJobDetails(testJobs[0].team, testJobs[0].job_name);
+
+ const dataJobManageDetailsPage = DataJobManageDetailsPage
+ .getPage();
+
+ dataJobManageDetailsPage
+ .getMainTitle()
+ .should('be.visible')
+ .should('contains.text', testJobs[0].job_name);
+
+ dataJobManageDetailsPage
+ .getDescription()
+ .should('be.visible')
+ .should('contain.text', testJobs[0].description.split(' ').slice(0, descriptionWordsBeforeTruncate).join(' '))
+
+ dataJobManageDetailsPage
+ .getSchedule()
+ .should('be.visible');
+
+ dataJobManageDetailsPage
+ .getDeploymentStatus('not-deployed')
+ .should('be.visible')
+ .should('have.text', 'Not Deployed');
+ });
+
+ it('Data Jobs Manage Page - disable/enable job', { tags: '@integration' }, () => {
+ dataJobsManagePage
+ .clickOnContentContainer();
+
+ dataJobsManagePage
+ .chooseQuickFilter(0);
+
+ const jobName = longLivedTestJob.job_name;
+ const team = longLivedTestJob.team;
+
+ dataJobsManagePage
+ .searchByJobName(jobName);
+
+ //Toggle job status twice, enable to disable and vice versa.
+ dataJobsManagePage.toggleJobStatus(longLivedTestJob.job_name);
+ dataJobsManagePage.toggleJobStatus(longLivedTestJob.job_name);
+ });
+
+ it('Data Jobs Manage Page - quick filters', { tags: '@integration' }, () => {
+ // Disable data job before test start
+ DataJobsManagePage
+ .changeJobStatus(longLivedFailingTestJob.team, longLivedFailingTestJob.job_name, true)
+ .then(() =>
+ DataJobsManagePage.changeJobStatus(longLivedTestJob.team, longLivedTestJob.job_name, false)
+ )
+ .then(() => {
+ dataJobsManagePage
+ .clickOnContentContainer();
+
+ dataJobsManagePage
+ .waitForClickThinkingTime();
+ dataJobsManagePage
+ .chooseQuickFilter(0);
+ dataJobsManagePage
+ .waitForViewToRender();
+
+ dataJobsManagePage
+ .getDataGridStatusIcons()
+ .then(($icons) => {
+ for (const icon of Array.from($icons)) {
+ cy.wrap(icon).invoke('attr', 'data-cy')
+ .should('match', new RegExp('data-pipelines-job-(enabled|disabled|not-deployed)'));
+ }
+ });
+
+ dataJobsManagePage
+ .waitForClickThinkingTime();
+ dataJobsManagePage
+ .chooseQuickFilter(1);
+ dataJobsManagePage
+ .waitForViewToRender();
+
+ dataJobsManagePage
+ .getDataGridStatusIcons()
+ .then(($icons) => {
+ for (const icon of Array.from($icons)) {
+ cy.wrap(icon).should('have.attr', 'data-cy', 'data-pipelines-job-enabled');
+ }
+ });
+
+ dataJobsManagePage
+ .waitForClickThinkingTime();
+ dataJobsManagePage
+ .chooseQuickFilter(2);
+ dataJobsManagePage
+ .waitForViewToRender();
+
+ dataJobsManagePage
+ .getDataGridStatusIcons()
+ .then(($icons) => {
+ for (const icon of Array.from($icons)) {
+ cy.wrap(icon).should('have.attr', 'data-cy', 'data-pipelines-job-disabled');
+ }
+ });
+
+ dataJobsManagePage
+ .waitForClickThinkingTime();
+ dataJobsManagePage
+ .chooseQuickFilter(3);
+ dataJobsManagePage
+ .waitForViewToRender();
+
+ dataJobsManagePage
+ .getDataGridStatusIcons()
+ .then(($icons) => {
+ for (const icon of Array.from($icons)) {
+ cy.wrap(icon).should('have.attr', 'data-cy', 'data-pipelines-job-not-deployed');
+ }
+ });
+
+ // Enable data job after job end
+ DataJobsManagePage.changeJobStatus(longLivedTestJob.team, longLivedTestJob.job_name, true);
+ });
+ });
+
+ it('Data Jobs Manage Page - show/hide column when toggling from menu', () => {
+ // show panel for show/hide columns
+ dataJobsManagePage
+ .toggleColumnShowHidePanel();
+
+ // verify correct options are rendered
+ dataJobsManagePage
+ .getDataGridColumnShowHideOptionsValues()
+ .should('have.length', 10)
+ .invoke('join', ',')
+ .should('eq', 'Description,Deployment Status,Last Execution Duration,Success rate,Next run (UTC),Last Deployed (UTC),Last Deployed By,Notifications,Source,Logs');
+
+ // verify column is not checked in toggling menu
+ dataJobsManagePage
+ .getDataGridColumnShowHideOption('Notifications')
+ .should('exist')
+ .should('not.be.checked');
+
+ // verify header cell for column is not rendered
+ dataJobsManagePage
+ .getDataGridHeaderCell('Notifications')
+ .should('have.length', 0);
+
+ // toggle column to render
+ dataJobsManagePage
+ .checkColumnShowHideOption('Notifications');
+
+ // verify column is checked in toggling menu
+ dataJobsManagePage
+ .getDataGridColumnShowHideOption('Notifications')
+ .should('exist')
+ .should('be.checked');
+
+ // verify header cell for column is rendered
+ dataJobsManagePage
+ .getDataGridHeaderCell('Notifications')
+ .should('have.length', 1);
+
+ // toggle column to hide
+ dataJobsManagePage
+ .uncheckColumnShowHideOption('Notifications');
+
+ // verify column is not checked in toggling menu
+ dataJobsManagePage
+ .getDataGridColumnShowHideOption('Notifications')
+ .should('exist')
+ .should('not.be.checked');
+
+ // verify header cell for column is not rendered
+ dataJobsManagePage
+ .getDataGridHeaderCell('Notifications')
+ .should('have.length', 0);
+
+ // hide panel for show/hide columns
+ dataJobsManagePage
+ .toggleColumnShowHidePanel();
+ });
+
+ it('Data Jobs Manage Page - execute now', { tags: '@integration' }, () => {
+ const jobName = longLivedTestJob.job_name;
+ const team = longLivedTestJob.team;
+
+ dataJobsManagePage
+ .chooseQuickFilter(0);
+
+ dataJobsManagePage
+ .sortByJobName();
+
+ dataJobsManagePage
+ .executeDataJob(jobName);
+
+ // TODO better handling toast message. If tests are executed immediately it will return
+ // error 409 conflict and it will say that the job is already executing
+ dataJobsManagePage
+ .getToastTitle(10000) // Wait up to 10 seconds for Toast to show.
+ .should('exist')
+ .contains(/Data job Queued for execution|Failed, Data job is already executing/);
+ });
+});
diff --git a/projects/frontend/data-pipelines/gui/e2e/plugins/index.js b/projects/frontend/data-pipelines/gui/e2e/plugins/index.js
new file mode 100644
index 0000000000..1fd0b4f108
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/plugins/index.js
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+///
+// ***********************************************************
+// This example plugins/index.js can be used to load plugins
+//
+// You can change the location of this file or turn off loading
+// the plugins file with the 'pluginsFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/plugins-guide
+// ***********************************************************
+
+// This function is called when a project is opened or re-opened (e.g. due to
+// the project's config changing)
+
+const { install, ensureBrowserFlags } = require('@neuralegion/cypress-har-generator');
+const path = require('path');
+
+/**
+ * @type {Cypress.PluginConfig}
+ */
+module.exports = (on, config) => {
+ const options = {
+ outputRoot: config.env.CYPRESS_TERMINAL_LOGS,
+ // Used to trim the base path of specs and reduce nesting in the
+ // generated output directory.
+ specRoot: path.relative(config.fileServerFolder, config.integrationFolder),
+ outputTarget: {
+ 'cypress-logs|json': 'json',
+ },
+ printLogsToConsole: 'always',
+ printLogsToFile: 'always',
+ includeSuccessfulHookLogs: true,
+ logToFilesOnAfterRun: true
+ };
+
+ require('cypress-terminal-report/src/installLogsPrinter')(on, options);
+ install(on, config);
+
+ on('before:browser:launch', (browser = {}, launchOptions) => {
+ ensureBrowserFlags(browser, launchOptions);
+ return launchOptions;
+ });
+};
diff --git a/projects/frontend/data-pipelines/gui/e2e/support/application/data-job-base.po.js b/projects/frontend/data-pipelines/gui/e2e/support/application/data-job-base.po.js
new file mode 100644
index 0000000000..54a0782984
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/support/application/data-job-base.po.js
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { DataPipelinesBasePO } from './data-pipelines-base.po';
+
+export class DataJobBasePO extends DataPipelinesBasePO {
+ static getPage() {
+ return new DataJobBasePO();
+ }
+
+ initGetExecutionInterceptor() {
+ cy.initGetExecutionInterceptor();
+ }
+
+ initGetExecutionsInterceptor() {
+ cy.initGetExecutionsInterceptor();
+ }
+
+ initPostExecutionInterceptor() {
+ cy.initPostExecutionInterceptor();
+ }
+
+ initDeleteExecutionInterceptor() {
+ cy.initDeleteExecutionInterceptor();
+ }
+
+ waitForPostExecutionCompletion() {
+ cy.waitForPostExecutionCompletion();
+ }
+
+ waitForDeleteExecutionCompletion() {
+ cy.waitForDeleteExecutionCompletion();
+ }
+
+ waitForDataJobStartExecute() {
+ cy.waitForInterceptor('@getExecutionsRequest', 20, (response) => {
+ return response?.data?.content?.findIndex((e) => {
+ return e.status?.toLowerCase() === 'running' || e.status?.toLowerCase() === 'submitted'
+ });
+ });
+ }
+
+ waitForDataJobStopExecute() {
+ cy.waitForInterceptor('@getExecutionRequest', 20, (response) => {
+ return response?.status === 'cancelled';
+ });
+ }
+
+ /* Selectors */
+
+ getNavigateBackBtn() {
+ return cy.get('[data-cy=data-pipelines-job-navigate-back]');
+ }
+
+ getMainTitle() {
+ return cy.get('[data-cy=data-pipelines-job-main-title]');
+ }
+
+ getExecuteNowButton() {
+ return cy.get('[data-cy=data-pipelines-job-execute-btn]');
+ }
+
+ getCancelExecutionButton() {
+ return cy.get('[data-cy=data-pipelines-job-cancel-execution-btn]');
+ }
+
+ getConfirmDialogButton() {
+ return cy.get('[data-cy="confirmation-dialog-ok-btn"]');
+ }
+
+ getActionDropdownBtn() {
+ return cy.get('[data-cy=data-pipelines-job-action-dropdown-btn]');
+ }
+
+ getDownloadKeyBtn() {
+ return cy.get('[data-cy=data-pipelines-job-download-btn]');
+ }
+
+ getDeleteJobBtn() {
+ return cy.get('[data-cy=data-pipelines-job-delete-btn]');
+ }
+
+ getConfirmDeleteBtn() {
+ return cy.get('#removeBtn');
+ }
+
+ getDetailsTab() {
+ return cy.get('[data-cy=data-pipelines-job-details-tab]');
+ }
+
+ getExecutionsTab() {
+ return cy.get('[data-cy=data-pipelines-job-executions-tab]');
+ }
+
+ getExecutionStatus() {
+ return cy.get('[data-cy="data-pipelines-job-execution-status"]');
+ }
+
+ /* Actions */
+
+ executeNow() {
+ this.getExecuteNowButton()
+ .scrollIntoView()
+ .click({ force: true });
+ }
+
+ openActionDropdown() {
+ this.getActionDropdownBtn()
+ .scrollIntoView()
+ .should('be.visible')
+ .click({ force: true });
+
+ this.waitForClickThinkingTime();
+ }
+
+ downloadJobKey() {
+ this.getDownloadKeyBtn()
+ .scrollIntoView()
+ .should('be.visible')
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion();
+ }
+
+ deleteJob() {
+ this.getDeleteJobBtn()
+ .scrollIntoView()
+ .should('be.visible')
+ .click({ force: true });
+ }
+
+ confirmDeleteJob() {
+ this.getConfirmDeleteBtn()
+ .scrollIntoView()
+ .should('be.visible')
+ .click({ force: true });
+ }
+
+ navigateBackToDataJobs() {
+ this.getNavigateBackBtn()
+ .should('be.visible')
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion();
+ }
+
+ openDetailsTab() {
+ this.getDetailsTab()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion();
+ }
+
+ openExecutionsTab() {
+ this.getExecutionsTab()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion();
+ }
+}
diff --git a/projects/frontend/data-pipelines/gui/e2e/support/application/data-job-details-base.po.js b/projects/frontend/data-pipelines/gui/e2e/support/application/data-job-details-base.po.js
new file mode 100644
index 0000000000..609099be4a
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/support/application/data-job-details-base.po.js
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { DataJobBasePO } from './data-job-base.po';
+
+export class DataJobDetailsBasePO extends DataJobBasePO {
+ static getPage() {
+ return new DataJobDetailsBasePO();
+ }
+
+ static navigateTo(context, teamName, jobName) {
+ return this.navigateToUrl(`/${ context }/data-jobs/${ teamName }/${ jobName }`);
+ }
+
+ // Selectors
+
+ getStatusField() {
+ return cy.get('[data-cy=data-pipelines-job-details-status]');
+ }
+
+ getDescriptionField() {
+ return cy.get('[data-cy=data-pipelines-job-details-description]');
+ }
+
+ getDescriptionFull() {
+ return cy.get('[data-cy=description-show-less]');
+ }
+
+ getShowDescriptionMoreBtn() {
+ return cy.get('[data-cy=description-show-more]');
+ }
+
+ getTeamField() {
+ return cy.get('[data-cy=data-pipelines-job-details-team]');
+ }
+
+ getScheduleField() {
+ return cy.get('[data-cy=data-pipelines-job-details-schedule]');
+ }
+
+ getSourceField() {
+ return cy.get('[data-cy=data-pipelines-job-details-source]');
+ }
+
+ getOnDeployedField() {
+ return cy.get('[data-cy=data-pipelines-job-details-on-deployed]');
+ }
+
+ getOnPlatformErrorField() {
+ return cy.get('[data-cy=data-pipelines-job-details-on-platform-error]');
+ }
+
+ getOnUserErrorField() {
+ return cy.get('[data-cy=data-pipelines-job-details-on-user-error]');
+ }
+
+ getOnSuccessField() {
+ return cy.get('[data-cy=data-pipelines-job-details-on-success]');
+ }
+
+ // Actions
+
+ showMoreDescription() {
+ this.getShowDescriptionMoreBtn()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForViewToRenderShort();
+
+ return this;
+ }
+}
diff --git a/projects/frontend/data-pipelines/gui/e2e/support/application/data-jobs-base.po.js b/projects/frontend/data-pipelines/gui/e2e/support/application/data-jobs-base.po.js
new file mode 100644
index 0000000000..d4b5dd9a4a
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/support/application/data-jobs-base.po.js
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { DataPipelinesBasePO } from './data-pipelines-base.po';
+
+export class DataJobsBasePO extends DataPipelinesBasePO {
+ static getPage() {
+ return new DataJobsExplorePage();
+ }
+
+ static navigateTo(linkSelector) {
+ cy.visit('/')
+
+ cy.wait(DataJobsBasePO.INITIAL_PAGE_LOAD_WAIT_TIME);
+
+ cy.get(linkSelector)
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion();
+
+ cy.wait(DataJobsBasePO.VIEW_RENDER_WAIT_TIME);
+
+ return this.getPage();
+ }
+
+ /* Selectors */
+
+ getDataGrid() {
+ return cy.get('clr-datagrid');
+ }
+
+ getQuickFilters() {
+ return cy.get('[data-cy=data-pipelines-quick-filters] > span');
+ }
+
+ getHeaderColumnJobName() {
+ return cy.get('[data-cy=data-pipelines-jobs-name-column]');
+ }
+
+ getHeaderColumnJobNameSortBtn() {
+ return this.getHeaderColumnJobName()
+ .should('exist')
+ .find('.datagrid-column-title');
+ }
+
+ getDataGridHeaderCell(content) {
+ return this.getDataGrid()
+ .should('exist')
+ .find('clr-dg-column:not(.datagrid-hidden-column)')
+ .contains(new RegExp(`${ content }`));
+ }
+
+ // Rows and Cells
+
+ getDataGridRows() {
+ return this.getDataGrid()
+ .should('exist')
+ .find('clr-dg-row.datagrid-row');
+ }
+
+ getDataGridRow(rowIndex) {
+ return this.getDataGridRows()
+ .should('have.length.gte', rowIndex - 1)
+ .then((rows) => Array.from(rows)[rowIndex - 1]);
+ }
+
+ getDataGridCells(rowIndex) {
+ return this.getDataGridRow(rowIndex)
+ .should('exist')
+ .find('clr-dg-cell.datagrid-cell');
+ }
+
+ getDataGridCellByIndex(rowIndex, cellIndex) {
+ return this.getDataGridCells(rowIndex)
+ .should('have.length.gte', cellIndex - 1)
+ .then((cells) => Array.from(cells)[cellIndex - 1]);
+ }
+
+ getDataGridCellByIdentifier(rowIndex, identifier) {
+ return this.getDataGridRow(rowIndex)
+ .should('exist')
+ .find(identifier)
+ }
+
+ getDataGridStatusCells() {
+ return this.getDataGrid()
+ .should('exist')
+ .find('[data-cy=data-pipelines-manage-grid-status-cell]');
+ }
+
+ getDataGridStatusIcons() {
+ return this.getDataGrid()
+ .should('exist')
+ .find('[data-cy=data-pipelines-manage-grid-status-cell] clr-icon[data-cy*=data-pipelines-job]');
+ }
+
+ getDataGridColumnToggle() {
+ return this.getDataGrid()
+ .should('exist')
+ .find('clr-dg-column-toggle button');
+ }
+
+ getDataGridColumnShowHidePanel() {
+ return cy.get('.column-switch');
+ }
+
+ getDataGridColumnShowHideOptions() {
+ return this.getDataGridColumnShowHidePanel()
+ .should('exist')
+ .find('clr-checkbox-wrapper');
+ }
+
+ getDataGridColumnShowHideOptionsValues() {
+ return this.getDataGridColumnShowHidePanel()
+ .should('exist')
+ .find('clr-checkbox-wrapper')
+ .then((elements) => Array.from(elements).map((el) => el.innerText));
+ }
+
+ getDataGridColumnShowHideOption(option) {
+ return this.getDataGridColumnShowHideOptions()
+ .should('have.length.gt', 0)
+ .then((elements) =>
+ Array.from(elements)
+ .find((el) => el.innerText === option)
+ )
+ .find('input');
+ }
+
+ /* Actions */
+
+ chooseQuickFilter(filterPosition) {
+ this.getQuickFilters()
+ .then((filters) => {
+ return filters && filters[filterPosition];
+ })
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion();
+
+ this.waitForViewToRender();
+ }
+
+ sortByJobName() {
+ this.getHeaderColumnJobNameSortBtn()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion();
+
+ this.waitForSmartDelay();
+ }
+
+ toggleColumnShowHidePanel() {
+ this.getDataGridColumnToggle()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForClickThinkingTime();
+ }
+
+ checkColumnShowHideOption(option) {
+ this.getDataGridColumnShowHideOption(option)
+ .should('exist')
+ .check({ force: true });
+
+ this.waitForViewToRenderShort();
+ }
+
+ uncheckColumnShowHideOption(option) {
+ this.getDataGridColumnShowHideOption(option)
+ .should('exist')
+ .uncheck({ force: true });
+
+ this.waitForViewToRenderShort();
+ }
+}
diff --git a/projects/frontend/data-pipelines/gui/e2e/support/application/data-pipelines-base.po.js b/projects/frontend/data-pipelines/gui/e2e/support/application/data-pipelines-base.po.js
new file mode 100644
index 0000000000..3cd77e6c1c
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/support/application/data-pipelines-base.po.js
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+export class DataPipelinesBasePO {
+ static API_MODIFY_CALL_TIME = 1000; // ms
+ static SMART_DELAY_TIME = 200; // ms
+ static CLICK_THINK_TIME = 500; // ms
+ static ACTION_THINK_TIME = 750; // ms
+ static VIEW_RENDER_SHORT_WAIT_TIME = 600; // ms
+ static VIEW_RENDER_WAIT_TIME = 1500; // ms
+ static INITIAL_PAGE_LOAD_WAIT_TIME = 2000; // ms
+ static INITIAL_PAGE_LOAD_LONG_WAIT_TIME = 2500; // ms
+
+ static getPage() {
+ return new DataPipelinesBasePO();
+ }
+
+ static login() {
+ return cy.login();
+ }
+
+ static navigateToUrl(url) {
+ cy.visit(url);
+
+ this.waitForBackendRequestCompletion();
+
+ cy.wait(DataPipelinesBasePO.VIEW_RENDER_SHORT_WAIT_TIME);
+
+ return this.getPage();
+ }
+
+ /**
+ * ** Init API requests interceptor.
+ */
+ static initBackendRequestInterceptor() {
+ cy.initBackendRequestInterceptor();
+ }
+
+ /**
+ * ** Wait for API request interceptor.
+ */
+ static waitForBackendRequestCompletion(numberOfReqToWait = 1) {
+ const thinkTimeMillis = 1000;
+
+ for (let i = 0; i < numberOfReqToWait; i++) {
+ cy.waitForBackendRequestCompletion();
+ }
+
+ cy.wait(thinkTimeMillis);
+ }
+
+ /**
+ * ** Start HAR recording.
+ *
+ * @returns {Cypress.Chainable}
+ */
+ static recordHarIfSupported() {
+ return cy.recordHarIfSupported();
+ }
+
+ /**
+ * ** Save recorded HAR.
+ *
+ * @returns {Cypress.Chainable}
+ */
+ static saveHarIfSupported() {
+ return cy.saveHarIfSupported();
+ }
+
+ /**
+ * ** Response from changing Data Job status.
+ * @param {string} teamName
+ * @param {string} jobName
+ * @param {boolean} status
+ * @returns {Cypress.Chainable}
+ */
+ static changeJobStatus(teamName, jobName, status) {
+ return cy.changeDataJobEnabledStatus(teamName, jobName, status);
+ }
+
+
+ // Interceptors and waiting
+
+ waitForBackendRequestCompletion(numberOfReqToWait = 1) {
+ DataPipelinesBasePO.waitForBackendRequestCompletion(numberOfReqToWait);
+ }
+
+ waitForTestJobExecutionCompletion() {
+ cy.waitForTestJobExecutionCompletion();
+ }
+
+ waitForApiModifyCall() {
+ cy.wait(DataPipelinesBasePO.API_MODIFY_CALL_TIME);
+ }
+
+ waitForSmartDelay() {
+ cy.wait(DataPipelinesBasePO.SMART_DELAY_TIME);
+ }
+
+ waitForClickThinkingTime() {
+ cy.wait(DataPipelinesBasePO.CLICK_THINK_TIME);
+ }
+
+ waitForActionThinkingTime() {
+ cy.wait(DataPipelinesBasePO.ACTION_THINK_TIME);
+ }
+
+ waitForViewToRenderShort() {
+ cy.wait(DataPipelinesBasePO.VIEW_RENDER_SHORT_WAIT_TIME);
+ }
+
+ waitForViewToRender() {
+ cy.wait(DataPipelinesBasePO.VIEW_RENDER_WAIT_TIME);
+ }
+
+ waitForInitialPageLoad() {
+ cy.wait(DataPipelinesBasePO.INITIAL_PAGE_LOAD_WAIT_TIME);
+ }
+
+ /* Selectors */
+
+ getMainTitle() {
+ return cy.get('[data-cy=dp-main-title]');
+ }
+
+ getCurrentUrl() {
+ return cy.url();
+ }
+
+ getToast(timeout) {
+ return cy.get('vdk-toast-container vdk-toast', { timeout: this.resolveTimeout(timeout) });
+ }
+
+ getToastTitle(timeout) {
+ return this
+ .getToast(timeout)
+ .get('.toast-title');
+ }
+
+ getToastDismiss(timeout) {
+ return this
+ .getToast(timeout)
+ .get('.dismiss-bg');
+ }
+
+ getContentContainer() {
+ return cy.get('div.content-container');
+ }
+
+ /* Actions */
+
+ confirmInConfirmDialog() {
+ cy.get('[data-cy=confirmation-dialog-ok-btn]')
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion();
+
+ this.waitForViewToRender();
+ }
+
+ clickOnContentContainer() {
+ this.getContentContainer()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForSmartDelay();
+ }
+
+ resolveTimeout(timeout) {
+ return timeout === undefined
+ ? Cypress.config('defaultCommandTimeout')
+ : timeout;
+ }
+
+ readFile(folderName, fileName) {
+ const path = require('path');
+ const downloadsFolder = Cypress.config(folderName);
+
+ return cy.readFile(path.join(downloadsFolder, fileName));
+ }
+
+ clearLocalStorage(key) {
+ if (key) {
+ cy.clearLocalStorage(key);
+
+ return;
+ }
+
+ cy.clearLocalStorage();
+ }
+}
diff --git a/projects/frontend/data-pipelines/gui/e2e/support/commands.js b/projects/frontend/data-pipelines/gui/e2e/support/commands.js
new file mode 100644
index 0000000000..e1e3ebd8d9
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/support/commands.js
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import 'cypress-localstorage-commands';
+
+import {
+ applyGlobalEnvSettings,
+ createExecutionsForJob,
+ createTestJob,
+ deleteTestJobIfExists,
+ deployTestJobIfNotExists,
+ waitForJobExecutionCompletion
+} from './helpers/commands.helpers';
+
+Cypress.Commands.add('login', () => {
+ return cy.request({
+ method: 'POST',
+ // See project README.md how to set this login url
+ url: Cypress.env('login_url'),
+ form: true,
+ body: {
+ // See project README.md how to set this token
+ api_token: Cypress.env('OAUTH2_API_TOKEN')
+ },
+ followRedirect: false,
+ failOnStatusCode: false
+ })
+ .its('body')
+ .then((body) => {
+ const token = body.access_token;
+ const expiresIn = body.expires_in;
+ const idToken = body.id_token;
+ const expiresAt = JSON.stringify(Date.now() + (expiresIn * 1000));
+
+ window.localStorage.setItem('expires_at', expiresAt);
+ window.localStorage.setItem('access_token', token);
+ window.localStorage.setItem('id_token', idToken);
+
+ // Set the CSP token in the session storage to enable the Integration testing against Management UI
+ sessionStorage.setItem('expires_at', expiresAt);
+ sessionStorage.setItem('access_token', token);
+ sessionStorage.setItem('id_token', idToken);
+
+ return cy.wrap({ context: 'commands::login()', action: 'continue' });
+ });
+});
+
+Cypress.Commands.add('initBackendRequestInterceptor', () => {
+ cy.intercept({
+ method:'GET',
+ url:'**/data-jobs/for-team/**'})
+ .as('getJobsRequest');
+});
+
+Cypress.Commands.add('waitForBackendRequestCompletion', () => {
+ cy.wait('@getJobsRequest');
+});
+
+Cypress.Commands.add('initGetExecutionsInterceptor', () => {
+ cy.intercept('GET', '**/data-jobs/for-team/**', (req) => {
+ if (req?.query?.operationName === 'jobsQuery' && req?.query?.query?.includes('executions')) {
+ req.alias = 'getExecutionsRequest';
+ }
+ })
+});
+
+Cypress.Commands.add('initGetExecutionInterceptor', () => {
+ cy.intercept({
+ method: 'GET',
+ url: '**/data-jobs/for-team/**/executions/**'
+ })
+ .as('getExecutionRequest');
+});
+
+Cypress.Commands.add('waitForInterceptor', (aliasName, retries, predicate) => {
+ cy.wait(aliasName).its('response.body').then((json) => {
+
+ if (predicate(json)) {
+ return;
+ }
+
+ if (retries > 0) {
+ cy.waitForInterceptor(aliasName, retries - 1, predicate);
+ }
+ });
+});
+
+Cypress.Commands.add('initPostExecutionInterceptor', () => {
+ cy.intercept({
+ method: 'POST',
+ url: '**/data-jobs/for-team/**/executions'
+ }).as('postExecuteNow');
+});
+
+Cypress.Commands.add('waitForPostExecutionCompletion', () => {
+ cy.wait('@postExecuteNow');
+});
+
+Cypress.Commands.add('initDeleteExecutionInterceptor', () => {
+ cy.intercept({
+ method: 'DELETE',
+ url: '**/data-jobs/for-team/**/executions/**'
+ }).as('deleteExecution');
+});
+
+Cypress.Commands.add('waitForDeleteExecutionCompletion', () => {
+ cy.wait('@deleteExecution');
+});
+
+Cypress.Commands.add('recordHarIfSupported', () => {
+ if (Cypress.browser.name === 'chrome') {
+ return cy.recordHar({ excludePaths: ['vendor.js$', 'clr-ui.min.css$', 'scripts.js$', 'polyfills.js$'] });
+ }
+
+ return cy.wrap({ context: 'commands::recordHarIfSupported()', action: 'continue' });
+});
+
+Cypress.Commands.add('saveHarIfSupported', () => {
+ if (Cypress.browser.name === 'chrome') {
+ return cy.saveHar();
+ }
+
+ return cy.wrap({ context: 'commands::saveHarIfSupported()', action: 'continue' });
+});
+
+Cypress.Commands.add('prepareBaseTestJobs', () => {
+ return cy.fixture('lib/explore/test-jobs.json')
+ .then((testJobs) => {
+ return Promise.all(
+ testJobs.map((testJob) => {
+ const normalizedTestJob = applyGlobalEnvSettings(testJob);
+
+ return createTestJob(normalizedTestJob);
+ })
+ );
+ });
+});
+
+Cypress.Commands.add('prepareAdditionalTestJobs', () => {
+ return cy.fixture('lib/explore/additional-test-job.json')
+ .then((testJob) => {
+ const normalizedTestJob = applyGlobalEnvSettings(testJob);
+
+ return createTestJob(normalizedTestJob);
+ })
+});
+
+Cypress.Commands.add('prepareLongLivedTestJob', () => {
+ return deployTestJobIfNotExists('lib/manage/e2e-cypress-dp-test.json', 'lib/manage/e2e-cypress-dp-test.zip');
+});
+
+Cypress.Commands.add('prepareLongLivedFailingTestJob', () => {
+ return deployTestJobIfNotExists('e2e-cy-dp-failing.job.json', 'e2e-cy-dp-failing.job.zip');
+});
+
+Cypress.Commands.add('cleanTestJobs', () => {
+ return cy.fixture('lib/explore/test-jobs.json')
+ .then((testJobs) => {
+ return Promise.all(
+ testJobs.map((testJob) => {
+ const normalizedTestJob = applyGlobalEnvSettings(testJob);
+
+ return deleteTestJobIfExists(normalizedTestJob);
+ })
+ );
+ })
+ .then(() => {
+ return cy.fixture('lib/explore/additional-test-job.json')
+ .then((testJob) => {
+ const normalizedTestJob = applyGlobalEnvSettings(testJob);
+
+ return deleteTestJobIfExists(normalizedTestJob);
+ });
+ });
+});
+
+Cypress.Commands.add('waitForTestJobExecutionCompletion', () => {
+ const waitForJobExecutionTimeout = 180000; // Wait up to 3 min for job execution to complete
+
+ return cy.fixture('lib/manage/e2e-cypress-dp-test.json')
+ .then((testJob) => {
+ const normalizedTestJob = applyGlobalEnvSettings(testJob);
+
+ return waitForJobExecutionCompletion(normalizedTestJob.team, normalizedTestJob.job_name, waitForJobExecutionTimeout);
+ });
+});
+
+Cypress.Commands.add('createTwoExecutionsLongLivedTestJob', () => {
+ const waitForJobExecutionTimeout = 180000; // Wait up to 3 min for job execution to complete
+
+ return cy.fixture('lib/manage/e2e-cypress-dp-test.json')
+ .then((testJob) => {
+ const normalizedTestJob = applyGlobalEnvSettings(testJob);
+
+ return createExecutionsForJob(normalizedTestJob.team, normalizedTestJob.job_name, waitForJobExecutionTimeout, 2);
+ });
+});
+
+Cypress.Commands.add('createExecutionsLongLivedFailingTestJob', () => {
+ const waitForJobExecutionTimeout = 180000; // Wait up to 3 min for job execution to complete
+
+ return cy.fixture('e2e-cy-dp-failing.job.json')
+ .then((failingTestJob) => {
+ const normalizedTestJob = applyGlobalEnvSettings(failingTestJob);
+
+ return createExecutionsForJob(normalizedTestJob.team, normalizedTestJob.job_name, waitForJobExecutionTimeout, 2);
+ });
+});
+
+Cypress.Commands.add('changeDataJobEnabledStatus', (teamName, jobName, status) => {
+ return cy.request({
+ url: Cypress.env('data_jobs_url') + `/data-jobs/for-team/${ teamName }/jobs/${ jobName }/deployments`,
+ method: 'get',
+ auth: {
+ 'bearer': window.localStorage.getItem('access_token')
+ },
+ failOnStatusCode: false
+ }).then((outerResponse) => {
+ if (outerResponse.status === 200) {
+ const lastDeployment = outerResponse.body[outerResponse.body.length - 1];
+ const lastDeploymentHash = lastDeployment.job_version;
+
+ return cy.request({
+ url: Cypress.env('data_jobs_url') + `/data-jobs/for-team/${ teamName }/jobs/${ jobName }/deployments/${ lastDeploymentHash }`,
+ method: 'patch',
+ body: { enabled: status },
+ auth: {
+ 'bearer': window.localStorage.getItem('access_token')
+ },
+ failOnStatusCode: false
+ }).then((innerResponse) => {
+ if (innerResponse.status >= 200 && innerResponse.status < 300) {
+ cy.log(`Change enable status to [${ status }] for data job [${ jobName }]`);
+ } else {
+ cy.log(`Cannot change enabled status to [${ status }] for data job [${ jobName }]`);
+
+ console.log(`Http request:`, innerResponse);
+ }
+
+ return cy.wrap({ context: 'commands::1::changeDataJobEnabledStatus()', action: 'continue' });
+ });
+ } else {
+ cy.log(`Cannot change enabled status to [${ status }] for data job [${ jobName }]`);
+
+ console.log(`Http request:`, outerResponse);
+
+ return cy.wrap({ context: 'commands::2::changeDataJobEnabledStatus()', action: 'continue' });
+ }
+ });
+});
diff --git a/projects/frontend/data-pipelines/gui/e2e/support/helpers/commands.helpers.js b/projects/frontend/data-pipelines/gui/e2e/support/helpers/commands.helpers.js
new file mode 100644
index 0000000000..7c5a904835
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/support/helpers/commands.helpers.js
@@ -0,0 +1,433 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+const DEFAULT_TEST_ENV_VAR = 'lib';
+
+const applyGlobalEnvSettings = (loadedElement, injectedTestEnvVar = null) => {
+ const TEST_ENV_VAR = injectedTestEnvVar ?? _loadTestEnvironmentVar();
+
+ if (typeof loadedElement === 'string') {
+ return loadedElement.replace('$env-placeholder$', TEST_ENV_VAR);
+ }
+
+ if (typeof loadedElement === 'object') {
+ Object.entries(loadedElement)
+ .forEach(([key, value]) => {
+ if (typeof value === 'string') {
+ if (value.includes('$env-placeholder$')) {
+ value = value.replace('$env-placeholder$', TEST_ENV_VAR);
+ }
+
+ loadedElement[key] = value;
+ }
+
+ if (typeof value === 'object') {
+ loadedElement[key] = applyGlobalEnvSettings(value);
+ }
+ });
+ }
+
+ return loadedElement;
+};
+
+const createExecutionsForJob = (teamName, jobName, waitForJobExecutionTimeout, numberOfExecutions = 2) => {
+ cy.log(`Trying to provide at least ${ numberOfExecutions } executions for Job "${ jobName }"`);
+
+ return _getJobExecutions(teamName, jobName)
+ .then((executions) => {
+ cy.log(`Found ${ executions.length } executions for Job "${ jobName }" and target is ${ numberOfExecutions } executions`);
+
+ if (executions.length >= numberOfExecutions) {
+ cy.log(`Skipping manual execution, expected number of executions found`);
+
+ return cy.wrap({ context: 'commands.helpers::1::createExecutionsForJob()', action: 'continue' });
+ }
+
+ return waitForJobExecutionCompletion(teamName, jobName, waitForJobExecutionTimeout)
+ .then(() => {
+ return _getJobLastDeployment(teamName, jobName)
+ .then((lastDeployment) => {
+ if (lastDeployment) {
+ return _executeCreateExecutionsForJob(
+ teamName,
+ jobName,
+ waitForJobExecutionTimeout,
+ lastDeployment,
+ executions,
+ numberOfExecutions
+ );
+ }
+
+ return cy.wrap({ context: 'commands.helpers::2::createExecutionsForJob()', action: 'continue' });
+ });
+ });
+ });
+};
+
+const waitForJobExecutionCompletion = (teamName, jobName, jobExecutionTimeout, loopStep = 0) => {
+ const pollInterval = 5000;
+
+ return cy.request({
+ url: Cypress.env('data_jobs_url') + `/data-jobs/for-team/${ teamName }/jobs/${ jobName }/executions`,
+ method: 'get',
+ auth: {
+ 'bearer': window.localStorage.getItem('access_token')
+ }
+ }).then((response) => {
+ if (response.status !== 200) {
+ cy.log(`Job execution poll failed. Status: ${ response.status }; Message: ${ response.body }`);
+
+ return cy.wrap({ context: 'commands.helpers::1::waitForJobExecutionCompletion()', action: 'continue' });
+ }
+
+ const lastExecutions = response.body
+ .sort((left, right) => _compareDatesAsc(left, right))
+ .slice(response.body.length > 5 ? response.body.length - 5 : 0);
+
+ if (!lastExecutions.length || lastExecutions.length === 0) {
+ cy.log(`No valid Job executions available.`);
+
+ return cy.wrap({ context: 'commands.helpers::2::waitForJobExecutionCompletion()', action: 'continue' });
+ }
+
+ const lastExecution = lastExecutions[lastExecutions.length - 1];
+ const lastExecutionStatus = lastExecution.status.toLowerCase();
+
+ if (lastExecutionStatus !== 'running' && lastExecutionStatus !== 'submitted') {
+ return (
+ loopStep > 0
+ ? cy.wait(pollInterval)
+ : cy.wrap({ context: 'commands.helpers::3::waitForJobExecutionCompletion()', action: 'continue' })
+ ).then(() => {
+ cy.log(`Job "${ jobName }" executed successfully, polling completed!`);
+
+ return cy.wrap({ context: 'commands.helpers::4::waitForJobExecutionCompletion()', action: 'continue' });
+ });
+ }
+
+ if (jobExecutionTimeout <= 0) {
+ cy.log('Time to wait exceeded! Will not wait for job execution to finish any longer.');
+
+ return cy.wrap({ context: 'commands.helpers::5::waitForJobExecutionCompletion()', action: 'continue' });
+ }
+
+ cy.log(`Job ${ jobName }, still executing... Retrying after: ${ pollInterval / 1000 } seconds`);
+
+ return cy.wait(pollInterval)
+ .then(() => waitForJobExecutionCompletion(teamName, jobName, jobExecutionTimeout - pollInterval, jobExecutionTimeout++))
+ });
+};
+
+const createTestJob = (testJob) => {
+ let teamName = testJob.team;
+
+ return cy.request({
+ // TODO: use jobsQuery - deprecated jobsList in favour of jobsQuery
+ url: Cypress.env('data_jobs_url') + `/data-jobs/for-team/${ teamName }/jobs`,
+ method: 'post',
+ body: testJob,
+ auth: {
+ 'bearer': window.localStorage.getItem('access_token')
+ },
+ failOnStatusCode: false
+ }).then((response) => {
+ if (response.status >= 400) {
+ cy.log(`Http request fail for url:`, `/data-jobs/for-team/${ teamName }/jobs`);
+
+ console.log(`Http request error:`, response);
+ }
+
+ return cy.wrap(response);
+ });
+};
+
+const deleteTestJobIfExists = (testJob) => {
+ let teamName = testJob.team;
+ let jobName = testJob.job_name;
+
+ return cy.request({
+ url: Cypress.env('data_jobs_url') + `/data-jobs/for-team/${ teamName }/jobs/${ jobName }`,
+ method: 'get',
+ auth: {
+ 'bearer': window.localStorage.getItem('access_token')
+ },
+ failOnStatusCode: false
+ }).then((outerResponse) => {
+ if (outerResponse.status === 200) {
+ return cy.request({
+ url: Cypress.env('data_jobs_url') + `/data-jobs/for-team/${ teamName }/jobs/${ jobName }`,
+ method: 'delete',
+ auth: {
+ 'bearer': window.localStorage.getItem('access_token')
+ },
+ failOnStatusCode: false
+ }).then((innerResponse) => {
+ if (innerResponse.status >= 400) {
+ cy.log(`Http request fail for url:`, `/data-jobs/for-team/${ teamName }/jobs/${ jobName }`);
+
+ console.log(`Http request error:`, innerResponse);
+ }
+
+ return cy.wrap({ context: 'commands.helpers::1::deleteTestJobIfExists()', action: 'continue' });
+ });
+ } else if (outerResponse.status >= 400) {
+ cy.log(`Http request fail for url:`, `/data-jobs/for-team/${ teamName }/jobs/${ jobName }`);
+
+ console.log(`Http request error:`, outerResponse);
+ }
+
+ return cy.wrap({ context: 'commands.helpers::2::deleteTestJobIfExists()', action: 'continue' });
+ });
+};
+
+const deployTestJobIfNotExists = (pathToJobData, pathToJobBinary) => {
+ return cy.fixture(pathToJobData)
+ .then((testJob) => {
+ const normalizedTestJob = applyGlobalEnvSettings(testJob);
+ const teamName = normalizedTestJob.team;
+ const jobName = normalizedTestJob.job_name;
+
+ // Check if data job exists and wether it has deployment or not.
+ return cy.request({
+ url: Cypress.env('data_jobs_url') + `/data-jobs/for-team/${ teamName }/jobs/${ jobName }/deployments`,
+ method: 'get',
+ auth: {
+ 'bearer': window.localStorage.getItem('access_token')
+ },
+ failOnStatusCode: false
+ }).then((outerResponse) => {
+ if (outerResponse.status === 200 && outerResponse.body.length > 0) {
+ cy.log(jobName + ' is already existing, therefore skipping creation and deployment.');
+ } else if (outerResponse.status === 200 && outerResponse.body.length === 0) {
+ cy.log(jobName + ' exists, but it is not deployed. Deploying...');
+
+ return _deployTestDataJob(normalizedTestJob, pathToJobBinary);
+ } else if (outerResponse.status === 404) {
+ return createTestJob(normalizedTestJob)
+ .then((innerResponse) => {
+ if (innerResponse.status === 201) {
+ return _deployTestDataJob(normalizedTestJob, pathToJobBinary);
+ } else {
+ throw new Error('Failed to create job: ' + jobName);
+ }
+ });
+ } else if (outerResponse.status >= 400) {
+ cy.log(`Http request fail for url:`, `/data-jobs/for-team/${ teamName }/jobs/${ jobName }/deployments`);
+
+ console.log(`Http request error:`, outerResponse);
+ }
+
+ return cy.wrap({ context: 'commands.helpers::deployTestJobIfNotExists()', action: 'continue' });
+ });
+ });
+};
+
+function _loadTestEnvironmentVar() {
+ let testEnv = Cypress?.env('test_environment');
+
+ if (!testEnv) {
+ console.log(`DataPipelines test_environment is not set. Using default: ${ DEFAULT_TEST_ENV_VAR }`);
+ testEnv = DEFAULT_TEST_ENV_VAR;
+ }
+
+ return testEnv;
+}
+
+function _executeCreateExecutionsForJob(teamName, jobName, waitForJobExecutionTimeout, lastDeployment, executions, numberOfExecutions, counterOfExecutions = 0) {
+ const neededExecutions = numberOfExecutions - executions.length;
+
+ if (counterOfExecutions === 0) {
+ cy.log(`Submitting ${ neededExecutions } serial executions`);
+ } else {
+ cy.log(`${ neededExecutions - counterOfExecutions } more executions left for executing`)
+ }
+
+ if (neededExecutions === counterOfExecutions) {
+ return cy.wrap({ context: 'commands.helpers::1::_executeCreateExecutionsForJob()', action: 'continue' });
+ }
+
+ return _executeJob(teamName, jobName, lastDeployment.id)
+ .then(() => {
+ return cy.wait(5000)
+ .then(() => cy.wrap({ context: 'commands.helpers::2::_executeCreateExecutionsForJob()', action: 'continue' }));
+ })
+ .then(() => {
+ return waitForJobExecutionCompletion(teamName, jobName, waitForJobExecutionTimeout);
+ })
+ .then(() => {
+ return _executeCreateExecutionsForJob(
+ teamName,
+ jobName,
+ waitForJobExecutionTimeout,
+ lastDeployment,
+ executions,
+ numberOfExecutions,
+ counterOfExecutions + 1
+ );
+ });
+}
+
+function _getJobExecutions(teamName, jobName) {
+ cy.log(`Fetching Job executions for Job "${ jobName }"`);
+
+ return cy
+ .request({
+ url: `/data-jobs/for-team/${ teamName }/jobs/${ jobName }/executions`,
+ method: 'get',
+ auth: {
+ 'bearer': window.localStorage.getItem('access_token')
+ }
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ cy.log(`Getting Job executions failed. Status: ${ response.status }; Message: ${ response.body }`);
+
+ return cy.wrap([]);
+ }
+
+ return cy.wrap(
+ response.body && response.body.length
+ ? response.body
+ : []
+ );
+ });
+}
+
+function _getJobLastDeployment(teamName, jobName) {
+ cy.log(`Fetching Job deployments for Job "${ jobName }"`);
+
+ return cy
+ .request({
+ url: Cypress.env('data_jobs_url') + `/data-jobs/for-team/${ teamName }/jobs/${ jobName }/deployments`,
+ method: 'get',
+ auth: {
+ 'bearer': window.localStorage.getItem('access_token')
+ }
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ cy.log(`Getting Job deployments failed. Status: ${ response.status }; Message: ${ response.body }`);
+
+ return cy.wrap(null);
+ }
+
+ return cy.wrap(
+ response.body && response.body.length
+ ? response.body[response.body.length - 1]
+ : null
+ );
+ });
+}
+
+function _executeJob(teamName, jobName, deploymentId) {
+ cy.log(`Submitting execution for Job "${ jobName }"`);
+
+ return cy.request({
+ url: Cypress.env('data_jobs_url') + `/data-jobs/for-team/${ teamName }/jobs/${ jobName }/deployments/${ deploymentId }/executions`,
+ method: 'post',
+ auth: {
+ 'bearer': window.localStorage.getItem('access_token')
+ },
+ body: {}
+ }).then((response) => {
+ if (response.status >= 400) {
+ cy.log(`Executing Job failed. Status: ${ response.status }; Message: ${ response.body }`);
+ }
+
+ return cy.wrap({ context: 'commands.helpers::_executeJob()', action: 'continue' });
+ });
+}
+
+function _compareDatesAsc(left, right) {
+ const leftStartTime = left.start_time ? left.start_time : 0;
+ const rightStartTime = right.start_time ? right.start_time : 0;
+
+ return new Date(leftStartTime).getTime() - new Date(rightStartTime).getTime();
+}
+
+function _deployTestDataJob(testJob, pathToJobBinary) {
+ const teamName = testJob.team;
+ const jobName = testJob.job_name;
+ const waitForJobDeploymentTimeout = 300000; // Wait up to 5 min for deployment to complete.
+
+ cy.log(`Deploying Job ${ jobName }`);
+
+ // Upload Data Job code
+ return cy.fixture(pathToJobBinary, 'binary')
+ .then((zippedDataJob) => {
+ return cy.request({
+ url: Cypress.env('data_jobs_url') + '/data-jobs/for-team/' + teamName + '/jobs/' + jobName + '/sources',
+ method: 'post',
+ auth: {
+ 'bearer': window.localStorage.getItem('access_token')
+ },
+ body: Cypress.Blob.binaryStringToBlob(zippedDataJob)
+ }).then((response1) => {
+ let jsonResponse = JSON.parse(Cypress.Blob.arrayBufferToBinaryString(response1.body));
+
+ // Deploy data job
+ return cy.request({
+ url: Cypress.env('data_jobs_url') + '/data-jobs/for-team/' + teamName + '/jobs/' + jobName + '/deployments',
+ method: 'post',
+ auth: {
+ 'bearer': window.localStorage.getItem('access_token')
+ },
+ body: {
+ job_version: jsonResponse.version_sha,
+ mode: 'release',
+ enabled: true
+ }
+ }).then((response2) => {
+ if (response2.status === 202) {
+ cy.log(`Job: ${ jobName } deployment in progress. Waiting for deployment to complete`);
+
+ return _waitForDeploymentToFinish(testJob, waitForJobDeploymentTimeout);
+ }
+
+ return cy.wrap({ context: 'commands.helpers::_deployTestDataJob()', action: 'continue' });
+ });
+ });
+ });
+}
+
+function _waitForDeploymentToFinish(testJob, timeout) {
+ const teamName = testJob.team;
+ const jobName = testJob.job_name;
+ const waitInterval = 5000;
+
+ return cy.request({
+ url: Cypress.env('data_jobs_url') + '/data-jobs/for-team/' + teamName + '/jobs/' + jobName + '/deployments',
+ method: 'get',
+ auth: {
+ 'bearer': window.localStorage.getItem('access_token')
+ }
+ }).then((resp) => {
+ if (resp.status === 200 && resp.body.length > 0) {
+ cy.log('Job deployment finished!');
+
+ return cy.wrap({ context: 'commands.helpers::1::_waitForDeploymentToFinish()', action: 'continue' });
+ }
+
+ if (timeout <= 0) {
+ cy.log('Time to wait exceeded! Will not wait for job deployment to finish any longer.');
+
+ return cy.wrap({ context: 'commands.helpers::2::_waitForDeploymentToFinish()', action: 'continue' });
+ }
+
+ cy.log(`Job ${ jobName }, still not deployed.. retrying after: ${ waitInterval / 1000 } seconds`);
+
+ return cy.wait(waitInterval)
+ .then(() => _waitForDeploymentToFinish(testJob, timeout - waitInterval));
+ })
+}
+
+module.exports = {
+ applyGlobalEnvSettings,
+ createExecutionsForJob,
+ waitForJobExecutionCompletion,
+ createTestJob,
+ deleteTestJobIfExists,
+ deployTestJobIfNotExists
+};
diff --git a/projects/frontend/data-pipelines/gui/e2e/support/index.js b/projects/frontend/data-pipelines/gui/e2e/support/index.js
new file mode 100644
index 0000000000..dfe8fbb0bc
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/support/index.js
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import './commands'
+
+require('@neuralegion/cypress-har-generator/commands');
+require('cypress-grep')();
+require('cypress-terminal-report/src/installLogsCollector')();
+
+Cypress.Server.defaults({
+ delay: 500,
+ force404: false,
+ ignore: (xhr) => {
+ return true;
+ }
+})
diff --git a/projects/frontend/data-pipelines/gui/e2e/support/pages/app/app.po.js b/projects/frontend/data-pipelines/gui/e2e/support/pages/app/app.po.js
new file mode 100644
index 0000000000..6a0e3e6cbb
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/support/pages/app/app.po.js
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { DataPipelinesBasePO } from '../../application/data-pipelines-base.po';
+
+export class AppPage extends DataPipelinesBasePO {
+ static getPage() {
+ return new AppPage();
+ }
+
+ static navigateTo() {
+ cy.visit('/');
+ }
+
+ getMainTitle() {
+ return cy.get('[data-cy=app-main-title]');
+ }
+}
diff --git a/projects/frontend/data-pipelines/gui/e2e/support/pages/app/getting-started/data-jobs-health-panel-component.po.js b/projects/frontend/data-pipelines/gui/e2e/support/pages/app/getting-started/data-jobs-health-panel-component.po.js
new file mode 100644
index 0000000000..13fcc897f5
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/support/pages/app/getting-started/data-jobs-health-panel-component.po.js
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { GettingStartedPage } from './getting-started.po';
+
+export class DataJobsHealthPanelComponentPO extends GettingStartedPage {
+ /**
+ * ** Returns instance of the page object.
+ *
+ * @returns {DataJobsHealthPanelComponentPO}
+ */
+ static getComponent() {
+ return new DataJobsHealthPanelComponentPO();
+ }
+
+ /**
+ * ** Get Execution status gauge Widget.
+ *
+ * @returns {Cypress.Chainable>}
+ */
+ getExecutionStatusGaugeWidget() {
+ return cy.get('[data-cy=dp-data-jobs-status-gauge-widget]');
+ }
+
+ /**
+ * ** Get percentage of successful executions against total from Execution status gauge Widget.
+ *
+ * @returns {Cypress.Chainable}
+ */
+ getExecutionsSuccessPercentage() {
+ return this.getExecutionStatusGaugeWidget()
+ .should('exist')
+ .find('[data-cy=dp-jobs-executions-status-gauge-widget-percentage]')
+ .invoke('text')
+ .invoke('replace', /%/, '')
+ .then((value) => +value);
+ }
+
+ /**
+ * ** Get number of failed executions from Execution status gauge Widget.
+ *
+ * @returns {Cypress.Chainable}
+ */
+ getNumberOfFailedExecutions() {
+ return this.getExecutionStatusGaugeWidget()
+ .should('exist')
+ .find('[data-cy=dp-jobs-executions-status-gauge-widget-failed]')
+ .invoke('text')
+ .invoke('replace', /\s\w+/, '')
+ .then((value) => +value);
+ }
+
+ /**
+ * ** Get number of total executions from Execution status gauge Widget.
+ *
+ * @returns {Cypress.Chainable}
+ */
+ getExecutionsTotal() {
+ return this.getExecutionStatusGaugeWidget()
+ .should('exist')
+ .find('[data-cy=dp-jobs-executions-status-gauge-widget-total]')
+ .invoke('text')
+ .invoke('replace', /\s\w+/, '')
+ .then((value) => +value);
+ }
+
+ /**
+ * ** Get Failing Data jobs Widget.
+ *
+ * @returns {Cypress.Chainable>}
+ */
+ getFailingJobsWidget() {
+ return cy.get('[data-cy=dp-failed-data-jobs-widget]');
+ }
+
+ /**
+ * ** Get all Data jobs from Failing Data jobs Widget.
+ *
+ * @returns {Cypress.Chainable>}
+ */
+ getAllFailingJobs() {
+ return this.getFailingJobsWidget()
+ .should('exist')
+ .find('[data-cy=dp-failed-data-jobs-widget-job-name-link]');
+ }
+
+ /**
+ * ** Get Most Recent Failing Data jobs executions Widget.
+ *
+ * @returns {Cypress.Chainable>}
+ */
+ getMostRecentFailingJobsExecutionsWidget() {
+ return cy.get('[data-cy=dp-failed-data-jobs-executions-widget]');
+ }
+
+ /**
+ * ** Get all executions from Most Recent Failing Data jobs executions Widget.
+ *
+ * @returns {Cypress.Chainable>}
+ */
+ getAllMostRecentFailingJobsExecutions() {
+ return this.getMostRecentFailingJobsExecutionsWidget()
+ .should('exist')
+ .find('[data-cy=dp-failed-data-jobs-executions-widget-job-name-link]');
+ }
+
+ // Actions
+
+ navigateToFailingJobDetails(jobName) {
+ this._navigateToDataJob(
+ this.getAllFailingJobs(),
+ jobName,
+ 3
+ );
+ }
+
+ navigateToMostRecentFailingJobExecutions(jobName) {
+ this._navigateToDataJob(
+ this.getAllMostRecentFailingJobsExecutions(),
+ jobName,
+ 2
+ );
+ }
+
+ /**
+ * ** Navigate to Data job
+ *
+ * @param {Cypress.Chainable>} chainable
+ * @param {string} jobName
+ * @param {number} numberOfReqToWait
+ * @private
+ */
+ _navigateToDataJob(chainable, jobName, numberOfReqToWait = 2) {
+ chainable
+ .then((elements) => {
+ /**
+ * @type {HTMLElement}
+ */
+ let foundElement;
+ elements.each((_index, el) => {
+ if (el.innerText.trim().includes(jobName)) {
+ foundElement = el;
+
+ return false;
+ }
+ });
+
+ return foundElement;
+ })
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion(numberOfReqToWait);
+
+ this.waitForViewToRender();
+ }
+}
diff --git a/projects/frontend/data-pipelines/gui/e2e/support/pages/app/getting-started/getting-started.po.js b/projects/frontend/data-pipelines/gui/e2e/support/pages/app/getting-started/getting-started.po.js
new file mode 100644
index 0000000000..0f23a016bb
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/support/pages/app/getting-started/getting-started.po.js
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { DataPipelinesBasePO } from '../../../application/data-pipelines-base.po';
+
+export class GettingStartedPage extends DataPipelinesBasePO {
+ /**
+ * ** Navigate to getting started page.
+ *
+ * @returns {GettingStartedPage}
+ */
+ static navigateTo() {
+ cy.visit('/get-started');
+
+ this.waitForBackendRequestCompletion(3);
+
+ return this.getPage();
+ }
+
+ /**
+ * ** Returns instance of the page object.
+ *
+ * @returns {GettingStartedPage}
+ */
+ static getPage() {
+ return new GettingStartedPage();
+ }
+
+ // Selectors
+
+ /**
+ * ** Returns main title of getting started page.
+ *
+ * @returns {Cypress.Chainable>}
+ */
+ getMainTitle() {
+ return cy.get('[data-cy=getting-started-main-title]');
+ }
+
+ /**
+ * ** Get Data jobs health panel.
+ *
+ * @returns {Cypress.Chainable>}
+ */
+ getDataJobsHealthPanel() {
+ return cy.get('[data-cy=dp-data-jobs-health-panel]');
+ }
+}
diff --git a/projects/frontend/data-pipelines/gui/e2e/support/pages/app/lib/explore/data-job-details.po.js b/projects/frontend/data-pipelines/gui/e2e/support/pages/app/lib/explore/data-job-details.po.js
new file mode 100644
index 0000000000..222bdee83a
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/support/pages/app/lib/explore/data-job-details.po.js
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { DataJobDetailsBasePO } from '../../../../application/data-job-details-base.po';
+
+export class DataJobExploreDetailsPage extends DataJobDetailsBasePO {
+ static getPage() {
+ return new DataJobExploreDetailsPage();
+ }
+
+ static navigateTo(teamName, jobName) {
+ return super.navigateTo('explore', teamName, jobName);
+ }
+}
diff --git a/projects/frontend/data-pipelines/gui/e2e/support/pages/app/lib/explore/data-jobs.po.js b/projects/frontend/data-pipelines/gui/e2e/support/pages/app/lib/explore/data-jobs.po.js
new file mode 100644
index 0000000000..373f0c96e4
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/support/pages/app/lib/explore/data-jobs.po.js
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { DataJobsBasePO } from '../../../../application/data-jobs-base.po';
+
+export class DataJobsExplorePage extends DataJobsBasePO {
+ static getPage() {
+ return new DataJobsExplorePage();
+ }
+
+ static navigateTo() {
+ return super.navigateTo('[data-cy=navigation-link-explore-datajobs]');
+ }
+
+ // Selectors
+
+ getMainTitle() {
+ return cy.get('[data-cy=data-pipelines-explore-page-main-title]');
+ }
+
+ getDataGrid() {
+ return cy.get('[data-cy=data-pipelines-explore-grid]');
+ }
+
+ getDataGridCell(content) {
+ return cy.get('[id^="clr-dg-row"] > .datagrid-row-scrollable > .datagrid-scrolling-cells > .ng-star-inserted')
+ .contains(new RegExp(`^${ content }$`));
+ }
+
+ getDataGridNavigateBtn(team, job) {
+ return cy.get('[data-cy=data-pipelines-explore-grid-details-link][data-job-params="' + team + ';' + job + '"]');
+ }
+
+ getDataGridNameFilter() {
+ return this.getHeaderColumnJobName()
+ .should('exist')
+ .find('clr-dg-filter button');
+ }
+
+ getDataGridNameFilterInput() {
+ return cy.get('div.datagrid-filter input');
+ }
+
+ getDataGridRefreshButton() {
+ return cy.get('[data-cy=data-pipelines-explore-refresh-btn]');
+ }
+
+ getDataGridSearchInput() {
+ return cy.get('[data-test-id=search-input]');
+ }
+
+ getDataGridClearSearchButton() {
+ return cy.get('[data-test-id="clear-search-btn"]').first();
+ }
+
+ // Actions
+
+ refreshDataGrid() {
+ this.getDataGridRefreshButton()
+ .should('be.visible')
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion();
+
+ this.waitForViewToRender();
+ }
+
+ searchByJobName(jobName) {
+ this.getDataGridSearchInput()
+ .should('be.visible')
+ .type(jobName);
+
+ this.waitForBackendRequestCompletion();
+
+ this.waitForViewToRender();
+ }
+
+ clearSearchField() {
+ this.getDataGridSearchInput()
+ .should('be.visible')
+ .clear();
+
+ this.waitForBackendRequestCompletion();
+
+ this.waitForViewToRender();
+ }
+
+ clearSearchFieldWithButton() {
+ this.getDataGridClearSearchButton()
+ .should('be.visible')
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion();
+
+ this.waitForViewToRender();
+ }
+
+ filterByJobName(jobName) {
+ this.getDataGridNameFilter()
+ .click({ force: true });
+
+ this.getDataGridNameFilterInput()
+ .should('be.visible')
+ .type(jobName);
+
+ this.getMainTitle()
+ .should('be.visible')
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion();
+
+ this.waitForViewToRender();
+ }
+
+ openJobDetails(team, jobName) {
+ this.getDataGridNavigateBtn(team, jobName)
+ .scrollIntoView()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion();
+
+ this.waitForViewToRenderShort();
+ }
+}
diff --git a/projects/frontend/data-pipelines/gui/e2e/support/pages/app/lib/manage/data-job-details.po.js b/projects/frontend/data-pipelines/gui/e2e/support/pages/app/lib/manage/data-job-details.po.js
new file mode 100644
index 0000000000..f0ac3c8b2b
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/support/pages/app/lib/manage/data-job-details.po.js
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { DataJobDetailsBasePO } from '../../../../application/data-job-details-base.po';
+
+export class DataJobManageDetailsPage extends DataJobDetailsBasePO {
+ static getPage() {
+ return new DataJobManageDetailsPage();
+ }
+
+ static navigateTo(teamName, jobName) {
+ return super.navigateTo('manage', teamName, jobName);
+ }
+
+ // Deployment methods
+
+ // Acceptable values are "not-deployed", "enabled", "disabled"
+ getDeploymentStatus(status) {
+ return cy.get('[data-cy=data-pipelines-job-details-status-' + status + ']');
+ }
+
+ // Description methods
+
+ getDescription() {
+ return cy.get('[data-cy=data-pipelines-data-job-details-description] .form-section-readonly');
+ }
+
+ getDescriptionEditButton() {
+ return cy.get('[data-cy=data-pipelines-data-job-details-description] .form-section-header > .btn');
+ }
+
+ getDescriptionEditTextarea() {
+ return cy.get('[data-cy=data-pipelines-data-job-details-description] textarea');
+ }
+
+ getDescriptionSaveButton() {
+ return cy.get('[data-cy=data-pipelines-data-job-details-description] button:contains(Save)')
+ }
+
+ openDescription() {
+ this.getDescriptionEditButton()
+ .click({ force: true });
+ }
+
+ enterDescriptionDetails(description) {
+ this.getDescriptionEditTextarea()
+ .clear()
+ .type(description)
+ }
+
+ saveDescription() {
+ this.getDescriptionSaveButton()
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion()
+ }
+
+ // Schedule methods
+
+ getSchedule() {
+ return cy.get('[data-cy=data-pipelines-data-job-details-schedule] .form-section-readonly');
+ }
+
+ // Disable/Enable methods
+
+ getStatusEditButton() {
+ return cy.get('[data-cy=data-pipelines-data-job-details-status] .form-section-header > .btn')
+ }
+
+ getStatusSaveButton() {
+ return cy.get('[data-cy=data-pipelines-data-job-details-status] button:contains(Save)');
+ }
+
+ changeStatus(currentStatus) {
+ const newStatus = currentStatus.trim().toLowerCase() === 'enabled'
+ ? 'disable'
+ : 'enable';
+
+ return cy.get(`[data-cy=data-pipelines-data-job-details-status-${ newStatus }]`)
+ .should('exist')
+ .check({ force: true });
+ }
+
+ toggleJobStatus() {
+ cy.get('[data-cy=data-pipelines-job-details-status]')
+ .invoke('text')
+ .then((jobStatus) => {
+ this.getStatusEditButton()
+ .scrollIntoView()
+ .click({ force: true });
+
+ this.changeStatus(jobStatus);
+
+ this.waitForClickThinkingTime();
+
+ this.getStatusSaveButton()
+ .scrollIntoView()
+ .click({ force: true });
+
+ let newStatus = jobStatus === 'Enabled'
+ ? 'Disabled'
+ : 'Enabled';
+
+ this.waitForBackendRequestCompletion();
+
+ this.getToastTitle()
+ .should('exist')
+ .should('contain.text', 'Status update completed');
+
+ this.waitForActionThinkingTime(); // Natural wait for User action
+
+ this.getToastDismiss()
+ .should('exist')
+ .click({ force: true });
+
+ cy.get('[data-cy=data-pipelines-job-details-status]')
+ .scrollIntoView()
+ .should('have.text', newStatus);
+ })
+ }
+}
diff --git a/projects/frontend/data-pipelines/gui/e2e/support/pages/app/lib/manage/data-job-executions.po.js b/projects/frontend/data-pipelines/gui/e2e/support/pages/app/lib/manage/data-job-executions.po.js
new file mode 100644
index 0000000000..af3c744c6a
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/support/pages/app/lib/manage/data-job-executions.po.js
@@ -0,0 +1,360 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { DataJobBasePO } from '../../../../application/data-job-base.po';
+
+export class DataJobManageExecutionsPage extends DataJobBasePO {
+ static getPage() {
+ return new DataJobManageExecutionsPage();
+ }
+
+ static navigateTo(teamName, jobName) {
+ return super.navigateToUrl(`/manage/data-jobs/${ teamName }/${ jobName }/executions`);
+ }
+
+ /* Utils */
+
+ /**
+ * @example
+ *
+ * - val=1m 34s
+ * - val=45s
+ */
+ convertStringContentToSeconds(val) {
+ const params = val.split(' ');
+
+ if (params.length === 2) {
+ return parseInt(params[0].trim().replace(/m$/, ''), 10) * 60 + parseInt(params[1].trim().replace(/s$/, ''), 10);
+ } else {
+ return parseInt(params[0].trim().replace(/s$/, ''), 10);
+ }
+ }
+
+ /* Selectors */
+
+ // General
+
+ getExecRefreshBtn() {
+ return cy.get('[data-cy=data-pipelines-job-executions-refresh-btn');
+ }
+
+ getExecLoadingSpinner() {
+ return cy.get('[data-cy=data-pipelines-job-executions-loading-spinner]');
+ }
+
+ // Charts
+
+ getStatusChart() {
+ return cy.get('[data-cy=data-pipelines-job-executions-status-chart]');
+ }
+
+ getDurationChart() {
+ return cy.get('[data-cy=data-pipelines-job-executions-duration-chart]');
+ }
+
+ getTimePeriod() {
+ return cy.get('[data-cy=data-pipelines-job-executions-time-period]');
+ }
+
+ // DataGrid
+
+ getDataGrid() {
+ return cy.get('[data-cy=data-pipelines-job-executions-datagrid]');
+ }
+
+ getDataGridPopupFilter() {
+ return cy.get('.datagrid-filter.clr-popover-content');
+ }
+
+ getDataGridInputFilter() {
+ return this.getDataGridPopupFilter()
+ .should('exist')
+ .find('input');
+ }
+
+ getDataGridPopupFilterCloseBtn() {
+ return this.getDataGridPopupFilter()
+ .should('exist')
+ .find('.close');
+ }
+
+ getDataGridSpinner() {
+ return this.getDataGrid()
+ .should('exist')
+ .find('.datagrid-spinner');
+ }
+
+ // Header
+
+ getDataGridExecStatusHeader() {
+ return this.getDataGrid()
+ .should('exist')
+ .find('[data-cy=data-pipelines-job-executions-status-header]');
+ }
+
+ getDataGridExecStatusFilterOpenerBtn() {
+ return this.getDataGridExecStatusHeader()
+ .should('exist')
+ .find('.datagrid-filter-toggle');
+ }
+
+ getDataGridExecStatusFilters() {
+ return this.getDataGridPopupFilter()
+ .should('exist')
+ .find('[data-cy=data-pipelines-job-executions-status-filters]');
+ }
+
+ getDataGridExecTypeHeader() {
+ return this.getDataGrid()
+ .should('exist')
+ .find('[data-cy=data-pipelines-job-executions-type-header]');
+ }
+
+ getDataGridExecTypeFilterOpenerBtn() {
+ return this.getDataGridExecTypeHeader()
+ .should('exist')
+ .find('.datagrid-filter-toggle');
+ }
+
+ getDataGridExecTypeFilters() {
+ return this.getDataGridPopupFilter()
+ .should('exist')
+ .find('[data-cy=data-pipelines-job-executions-type-filters]');
+ }
+
+ getDataGridExecDurationHeader() {
+ return this.getDataGrid()
+ .should('exist')
+ .find('[data-cy=data-pipelines-job-executions-duration-header]');
+ }
+
+ getDataGridExecDurationSortBtn() {
+ return this.getDataGridExecDurationHeader()
+ .should('exist')
+ .find('.datagrid-column-title');
+ }
+
+ getDataGridExecStartHeader() {
+ return this.getDataGrid()
+ .should('exist')
+ .find('[data-cy=data-pipelines-job-executions-start-header]');
+ }
+
+ getDataGridExecStartSortBtn() {
+ return this.getDataGridExecStartHeader()
+ .should('exist')
+ .find('.datagrid-column-title');
+ }
+
+ getDataGridExecEndHeader() {
+ return this.getDataGrid()
+ .should('exist')
+ .find('[data-cy=data-pipelines-job-executions-end-header]');
+ }
+
+ getDataGridExecEndSortBtn() {
+ return this.getDataGridExecEndHeader()
+ .should('exist')
+ .find('.datagrid-column-title');
+ }
+
+ getDataGridExecIDHeader() {
+ return this.getDataGrid()
+ .should('exist')
+ .find('[data-cy=data-pipelines-job-executions-id-header]');
+ }
+
+ getDataGridExecIDFilterOpenerBtn() {
+ return this.getDataGridExecIDHeader()
+ .should('exist')
+ .find('.datagrid-filter-toggle');
+ }
+
+ getDataGridExecVersionHeader() {
+ return this.getDataGrid()
+ .should('exist')
+ .find('[data-cy=data-pipelines-job-executions-version-header]');
+ }
+
+ getDataGridExecVersionFilterOpenerBtn() {
+ return this.getDataGridExecVersionHeader()
+ .should('exist')
+ .find('.datagrid-filter-toggle');
+ }
+
+ // Rows and Cells
+
+ getDataGridRows() {
+ return this.getDataGrid()
+ .should('exist')
+ .find('clr-dg-row.datagrid-row');
+ }
+
+ getDataGridRow(rowIndex) {
+ return this.getDataGridRows()
+ .should('have.length.gte', rowIndex - 1)
+ .then((rows) => Array.from(rows)[rowIndex - 1]);
+ }
+
+ getDataGridCells(rowIndex) {
+ return this.getDataGridRow(rowIndex)
+ .should('exist')
+ .find('clr-dg-cell.datagrid-cell');
+ }
+
+ getDataGridCellByIndex(rowIndex, cellIndex) {
+ if (rowIndex) {
+ return this.getDataGridCells(rowIndex)
+ .should('have.length.gte', cellIndex - 1)
+ .then((cells) => Array.from(cells)[cellIndex - 1]);
+ }
+
+ return this.getDataGridRows()
+ .should('have.length.gte', 0)
+ .then(($rows) => {
+ const _rows = $rows.reduce((accumulator, row) => {
+ const _cells = Array.from(row.querySelectorAll('clr-dg-cell.datagrid-cell'));
+
+ if (_cells[cellIndex - 1]) {
+ accumulator.push(_cells[cellIndex - 1]);
+ }
+
+ return accumulator;
+ }, []);
+
+ return _rows;
+ })
+ }
+
+ getDataGridCellByIdentifier(rowIndex, identifier) {
+ return this.getDataGridRow(rowIndex)
+ .should('exist')
+ .find(identifier)
+ }
+
+ getDataGridExecTypeCell(rowIndex) {
+ return this.getDataGridCellByIdentifier(rowIndex, '[data-cy=data-pipelines-job-executions-type-cell]');
+ }
+
+ getDataGridExecTypeContainer(rowIndex) {
+ return this.getDataGridExecTypeCell(rowIndex)
+ .should('exist')
+ .find('[data-cy=data-pipelines-job-executions-type-container]');
+ }
+
+ getDataGridExecDurationCell(rowIndex) {
+ return this.getDataGridCellByIdentifier(rowIndex, '[data-cy=data-pipelines-job-executions-duration-cell]');
+ }
+
+ getDataGridExecStartCell(rowIndex) {
+ return this.getDataGridCellByIdentifier(rowIndex, '[data-cy=data-pipelines-job-executions-start-cell]');
+ }
+
+ getDataGridExecEndCell(rowIndex) {
+ return this.getDataGridCellByIdentifier(rowIndex, '[data-cy=data-pipelines-job-executions-end-cell]');
+ }
+
+ // Pagination
+
+ getDataGridPagination() {
+ return this.getDataGrid()
+ .should('exist')
+ .find('[data-cy=data-pipelines-job-executions-datagrid-pagination]');
+ }
+
+ /* Actions */
+
+ // General
+
+ refreshExecData() {
+ this.getExecRefreshBtn()
+ .should('exist')
+ .click({ force: true })
+ }
+
+ // DataGrid
+
+ typeToFilterInput(value) {
+ this.getDataGridInputFilter()
+ .should('exist')
+ .type(value);
+
+ this.waitForViewToRenderShort();
+ }
+
+ clearFilterInput() {
+ this.getDataGridInputFilter()
+ .should('exist')
+ .clear({ force: true });
+
+ this.waitForViewToRenderShort();
+ }
+
+ closeFilter() {
+ this.getDataGridPopupFilterCloseBtn()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForViewToRenderShort();
+ }
+
+ // Header
+
+ openStatusFilter() {
+ this.getDataGridExecStatusFilterOpenerBtn()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForViewToRenderShort();
+ }
+
+ openTypeFilter() {
+ this.getDataGridExecTypeFilterOpenerBtn()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForViewToRenderShort();
+ }
+
+ openIDFilter() {
+ this.getDataGridExecIDFilterOpenerBtn()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForViewToRenderShort();
+ }
+
+ openVersionFilter() {
+ this.getDataGridExecVersionFilterOpenerBtn()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForViewToRenderShort();
+ }
+
+ sortByExecDuration() {
+ this.getDataGridExecDurationSortBtn()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForViewToRenderShort();
+ }
+
+ sortByExecStart() {
+ this.getDataGridExecStartSortBtn()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForViewToRenderShort();
+ }
+
+ sortByExecEnd() {
+ this.getDataGridExecEndSortBtn()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForViewToRenderShort();
+ }
+}
diff --git a/projects/frontend/data-pipelines/gui/e2e/support/pages/app/lib/manage/data-jobs.po.js b/projects/frontend/data-pipelines/gui/e2e/support/pages/app/lib/manage/data-jobs.po.js
new file mode 100644
index 0000000000..1de29752f5
--- /dev/null
+++ b/projects/frontend/data-pipelines/gui/e2e/support/pages/app/lib/manage/data-jobs.po.js
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2021-2023 VMware, Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { DataJobsBasePO } from '../../../../application/data-jobs-base.po';
+
+export class DataJobsManagePage extends DataJobsBasePO {
+ static getPage() {
+ return new DataJobsManagePage();
+ }
+
+ static navigateTo() {
+ return super.navigateTo('[data-cy=navigation-link-manage-datajobs]');
+ }
+
+ getPageTitle() {
+ return cy.get('[data-cy=data-pipelines-manage-page-main-title]');
+ }
+
+ getDataGrid() {
+ return cy.get('[data-cy=data-pipelines-manage-grid]');
+ }
+
+ getDataGridCell(content, timeout) {
+ return cy.get('[id^="clr-dg-row"] > .datagrid-row-scrollable > .datagrid-scrolling-cells > .ng-star-inserted')
+ .contains(new RegExp(`^\\s*${ content }\\s*$`), { timeout: this.resolveTimeout(timeout) });
+ }
+
+ getDataGridRow(jobName) {
+ cy.log(`Looking for Job row: ${ jobName }`);
+
+ return this.getDataGridCell(jobName)
+ .parents('clr-dg-row');
+ }
+
+ getDataGridNavigateBtn(team, job) {
+ return cy.get('[data-cy=data-pipelines-manage-grid-details-link][data-job-params="' + team + ';' + job + '"]');
+ }
+
+ getDataGridSearchInput() {
+ return cy.get('[data-test-id=search-input]')
+ .first();
+ }
+
+ getDataGridClearSearchButton() {
+ return cy.get('[data-test-id="clear-search-btn"]').first();
+ }
+
+ searchByJobName(jobName) {
+ this.getDataGridSearchInput()
+ .type(jobName);
+
+ this.waitForBackendRequestCompletion();
+
+ this.waitForViewToRender();
+ }
+
+ clearSearchField() {
+ this.getDataGridSearchInput()
+ .should('be.visible')
+ .clear();
+
+ this.waitForBackendRequestCompletion();
+
+ this.waitForViewToRender();
+ }
+
+ clearSearchFieldWithButton() {
+ this.getDataGridClearSearchButton()
+ .should('be.visible')
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion();
+
+ this.waitForViewToRender();
+ }
+
+ refreshDataGrid() {
+ cy.get('[data-cy=data-pipelines-manage-refresh-btn]')
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion();
+
+ this.waitForViewToRender();
+ }
+
+ openJobDetails(teamName, jobName) {
+ this.getDataGridNavigateBtn(teamName, jobName)
+ .scrollIntoView()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion();
+
+ this.waitForViewToRenderShort();
+
+ cy.get('[data-cy=data-pipelines-job-page]')
+ .should('be.visible');
+ }
+
+ selectRow(jobName) {
+ this.getDataGridRow(jobName)
+ .find('.datagrid-select input')
+ .check({ force: true });
+ }
+
+ getExecuteNowGridButton() {
+ return cy.get('[data-cy=data-pipelines-manage-grid-execute-btn]');
+ }
+
+ changeStatus(newStatus) {
+ cy.get(`[data-cy=data-pipelines-job-${ newStatus }-btn]`)
+ .should('exist')
+ .should('be.enabled')
+ .click({ force: true });
+ }
+
+ getJobStatus(jobName) {
+ return this.getDataGridRow(jobName)
+ .then(($row) => {
+ if ($row.find('[data-cy=data-pipelines-job-disabled]').length) {
+ return 'disable';
+ }
+
+ if ($row.find('[data-cy=data-pipelines-job-enabled]').length) {
+ return 'enable';
+ }
+
+ return 'not_deployed';
+ });
+ }
+
+ toggleJobStatus(jobName) {
+ this.selectRow(jobName);
+
+ this.getJobStatus(jobName)
+ .then((currentStatus) => {
+ if (currentStatus === 'not_deployed') {
+ throw new Error('Data job is not Deployed.');
+ }
+
+ const newStatus = currentStatus === 'enable'
+ ? 'disable'
+ : 'enable';
+
+ cy.log(`Current status: ${ currentStatus }, new status: ${ newStatus }`);
+
+ this.changeStatus(newStatus)
+
+ this.confirmInConfirmDialog();
+
+ this.getToastTitle()
+ .should('exist')
+ .should('contain.text', 'Status update completed');
+
+ this.getToastDismiss()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForClickThinkingTime(); // Natural wait for User action
+
+ this.refreshDataGrid();
+
+ this.getJobStatus(jobName)
+ .then((changedStatus) => {
+ expect(changedStatus).to.equal(newStatus);
+ });
+ });
+ }
+
+ filterByJobName(jobName) {
+ cy.get('[data-cy=data-pipelines-jobs-name-column] > .datagrid-column-flex > clr-dg-string-filter.ng-star-inserted > clr-dg-filter > .datagrid-filter-toggle > cds-icon')
+ .click({ force: true });
+
+ cy.get('div.datagrid-filter > input')
+ .should('be.visible')
+ .type(jobName);
+
+ this.getPageTitle()
+ .should('be.visible')
+ .click({ force: true });
+
+ this.waitForBackendRequestCompletion();
+
+ this.waitForViewToRenderShort();
+ }
+
+ executeDataJob(jobName) {
+ this.selectRow(jobName);
+
+ this.waitForClickThinkingTime();
+
+ this.getExecuteNowGridButton()
+ .should('exist')
+ .click({ force: true });
+
+ this.waitForViewToRenderShort();
+
+ this.confirmInConfirmDialog();
+ }
+
+ prepareAdditionalTestJob() {
+ cy.prepareAdditionalTestJobs();
+
+ this.waitForApiModifyCall();
+ }
+}