This is an automated email from the ASF dual-hosted git repository.
kirs pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler.git
The following commit(s) were added to refs/heads/dev by this push:
new 37ba1eb Add E2E tests for some core features (#7025)
37ba1eb is described below
commit 37ba1eb5adb962f6f13825ee0375e82b4823f254
Author: kezhenxu94 <[email protected]>
AuthorDate: Tue Nov 30 13:07:55 2021 +0800
Add E2E tests for some core features (#7025)
---
.github/workflows/e2e.yml | 16 +-
.../dolphinscheduler/e2e/cases/ProjectE2ETest.java | 68 +++
.../dolphinscheduler/e2e/cases/TenantE2ETest.java | 88 +++
.../e2e/cases/WorkflowE2ETest.java | 200 +++++++
.../e2e/cases/security/TenantE2ETest.java | 98 ----
.../dolphinscheduler/e2e/pages/LoginPage.java | 25 +-
.../e2e/pages/common/CodeEditor.java | 45 ++
.../e2e/pages/common/NavBarPage.java | 62 +++
.../e2e/pages/project/ProjectDetailPage.java | 58 ++
.../e2e/pages/project/ProjectPage.java | 114 ++++
.../project/workflow/WorkflowDefinitionTab.java | 123 +++++
.../e2e/pages/project/workflow/WorkflowForm.java | 85 +++
.../project/workflow/WorkflowInstanceTab.java | 107 ++++
.../pages/project/workflow/WorkflowRunDialog.java | 46 ++
.../pages/project/workflow/WorkflowSaveDialog.java | 115 ++++
.../pages/project/workflow/task/ShellTaskForm.java | 42 ++
.../project/workflow/task/SubWorkflowTaskForm.java | 31 ++
.../pages/project/workflow/task/TaskNodeForm.java | 93 ++++
.../e2e/pages/security/SecurityPage.java | 51 ++
.../e2e/pages/{ => security}/TenantPage.java | 55 +-
.../docker/{tenant => basic}/docker-compose.yaml | 1 +
.../src/test/resources/dragAndDrop.js | 55 ++
.../e2e/core/DolphinSchedulerExtension.java | 45 +-
dolphinscheduler-e2e/pom.xml | 18 +-
dolphinscheduler-ui/pom.xml | 2 +-
.../conf/home/pages/dag/_source/canvas/taskbar.vue | 3 +-
.../conf/home/pages/dag/_source/canvas/toolbar.vue | 1 +
.../src/js/conf/home/pages/dag/_source/config.js | 18 +-
.../src/js/conf/home/pages/dag/_source/dag.vue | 1 +
.../home/pages/dag/_source/formModel/formModel.vue | 2 +
.../formModel/tasks/_source/localParams.vue | 2 +
.../pages/dag/_source/udp/_source/selectTenant.vue | 2 +
.../src/js/conf/home/pages/dag/_source/udp/udp.vue | 3 +-
.../pages/definition/pages/list/_source/list.vue | 14 +-
.../pages/definition/pages/list/_source/start.vue | 2 +-
.../projects/pages/definition/pages/list/index.vue | 2 +-
.../pages/instance/pages/list/_source/list.vue | 12 +-
.../projects/pages/list/_source/createProject.vue | 3 +-
.../pages/projects/pages/list/_source/list.vue | 6 +-
.../conf/home/pages/projects/pages/list/index.vue | 2 +-
.../pages/projects/pages/taskDefinition/index.vue | 614 ++++++++++-----------
.../src/js/module/components/nav/nav.vue | 4 +-
.../components/secondaryMenu/_source/menu.js | 9 +-
.../components/secondaryMenu/secondaryMenu.vue | 4 +-
44 files changed, 1861 insertions(+), 486 deletions(-)
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index 244b0e5..5ccf937 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -27,10 +27,6 @@ on:
branches:
- dev
-env:
- TAG: ci
- RECORDING_PATH: /tmp/recording
-
name: E2E
concurrency:
@@ -45,7 +41,13 @@ jobs:
matrix:
case:
- name: Tenant
- class: org.apache.dolphinscheduler.e2e.cases.security.TenantE2ETest
+ class: org.apache.dolphinscheduler.e2e.cases.TenantE2ETest
+ - name: Project
+ class: org.apache.dolphinscheduler.e2e.cases.ProjectE2ETest
+ - name: Workflow
+ class: org.apache.dolphinscheduler.e2e.cases.WorkflowE2ETest
+ env:
+ RECORDING_PATH: /tmp/recording-${{ matrix.case.name }}
steps:
- uses: actions/checkout@v2
with:
@@ -62,13 +64,13 @@ jobs:
run: TAG=ci sh ./docker/build/hooks/build
- name: Run Test
run: |
- ./mvnw -f dolphinscheduler-e2e/pom.xml -am \
+ ./mvnw -B -f dolphinscheduler-e2e/pom.xml -am \
-DfailIfNoTests=false \
-Dtest=${{ matrix.case.class }} test
- uses: actions/upload-artifact@v2
if: always()
name: Upload Recording
with:
- name: recording
+ name: recording-${{ matrix.case.name }}
path: ${{ env.RECORDING_PATH }}
retention-days: 1
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/ProjectE2ETest.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/ProjectE2ETest.java
new file mode 100644
index 0000000..9615075
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/ProjectE2ETest.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to Apache Software Foundation (ASF) under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Apache Software Foundation (ASF) licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.dolphinscheduler.e2e.cases;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+
+import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
+import org.apache.dolphinscheduler.e2e.pages.LoginPage;
+import org.apache.dolphinscheduler.e2e.pages.project.ProjectPage;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.openqa.selenium.remote.RemoteWebDriver;
+
+@DolphinScheduler(composeFiles = "docker/basic/docker-compose.yaml")
+class ProjectE2ETest {
+ private static final String project = "test-project-1";
+
+ private static RemoteWebDriver browser;
+
+ @BeforeAll
+ public static void setup() {
+ new LoginPage(browser)
+ .login("admin", "dolphinscheduler123")
+ .goToNav(ProjectPage.class);
+ }
+
+ @Test
+ @Order(1)
+ void testCreateProject() {
+ new ProjectPage(browser).create(project);
+ }
+
+ @Test
+ @Order(30)
+ void testDeleteProject() {
+ final var page = new ProjectPage(browser);
+ page.delete(project);
+
+ await().untilAsserted(() -> {
+ browser.navigate().refresh();
+ assertThat(
+ page.projectList()
+ ).noneMatch(
+ it -> it.getText().contains(project)
+ );
+ });
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/TenantE2ETest.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/TenantE2ETest.java
new file mode 100644
index 0000000..9de3edb
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/TenantE2ETest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to Apache Software Foundation (ASF) under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Apache Software Foundation (ASF) licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.dolphinscheduler.e2e.cases;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+
+import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
+import org.apache.dolphinscheduler.e2e.pages.LoginPage;
+import org.apache.dolphinscheduler.e2e.pages.security.SecurityPage;
+import org.apache.dolphinscheduler.e2e.pages.security.TenantPage;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.remote.RemoteWebDriver;
+
+@DolphinScheduler(composeFiles = "docker/basic/docker-compose.yaml")
+class TenantE2ETest {
+ private static final String tenant = System.getProperty("user.name");
+
+ private static RemoteWebDriver browser;
+
+ @BeforeAll
+ public static void setup() {
+ new LoginPage(browser)
+ .login("admin", "dolphinscheduler123")
+ .goToNav(SecurityPage.class)
+ .goToTab(TenantPage.class)
+ ;
+ }
+
+ @Test
+ @Order(10)
+ void testCreateTenant() {
+ new TenantPage(browser).create(tenant);
+ }
+
+ @Test
+ @Order(20)
+ void testCreateDuplicateTenant() {
+ final var page = new TenantPage(browser);
+
+ page.create(tenant);
+
+ await().untilAsserted(() ->
+ assertThat(browser.findElement(By.tagName("body")).getText())
+ .contains("already exists")
+ );
+
+ page.createTenantForm().buttonCancel().click();
+ }
+
+ @Test
+ @Order(30)
+ void testDeleteTenant() {
+ final var page = new TenantPage(browser);
+ page.delete(tenant);
+
+ await().untilAsserted(() -> {
+ browser.navigate().refresh();
+ assertThat(
+ page.tenantList()
+ ).noneMatch(
+ it -> it.getText().contains(tenant)
+ );
+ });
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java
new file mode 100644
index 0000000..cc51932
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to Apache Software Foundation (ASF) under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Apache Software Foundation (ASF) licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.dolphinscheduler.e2e.cases;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+
+import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
+import org.apache.dolphinscheduler.e2e.pages.LoginPage;
+import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
+import org.apache.dolphinscheduler.e2e.pages.project.ProjectPage;
+import
org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowDefinitionTab;
+import
org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowForm.TaskType;
+import
org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowInstanceTab;
+import
org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowInstanceTab.Row;
+import
org.apache.dolphinscheduler.e2e.pages.project.workflow.task.ShellTaskForm;
+import
org.apache.dolphinscheduler.e2e.pages.project.workflow.task.SubWorkflowTaskForm;
+import org.apache.dolphinscheduler.e2e.pages.security.SecurityPage;
+import org.apache.dolphinscheduler.e2e.pages.security.TenantPage;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.openqa.selenium.remote.RemoteWebDriver;
+
+@DolphinScheduler(composeFiles = "docker/basic/docker-compose.yaml")
+class WorkflowE2ETest {
+ private static final String project = "test-workflow-1";
+ private static final String tenant = System.getProperty("user.name");
+
+ private static RemoteWebDriver browser;
+
+ @BeforeAll
+ public static void setup() {
+ new LoginPage(browser)
+ .login("admin", "dolphinscheduler123")
+ .goToNav(SecurityPage.class)
+ .goToTab(TenantPage.class)
+ .create(tenant)
+ .goToNav(ProjectPage.class)
+ .create(project)
+ ;
+ }
+
+ @AfterAll
+ public static void cleanup() {
+ new NavBarPage(browser)
+ .goToNav(ProjectPage.class)
+ .goTo(project)
+ .goToTab(WorkflowDefinitionTab.class)
+ .cancelPublishAll()
+ .deleteAll()
+ ;
+ new NavBarPage(browser)
+ .goToNav(ProjectPage.class)
+ .delete(project)
+ .goToNav(SecurityPage.class)
+ .goToTab(TenantPage.class)
+ .delete(tenant)
+ ;
+ }
+
+ @Test
+ @Order(1)
+ void testCreateWorkflow() {
+ final var workflow = "test-workflow-1";
+
+ final var workflowDefinitionPage =
+ new ProjectPage(browser)
+ .goTo(project)
+ .goToTab(WorkflowDefinitionTab.class);
+
+ workflowDefinitionPage
+ .createWorkflow()
+
+ .<ShellTaskForm> addTask(TaskType.SHELL)
+ .script("echo ${today}\necho ${global_param}\n")
+ .name("test-1")
+ .addParam("today", "${system.datetime}")
+ .submit()
+
+ .submit()
+ .name(workflow)
+ .tenant(tenant)
+ .addGlobalParam("global_param", "hello world")
+ .submit()
+ ;
+
+ await().untilAsserted(() -> assertThat(
+ workflowDefinitionPage.workflowList()
+ ).anyMatch(it -> it.getText().contains(workflow)));
+
+ workflowDefinitionPage.publish(workflow);
+ }
+
+ @Test
+ @Order(10)
+ void testCreateSubWorkflow() {
+ final var workflow = "test-sub-workflow-1";
+
+ final var workflowDefinitionPage =
+ new ProjectPage(browser)
+ .goToNav(ProjectPage.class)
+ .goTo(project)
+ .goToTab(WorkflowDefinitionTab.class);
+
+ workflowDefinitionPage
+ .createWorkflow()
+
+ .<SubWorkflowTaskForm> addTask(TaskType.SUB_PROCESS)
+ .submit()
+
+ .submit()
+ .name(workflow)
+ .tenant(tenant)
+ .addGlobalParam("global_param", "hello world")
+ .submit()
+ ;
+
+ await().untilAsserted(() -> assertThat(
+ workflowDefinitionPage.workflowList()
+ ).anyMatch(it -> it.getText().contains(workflow)));
+
+ workflowDefinitionPage.publish(workflow);
+ }
+
+ @Test
+ @Order(30)
+ void testRunWorkflow() {
+ final var workflow = "test-workflow-1";
+
+ final var projectPage =
+ new ProjectPage(browser)
+ .goToNav(ProjectPage.class)
+ .goTo(project);
+
+ projectPage
+ .goToTab(WorkflowInstanceTab.class)
+ .deleteAll();
+
+ projectPage
+ .goToTab(WorkflowDefinitionTab.class)
+ .run(workflow)
+ .submit();
+
+ await().untilAsserted(() -> {
+ browser.navigate().refresh();
+
+ final Row row = projectPage
+ .goToTab(WorkflowInstanceTab.class)
+ .instances()
+ .iterator()
+ .next();
+
+ assertThat(row.isSuccess()).isTrue();
+ assertThat(row.executionTime()).isEqualTo(1);
+ });
+
+ // Test rerun
+ projectPage
+ .goToTab(WorkflowInstanceTab.class)
+ .instances()
+ .stream()
+ .filter(it -> it.rerunButton().isDisplayed())
+ .iterator()
+ .next()
+ .rerun();
+
+ await().untilAsserted(() -> {
+ browser.navigate().refresh();
+
+ final Row row = projectPage
+ .goToTab(WorkflowInstanceTab.class)
+ .instances()
+ .iterator()
+ .next();
+
+ assertThat(row.isSuccess()).isTrue();
+ assertThat(row.executionTime()).isEqualTo(2);
+ });
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/security/TenantE2ETest.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/security/TenantE2ETest.java
deleted file mode 100644
index ab3159a..0000000
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/security/TenantE2ETest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Licensed to Apache Software Foundation (ASF) under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Apache Software Foundation (ASF) licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.dolphinscheduler.e2e.cases.security;
-
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.awaitility.Awaitility.await;
-
-import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
-import org.apache.dolphinscheduler.e2e.pages.LoginPage;
-import org.apache.dolphinscheduler.e2e.pages.TenantPage;
-
-import org.junit.jupiter.api.Order;
-import org.junit.jupiter.api.Test;
-import org.openqa.selenium.By;
-import org.openqa.selenium.WebElement;
-import org.openqa.selenium.remote.RemoteWebDriver;
-
-@DolphinScheduler(composeFiles = "docker/tenant/docker-compose.yaml")
-class TenantE2ETest {
- private RemoteWebDriver browser;
-
- @Test
- @Order(1)
- void testLogin() {
- final LoginPage page = new LoginPage(browser);
- page.inputUsername().sendKeys("admin");
- page.inputPassword().sendKeys("dolphinscheduler123");
- page.buttonLogin().click();
- }
-
- @Test
- @Order(10)
- void testCreateTenant() {
- final TenantPage page = new TenantPage(browser);
- final String tenant = System.getProperty("user.name");
-
- page.buttonCreateTenant().click();
- page.createTenantForm().inputTenantCode().sendKeys(tenant);
- page.createTenantForm().inputDescription().sendKeys("Test");
- page.createTenantForm().buttonSubmit().click();
-
- await().untilAsserted(() -> assertThat(page.tenantList())
- .as("Tenant list should contain newly-created tenant")
- .extracting(WebElement::getText)
- .anyMatch(it -> it.contains(tenant)));
- }
-
- @Test
- @Order(20)
- void testCreateDuplicateTenant() {
- final String tenant = System.getProperty("user.name");
- final TenantPage page = new TenantPage(browser);
- page.buttonCreateTenant().click();
- page.createTenantForm().inputTenantCode().sendKeys(tenant);
- page.createTenantForm().inputDescription().sendKeys("Test");
- page.createTenantForm().buttonSubmit().click();
-
- await().untilAsserted(() ->
assertThat(browser.findElementByTagName("body")
-
.getText().contains("already exists"))
- .as("Should fail when creating a duplicate tenant")
- .isTrue());
-
- page.createTenantForm().buttonCancel().click();
- }
-
- @Test
- @Order(30)
- void testDeleteTenant() {
- final String tenant = System.getProperty("user.name");
- final TenantPage page = new TenantPage(browser);
-
- page.tenantList()
- .stream()
- .filter(it -> it.getText().contains(tenant))
- .findFirst()
- .ifPresent(it -> it.findElement(By.className("delete")).click());
-
- page.buttonConfirm().click();
- }
-}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/LoginPage.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/LoginPage.java
index a772517..f2431d6 100644
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/LoginPage.java
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/LoginPage.java
@@ -19,17 +19,20 @@
package org.apache.dolphinscheduler.e2e.pages;
+import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
+import org.apache.dolphinscheduler.e2e.pages.security.TenantPage;
+
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.FindBy;
-import org.openqa.selenium.support.PageFactory;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
import lombok.Getter;
+import lombok.SneakyThrows;
@Getter
-public final class LoginPage {
- private final RemoteWebDriver driver;
-
+public final class LoginPage extends NavBarPage {
@FindBy(id = "input-username")
private WebElement inputUsername;
@@ -40,8 +43,18 @@ public final class LoginPage {
private WebElement buttonLogin;
public LoginPage(RemoteWebDriver driver) {
- this.driver = driver;
+ super(driver);
+ }
+
+ @SneakyThrows
+ public TenantPage login(String username, String password) {
+ inputUsername().sendKeys(username);
+ inputPassword().sendKeys(password);
+ buttonLogin().click();
+
+ new WebDriverWait(driver(), 10)
+ .until(ExpectedConditions.urlContains("/#/security"));
- PageFactory.initElements(driver, this);
+ return new TenantPage(driver);
}
}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/CodeEditor.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/CodeEditor.java
new file mode 100644
index 0000000..5a8645d
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/CodeEditor.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.dolphinscheduler.e2e.pages.common;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.PageFactory;
+
+import lombok.Getter;
+
+@Getter
+public final class CodeEditor {
+ @FindBy(className = "CodeMirror")
+ private WebElement editor;
+
+ public CodeEditor(WebDriver driver) {
+ PageFactory.initElements(driver, this);
+ }
+
+ public CodeEditor content(String content) {
+ editor.findElement(By.className("CodeMirror-line")).click();
+ editor.findElement(By.tagName("textarea")).sendKeys(content);
+
+ return this;
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/NavBarPage.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/NavBarPage.java
new file mode 100644
index 0000000..546a714
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/common/NavBarPage.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.dolphinscheduler.e2e.pages.common;
+
+import org.apache.dolphinscheduler.e2e.pages.project.ProjectPage;
+import org.apache.dolphinscheduler.e2e.pages.security.SecurityPage;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.remote.RemoteWebDriver;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.PageFactory;
+
+import lombok.Getter;
+
+@Getter
+public class NavBarPage {
+ protected final RemoteWebDriver driver;
+
+ @FindBy(id = "project-tab")
+ private WebElement projectTab;
+ @FindBy(id = "security-tab")
+ private WebElement securityTab;
+
+ public NavBarPage(RemoteWebDriver driver) {
+ this.driver = driver;
+
+ PageFactory.initElements(driver, this);
+ }
+
+ public <T extends NavBarItem> T goToNav(Class<T> nav) {
+ if (nav == ProjectPage.class) {
+ projectTab().click();
+ return nav.cast(new ProjectPage(driver));
+ }
+ if (nav == SecurityPage.class) {
+ securityTab().click();
+ return nav.cast(new SecurityPage(driver));
+ }
+
+ throw new UnsupportedOperationException("Unknown nav bar");
+ }
+
+ public interface NavBarItem {
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectDetailPage.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectDetailPage.java
new file mode 100644
index 0000000..5dd7051
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectDetailPage.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.dolphinscheduler.e2e.pages.project;
+
+import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
+import
org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowDefinitionTab;
+import
org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowInstanceTab;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.remote.RemoteWebDriver;
+import org.openqa.selenium.support.FindBy;
+
+import lombok.Getter;
+
+@Getter
+public final class ProjectDetailPage extends NavBarPage {
+ @FindBy(className = "process-definition")
+ private WebElement menuProcessDefinition;
+ @FindBy(className = "process-instance")
+ private WebElement menuProcessInstances;
+
+ public ProjectDetailPage(RemoteWebDriver driver) {
+ super(driver);
+ }
+
+ public <T extends Tab> T goToTab(Class<T> tab) {
+ if (tab == WorkflowDefinitionTab.class) {
+ menuProcessDefinition().click();
+ return tab.cast(new WorkflowDefinitionTab(driver));
+ }
+ if (tab == WorkflowInstanceTab.class) {
+ menuProcessInstances().click();
+ return tab.cast(new WorkflowInstanceTab(driver));
+ }
+
+ throw new UnsupportedOperationException("Unknown tab: " +
tab.getName());
+ }
+
+ public interface Tab {
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectPage.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectPage.java
new file mode 100644
index 0000000..86cdb93
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectPage.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.dolphinscheduler.e2e.pages.project;
+
+import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
+import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage.NavBarItem;
+
+import java.util.List;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.remote.RemoteWebDriver;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.FindBys;
+import org.openqa.selenium.support.PageFactory;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import lombok.Getter;
+
+@Getter
+public final class ProjectPage extends NavBarPage implements NavBarItem {
+ @FindBy(id = "button-create-project")
+ private WebElement buttonCreateProject;
+
+ @FindBy(className = "rows-project")
+ private List<WebElement> projectList;
+
+ @FindBys({
+ @FindBy(className = "el-popconfirm"),
+ @FindBy(className = "el-button--primary"),
+ })
+ private List<WebElement> buttonConfirm;
+
+ private final CreateProjectForm createProjectForm;
+
+ public ProjectPage(RemoteWebDriver driver) {
+ super(driver);
+
+ this.createProjectForm = new CreateProjectForm();
+
+ PageFactory.initElements(driver, this);
+ }
+
+ public ProjectPage create(String project) {
+ buttonCreateProject().click();
+ createProjectForm().inputProjectName().sendKeys(project);
+ createProjectForm().buttonSubmit().click();
+
+ new WebDriverWait(driver(), 10)
+
.until(ExpectedConditions.textToBePresentInElementLocated(By.className("project-name"),
project));
+
+ return this;
+ }
+
+ public ProjectPage delete(String project) {
+ projectList()
+ .stream()
+ .filter(it -> it.getText().contains(project))
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("Cannot find project: " +
project))
+ .findElement(By.className("delete")).click();
+
+ buttonConfirm()
+ .stream()
+ .filter(WebElement::isDisplayed)
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("No confirm button is
displayed"))
+ .click();
+
+ return this;
+ }
+
+ public ProjectDetailPage goTo(String project) {
+ projectList().stream()
+ .filter(it -> it.getText().contains(project))
+ .map(it -> it.findElement(By.className("project-name")))
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("Cannot click the
project item"))
+ .click();
+
+ return new ProjectDetailPage(driver);
+ }
+
+ @Getter
+ public class CreateProjectForm {
+ CreateProjectForm() {
+ PageFactory.initElements(driver, this);
+ }
+
+ @FindBy(id = "input-project-name")
+ private WebElement inputProjectName;
+
+ @FindBy(id = "button-submit")
+ private WebElement buttonSubmit;
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowDefinitionTab.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowDefinitionTab.java
new file mode 100644
index 0000000..56f0eb3
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowDefinitionTab.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.dolphinscheduler.e2e.pages.project.workflow;
+
+import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
+import org.apache.dolphinscheduler.e2e.pages.project.ProjectDetailPage;
+
+import java.util.List;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.remote.RemoteWebDriver;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.FindBys;
+
+import lombok.Getter;
+
+@Getter
+public final class WorkflowDefinitionTab extends NavBarPage implements
ProjectDetailPage.Tab {
+ @FindBy(id = "button-create-process")
+ private WebElement buttonCreateProcess;
+ @FindBy(className = "select-all")
+ private WebElement checkBoxSelectAll;
+ @FindBy(className = "button-delete-all")
+ private WebElement buttonDeleteAll;
+ @FindBys({
+ @FindBy(className = "el-popconfirm"),
+ @FindBy(className = "el-button--primary"),
+ })
+ private List<WebElement> buttonConfirm;
+ @FindBy(className = "rows-workflow-definitions")
+ private List<WebElement> workflowList;
+
+ public WorkflowDefinitionTab(RemoteWebDriver driver) {
+ super(driver);
+ }
+
+ public WorkflowForm createWorkflow() {
+ buttonCreateProcess().click();
+
+ return new WorkflowForm(driver);
+ }
+
+ public WorkflowDefinitionTab publish(String workflow) {
+ workflowList()
+ .stream()
+ .filter(it ->
it.findElement(By.className("name")).getAttribute("innerHTML").equals(workflow))
+ .flatMap(it ->
it.findElements(By.className("button-publish")).stream())
+ .filter(WebElement::isDisplayed)
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("Cannot find publish
button in workflow definition"))
+ .click();
+
+ return this;
+ }
+
+ public WorkflowRunDialog run(String workflow) {
+ workflowList()
+ .stream()
+ .filter(it ->
it.findElement(By.className("name")).getAttribute("innerHTML").equals(workflow))
+ .flatMap(it ->
it.findElements(By.className("button-run")).stream())
+ .filter(WebElement::isDisplayed)
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("Cannot find run button in
workflow definition"))
+ .click();
+
+ return new WorkflowRunDialog(this);
+ }
+
+ public WorkflowDefinitionTab cancelPublishAll() {
+ final Supplier<List<WebElement>> cancelButtons = () ->
+ workflowList()
+ .stream()
+ .flatMap(it ->
it.findElements(By.className("button-cancel-publish")).stream())
+ .filter(WebElement::isDisplayed)
+ .collect(Collectors.toList());
+
+ for (var buttons = cancelButtons.get();
+ !buttons.isEmpty();
+ buttons = cancelButtons.get()) {
+ buttons.forEach(WebElement::click);
+ driver().navigate().refresh();
+ }
+
+ return this;
+ }
+
+ public WorkflowDefinitionTab deleteAll() {
+ if (workflowList().isEmpty()) {
+ return this;
+ }
+
+ checkBoxSelectAll().click();
+ buttonDeleteAll().click();
+ buttonConfirm()
+ .stream()
+ .filter(WebElement::isDisplayed)
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("No confirm button is
displayed"))
+ .click();
+
+ return this;
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java
new file mode 100644
index 0000000..95c21b1
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.dolphinscheduler.e2e.pages.project.workflow;
+
+import
org.apache.dolphinscheduler.e2e.pages.project.workflow.task.ShellTaskForm;
+import
org.apache.dolphinscheduler.e2e.pages.project.workflow.task.SubWorkflowTaskForm;
+
+import java.nio.charset.StandardCharsets;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.PageFactory;
+
+import com.google.common.io.Resources;
+
+import lombok.Getter;
+import lombok.SneakyThrows;
+
+@SuppressWarnings("UnstableApiUsage")
+@Getter
+public final class WorkflowForm {
+ private final WebDriver driver;
+ private final WorkflowSaveDialog saveForm;
+
+ @FindBy(id = "button-save")
+ private WebElement buttonSave;
+
+ public WorkflowForm(WebDriver driver) {
+ this.driver = driver;
+ this.saveForm = new WorkflowSaveDialog(this);
+
+ PageFactory.initElements(driver, this);
+ }
+
+ @SneakyThrows
+ @SuppressWarnings("unchecked")
+ public <T> T addTask(TaskType type) {
+ final var task = driver.findElement(By.className("task-item-" +
type.name()));
+ final var canvas = driver.findElement(By.className("dag-container"));
+
+ final var js = (JavascriptExecutor) driver;
+ final var dragAndDrop = String.join("\n",
+ Resources.readLines(Resources.getResource("dragAndDrop.js"),
StandardCharsets.UTF_8));
+ js.executeScript(dragAndDrop, task, canvas);
+
+ switch (type) {
+ case SHELL:
+ return (T) new ShellTaskForm(this);
+ case SUB_PROCESS:
+ return (T) new SubWorkflowTaskForm(this);
+ }
+ throw new UnsupportedOperationException("Unknown task type");
+ }
+
+ public WorkflowSaveDialog submit() {
+ buttonSave().click();
+
+ return new WorkflowSaveDialog(this);
+ }
+
+ public enum TaskType {
+ SHELL,
+ SUB_PROCESS,
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowInstanceTab.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowInstanceTab.java
new file mode 100644
index 0000000..97f29b4
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowInstanceTab.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.dolphinscheduler.e2e.pages.project.workflow;
+
+import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
+import org.apache.dolphinscheduler.e2e.pages.project.ProjectDetailPage;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.remote.RemoteWebDriver;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.FindBys;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+public final class WorkflowInstanceTab extends NavBarPage implements
ProjectDetailPage.Tab {
+ @FindBy(className = "rows-workflow-instances")
+ private List<WebElement> instanceList;
+ @FindBy(className = "select-all")
+ private WebElement checkBoxSelectAll;
+ @FindBy(className = "button-delete-all")
+ private WebElement buttonDeleteAll;
+ @FindBys({
+ @FindBy(className = "el-popconfirm"),
+ @FindBy(className = "el-button--primary"),
+ })
+ private List<WebElement> buttonConfirm;
+
+ public WorkflowInstanceTab(RemoteWebDriver driver) {
+ super(driver);
+ }
+
+ public List<Row> instances() {
+ return instanceList()
+ .stream()
+ .filter(WebElement::isDisplayed)
+ .map(Row::new)
+ .collect(Collectors.toList());
+ }
+
+ public WorkflowInstanceTab deleteAll() {
+ if (instanceList().isEmpty()) {
+ return this;
+ }
+
+ checkBoxSelectAll().click();
+ buttonDeleteAll().click();
+ buttonConfirm()
+ .stream()
+ .filter(WebElement::isDisplayed)
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("No confirm button is
displayed"))
+ .click();
+
+ return this;
+ }
+
+ @RequiredArgsConstructor
+ public static class Row {
+ private final WebElement row;
+
+ public WebElement rerunButton() {
+ return row.findElement(By.className("button-rerun"));
+ }
+
+ public boolean isSuccess() {
+ return !row.findElements(By.className("success")).isEmpty();
+ }
+
+ public int executionTime() {
+ return
Integer.parseInt(row.findElement(By.className("execution-time")).getText());
+ }
+
+ public Row rerun() {
+ row.findElements(By.className("button-rerun"))
+ .stream()
+ .filter(WebElement::isDisplayed)
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("Cannot find rerun
button"))
+ .click();
+
+ return this;
+ }
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowRunDialog.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowRunDialog.java
new file mode 100644
index 0000000..9801378
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowRunDialog.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.dolphinscheduler.e2e.pages.project.workflow;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.PageFactory;
+
+import lombok.Getter;
+
+@Getter
+public final class WorkflowRunDialog {
+ private final WorkflowDefinitionTab parent;
+
+ @FindBy(id = "button-submit")
+ private WebElement buttonSubmit;
+
+ public WorkflowRunDialog(WorkflowDefinitionTab parent) {
+ this.parent = parent;
+
+ PageFactory.initElements(parent().driver(), this);
+ }
+
+ public WorkflowDefinitionTab submit() {
+ buttonSubmit().click();
+
+ return parent();
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowSaveDialog.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowSaveDialog.java
new file mode 100644
index 0000000..ce08926
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowSaveDialog.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.dolphinscheduler.e2e.pages.project.workflow;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.FindBys;
+import org.openqa.selenium.support.PageFactory;
+import org.openqa.selenium.support.pagefactory.ByChained;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import lombok.Getter;
+
+@Getter
+public final class WorkflowSaveDialog {
+ private final WebDriver driver;
+ private final WorkflowForm parent;
+
+ @FindBy(id = "input-name")
+ private WebElement inputName;
+ @FindBy(id = "button-submit")
+ private WebElement buttonSubmit;
+ @FindBys({
+ @FindBy(className = "input-param-key"),
+ @FindBy(tagName = "input"),
+ })
+ private List<WebElement> inputParamKey;
+ @FindBys({
+ @FindBy(className = "input-param-val"),
+ @FindBy(tagName = "input"),
+ })
+ private List<WebElement> inputParamVal;
+ @FindBy(id = "select-tenant")
+ private WebElement selectTenant;
+
+ public WorkflowSaveDialog(WorkflowForm parent) {
+ this.parent = parent;
+ this.driver = parent.driver();
+
+ PageFactory.initElements(driver, this);
+ }
+
+ public WorkflowSaveDialog name(String name) {
+ inputName().sendKeys(name);
+
+ return this;
+ }
+
+ public WorkflowSaveDialog tenant(String tenant) {
+ selectTenant().click();
+
+ final var optionsLocator = By.className("option-tenants");
+
+ new WebDriverWait(driver, 10)
+
.until(ExpectedConditions.visibilityOfElementLocated(optionsLocator));
+
+ driver().findElements(optionsLocator)
+ .stream()
+ .filter(it -> it.getText().contains(tenant))
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("No such tenant: " +
tenant))
+ .click()
+ ;
+
+ return this;
+ }
+
+ public WorkflowSaveDialog addGlobalParam(String key, String val) {
+ assert inputParamKey().size() == inputParamVal().size();
+
+ final var len = inputParamKey().size();
+
+ final var driver = parent().driver();
+ Stream.concat(
+ driver.findElements(new
ByChained(By.className("user-def-params-model"), By.className("add"))).stream(),
+ driver.findElements(new
ByChained(By.className("user-def-params-model"),
By.className("add-dp"))).stream())
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("Cannot find button to
add param"))
+ .click();
+
+ inputParamKey().get(len).sendKeys(key);
+ inputParamVal().get(len).sendKeys(val);
+
+ return this;
+ }
+
+ public WorkflowForm submit() {
+ buttonSubmit().click();
+
+ return parent;
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/ShellTaskForm.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/ShellTaskForm.java
new file mode 100644
index 0000000..70b5f5d
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/ShellTaskForm.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.dolphinscheduler.e2e.pages.project.workflow.task;
+
+import org.apache.dolphinscheduler.e2e.pages.common.CodeEditor;
+import org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowForm;
+
+import lombok.Getter;
+
+@Getter
+public final class ShellTaskForm extends TaskNodeForm {
+ private final CodeEditor codeEditor;
+
+ public ShellTaskForm(WorkflowForm parent) {
+ super(parent);
+
+ this.codeEditor = new CodeEditor(parent.driver());
+ }
+
+ public ShellTaskForm script(String script) {
+ codeEditor().content(script);
+
+ return this;
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/SubWorkflowTaskForm.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/SubWorkflowTaskForm.java
new file mode 100644
index 0000000..a94102f
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/SubWorkflowTaskForm.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.dolphinscheduler.e2e.pages.project.workflow.task;
+
+import org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowForm;
+
+import lombok.Getter;
+
+@Getter
+public final class SubWorkflowTaskForm extends TaskNodeForm {
+ public SubWorkflowTaskForm(WorkflowForm parent) {
+ super(parent);
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java
new file mode 100644
index 0000000..8689a21
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.dolphinscheduler.e2e.pages.project.workflow.task;
+
+import org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowForm;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.FindBys;
+import org.openqa.selenium.support.PageFactory;
+import org.openqa.selenium.support.pagefactory.ByChained;
+
+import lombok.Getter;
+
+@Getter
+public abstract class TaskNodeForm {
+ @FindBy(id = "input-node-name")
+ private WebElement inputNodeName;
+ @FindBy(id = "button-submit")
+ private WebElement buttonSubmit;
+ @FindBys({
+ @FindBy(className = "input-param-key"),
+ @FindBy(tagName = "input"),
+ })
+ private List<WebElement> inputParamKey;
+ @FindBys({
+ @FindBy(className = "input-param-val"),
+ @FindBy(tagName = "input"),
+ })
+ private List<WebElement> inputParamVal;
+
+ private final WorkflowForm parent;
+
+ TaskNodeForm(WorkflowForm parent) {
+ this.parent = parent;
+
+ final var driver = parent.driver();
+
+ PageFactory.initElements(driver, this);
+ }
+
+ public TaskNodeForm name(String name) {
+ inputNodeName().sendKeys(name);
+
+ return this;
+ }
+
+ public TaskNodeForm addParam(String key, String val) {
+ assert inputParamKey().size() == inputParamVal().size();
+
+ final var len = inputParamKey().size();
+
+ final var driver = parent().driver();
+ Stream.concat(
+ driver.findElements(new
ByChained(By.className("user-def-params-model"), By.className("add"))).stream(),
+ driver.findElements(new
ByChained(By.className("user-def-params-model"),
By.className("add-dp"))).stream())
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("Cannot find button to
add param"))
+ .click();
+
+ inputParamKey().get(len).sendKeys(key);
+ inputParamVal().get(len).sendKeys(val);
+
+ return this;
+ }
+
+ public WorkflowForm submit() {
+ buttonSubmit.click();
+
+ return parent();
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/SecurityPage.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/SecurityPage.java
new file mode 100644
index 0000000..8e4c7b9
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/SecurityPage.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.dolphinscheduler.e2e.pages.security;
+
+import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
+import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage.NavBarItem;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.remote.RemoteWebDriver;
+import org.openqa.selenium.support.FindBy;
+
+import lombok.Getter;
+
+@Getter
+public class SecurityPage extends NavBarPage implements NavBarItem {
+ @FindBy(className = "tenant-manage")
+ private WebElement menuTenantManage;
+
+ public SecurityPage(RemoteWebDriver driver) {
+ super(driver);
+ }
+
+ public <T extends SecurityPage.Tab> T goToTab(Class<T> tab) {
+ if (tab == TenantPage.class) {
+ menuTenantManage().click();
+ return tab.cast(new TenantPage(driver));
+ }
+
+ throw new UnsupportedOperationException("Unknown tab: " +
tab.getName());
+ }
+
+ public interface Tab {
+ }
+}
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/TenantPage.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/TenantPage.java
similarity index 55%
rename from
dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/TenantPage.java
rename to
dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/TenantPage.java
index da97349..6e9c548 100644
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/TenantPage.java
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/security/TenantPage.java
@@ -17,12 +17,18 @@
* under the License.
*/
-package org.apache.dolphinscheduler.e2e.pages;
+package org.apache.dolphinscheduler.e2e.pages.security;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+
+import org.apache.dolphinscheduler.e2e.pages.common.NavBarPage;
import java.util.List;
-import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
+import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.FindBys;
import org.openqa.selenium.support.PageFactory;
@@ -30,9 +36,7 @@ import org.openqa.selenium.support.PageFactory;
import lombok.Getter;
@Getter
-public final class TenantPage {
- private final WebDriver driver;
-
+public final class TenantPage extends NavBarPage implements SecurityPage.Tab {
@FindBy(id = "button-create-tenant")
private WebElement buttonCreateTenant;
@@ -40,18 +44,47 @@ public final class TenantPage {
private List<WebElement> tenantList;
@FindBys({
- @FindBy(className = "el-popconfirm"),
- @FindBy(className = "el-button--primary"),
+ @FindBy(className = "el-popconfirm"),
+ @FindBy(className = "el-button--primary"),
})
private WebElement buttonConfirm;
private final CreateTenantForm createTenantForm;
- public TenantPage(WebDriver driver) {
- this.driver = driver;
- this.createTenantForm = new CreateTenantForm();
+ public TenantPage(RemoteWebDriver driver) {
+ super(driver);
+
+ createTenantForm = new CreateTenantForm();
+ }
+
+ public TenantPage create(String tenant) {
+ return create(tenant, "");
+ }
+
+ public TenantPage create(String tenant, String description) {
+ buttonCreateTenant().click();
+ createTenantForm().inputTenantCode().sendKeys(tenant);
+ createTenantForm().inputDescription().sendKeys(description);
+ createTenantForm().buttonSubmit().click();
+
+ await().untilAsserted(() -> assertThat(tenantList())
+ .as("Tenant list should contain newly-created tenant")
+ .extracting(WebElement::getText)
+ .anyMatch(it -> it.contains(tenant)));
+
+ return this;
+ }
+
+ public TenantPage delete(String tenant) {
+ tenantList()
+ .stream()
+ .filter(it -> it.getText().contains(tenant))
+ .findFirst()
+ .ifPresent(it -> it.findElement(By.className("delete")).click());
+
+ buttonConfirm().click();
- PageFactory.initElements(driver, this);
+ return this;
}
@Getter
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/resources/docker/tenant/docker-compose.yaml
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/resources/docker/basic/docker-compose.yaml
similarity index 96%
rename from
dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/resources/docker/tenant/docker-compose.yaml
rename to
dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/resources/docker/basic/docker-compose.yaml
index ec761b2..22bd9ec 100644
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/resources/docker/tenant/docker-compose.yaml
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/resources/docker/basic/docker-compose.yaml
@@ -23,6 +23,7 @@ services:
command: [ standalone-server ]
environment:
DATABASE_TYPE: h2
+ WORKER_TENANT_AUTO_CREATE: 'true'
expose:
- 12345
networks:
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/resources/dragAndDrop.js
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/resources/dragAndDrop.js
new file mode 100644
index 0000000..96011d9
--- /dev/null
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/resources/dragAndDrop.js
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+function createEvent(typeOfEvent) {
+ const event = document.createEvent("CustomEvent");
+ event.initCustomEvent(typeOfEvent, true, true, null);
+ event.dataTransfer = {
+ data: {},
+ setData: function (key, value) {
+ this.data[key] = value;
+ },
+ getData: function (key) {
+ return this.data[key];
+ }
+ };
+ return event;
+}
+
+function dispatchEvent(element, event, transferData) {
+ if (transferData !== undefined) {
+ event.dataTransfer = transferData;
+ }
+ if (element.dispatchEvent) {
+ element.dispatchEvent(event);
+ } else if (element.fireEvent) {
+ element.fireEvent("on" + event.type, event);
+ }
+}
+
+function simulateHTML5DragAndDrop(element, destination) {
+ const dragStartEvent = createEvent('dragstart');
+ dispatchEvent(element, dragStartEvent);
+ const dropEvent = createEvent('drop');
+ dispatchEvent(destination, dropEvent, dragStartEvent.dataTransfer);
+ const dragEndEvent = createEvent('dragend');
+ dispatchEvent(element, dragEndEvent, dropEvent.dataTransfer);
+}
+
+const source = arguments[0];
+const destination = arguments[1];
+simulateHTML5DragAndDrop(source, destination);
diff --git
a/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/DolphinSchedulerExtension.java
b/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/DolphinSchedulerExtension.java
index d7f3232..d6b1886 100644
---
a/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/DolphinSchedulerExtension.java
+++
b/dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/DolphinSchedulerExtension.java
@@ -24,6 +24,8 @@ import static
org.testcontainers.containers.VncRecordingContainer.VncRecordingFo
import java.io.File;
import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -60,8 +62,8 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
final class DolphinSchedulerExtension
- implements BeforeAllCallback, AfterAllCallback,
- BeforeEachCallback {
+ implements BeforeAllCallback, AfterAllCallback,
+ BeforeEachCallback {
private final boolean LOCAL_MODE =
Objects.equals(System.getProperty("local"), "true");
private RemoteWebDriver driver;
@@ -71,7 +73,7 @@ final class DolphinSchedulerExtension
@Override
@SuppressWarnings("UnstableApiUsage")
public void beforeAll(ExtensionContext context) throws IOException {
- Awaitility.setDefaultTimeout(Duration.ofSeconds(5));
+ Awaitility.setDefaultTimeout(Duration.ofSeconds(10));
Awaitility.setDefaultPollInterval(Duration.ofSeconds(1));
Network network = null;
@@ -115,8 +117,8 @@ final class DolphinSchedulerExtension
record = Files.createTempDirectory("record-");
}
browser = new BrowserWebDriverContainer<>()
- .withCapabilities(new ChromeOptions())
- .withRecordingMode(RECORD_ALL, record.toFile(), MP4);
+ .withCapabilities(new ChromeOptions())
+ .withRecordingMode(RECORD_ALL, record.toFile(), MP4);
if (network != null) {
browser.withNetwork(network);
}
@@ -127,6 +129,8 @@ final class DolphinSchedulerExtension
driver.manage().timeouts()
.implicitlyWait(5, TimeUnit.SECONDS)
.pageLoadTimeout(5, TimeUnit.SECONDS);
+ driver.manage().window()
+ .maximize();
if (address == null) {
try {
address =
HostAndPort.fromParts(browser.getTestHostIpAddress(), 8888);
@@ -142,6 +146,12 @@ final class DolphinSchedulerExtension
driver.get(new URL("http", address.getHost(), address.getPort(),
rootPath).toString());
browser.beforeTest(new TestDescription(context));
+
+ final Class<?> clazz = context.getRequiredTestClass();
+ Stream.of(clazz.getDeclaredFields())
+ .filter(it -> Modifier.isStatic(it.getModifiers()))
+ .filter(f -> WebDriver.class.isAssignableFrom(f.getType()))
+ .forEach(it -> setDriver(clazz, it));
}
@Override
@@ -158,14 +168,16 @@ final class DolphinSchedulerExtension
final Object instance = context.getRequiredTestInstance();
Stream.of(instance.getClass().getDeclaredFields())
.filter(f -> WebDriver.class.isAssignableFrom(f.getType()))
- .forEach(it -> {
- try {
- it.setAccessible(true);
- it.set(instance, driver);
- } catch (IllegalAccessException e) {
- LOGGER.error("Failed to inject web driver to field: {}",
it.getName(), e);
- }
- });
+ .forEach(it -> setDriver(instance, it));
+ }
+
+ private void setDriver(Object object, Field field) {
+ try {
+ field.setAccessible(true);
+ field.set(object, driver);
+ } catch (IllegalAccessException e) {
+ LOGGER.error("Failed to inject web driver to field: {}",
field.getName(), e);
+ }
}
private DockerComposeContainer<?> createDockerCompose(ExtensionContext
context) {
@@ -178,9 +190,10 @@ final class DolphinSchedulerExtension
.map(File::new)
.collect(Collectors.toList());
compose = new DockerComposeContainer<>(files)
- .withPull(true)
- .withTailChildContainers(true)
- .waitingFor("dolphinscheduler_1", Wait.forHealthcheck());
+ .withPull(true)
+ .withTailChildContainers(true)
+ .withLogConsumer("dolphinscheduler_1", outputFrame ->
LOGGER.info(outputFrame.getUtf8String()))
+ .waitingFor("dolphinscheduler_1", Wait.forHealthcheck());
return compose;
}
diff --git a/dolphinscheduler-e2e/pom.xml b/dolphinscheduler-e2e/pom.xml
index 1c7ce2e..22f913d 100644
--- a/dolphinscheduler-e2e/pom.xml
+++ b/dolphinscheduler-e2e/pom.xml
@@ -31,8 +31,8 @@
</modules>
<properties>
- <maven.compiler.source>8</maven.compiler.source>
- <maven.compiler.target>8</maven.compiler.target>
+ <maven.compiler.source>11</maven.compiler.source>
+ <maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.7.2</junit.version>
@@ -41,18 +41,21 @@
<assertj-core.version>3.20.2</assertj-core.version>
<awaitility.version>4.1.0</awaitility.version>
<kotlin.version>1.5.30</kotlin.version>
+ <slf4j-api.version>1.7.32</slf4j-api.version>
+ <log4j-slf4j-impl.version>2.14.1</log4j-slf4j-impl.version>
+ <guava.version>31.0.1-jre</guava.version>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
- <version>1.7.32</version>
+ <version>${slf4j-api.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
- <version>2.14.1</version>
+ <version>${log4j-slf4j-impl.version}</version>
</dependency>
<dependency>
@@ -110,6 +113,11 @@
<dependencyManagement>
<dependencies>
<dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>${guava.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>${junit.version}</version>
@@ -119,7 +127,7 @@
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
- <version>1.16.0</version>
+ <version>1.16.1</version>
<scope>import</scope>
<type>pom</type>
</dependency>
diff --git a/dolphinscheduler-ui/pom.xml b/dolphinscheduler-ui/pom.xml
index a50b82e..8ffb2d4 100644
--- a/dolphinscheduler-ui/pom.xml
+++ b/dolphinscheduler-ui/pom.xml
@@ -29,7 +29,7 @@
<name>${project.artifactId}</name>
<properties>
- <node.version>v12.20.2</node.version>
+ <node.version>v14.15.1</node.version>
<npm.version>6.14.11</npm.version>
<sonar.sources>src</sonar.sources>
</properties>
diff --git
a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.vue
b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.vue
index 7c5b665..5a46aca 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.vue
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/taskbar.vue
@@ -25,7 +25,8 @@
:key="taskType.name"
@onDragstart="(e) => $emit('on-drag-start', e, taskType)"
:class="{
- disabled: isDetails
+ disabled: isDetails,
+ [`task-item-${taskType.name}`]: true
}"
>
<div class="task-item">
diff --git
a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.vue
b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.vue
index d8f57ad..3b8f415 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.vue
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/canvas/toolbar.vue
@@ -143,6 +143,7 @@
type="primary"
size="mini"
@click="saveProcess"
+ id="button-save"
>{{ $t("Save") }}</el-button
>
<el-button
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/config.js
b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/config.js
index d4a35e5..b56e4e6 100755
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/config.js
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/config.js
@@ -155,28 +155,32 @@ const tasksState = {
desc: `${i18n.$t('Submitted successfully')}`,
color: '#A9A9A9',
icoUnicode: 'ri-record-circle-fill',
- isSpin: false
+ isSpin: false,
+ classNames: 'submitted'
},
RUNNING_EXECUTION: {
id: 1,
desc: `${i18n.$t('Executing')}`,
color: '#0097e0',
icoUnicode: 'el-icon-s-tools',
- isSpin: true
+ isSpin: true,
+ classNames: 'executing'
},
READY_PAUSE: {
id: 2,
desc: `${i18n.$t('Ready to pause')}`,
color: '#07b1a3',
icoUnicode: 'ri-settings-3-line',
- isSpin: false
+ isSpin: false,
+ classNames: 'submitted'
},
PAUSE: {
id: 3,
desc: `${i18n.$t('Pause')}`,
color: '#057c72',
icoUnicode: 'el-icon-video-pause',
- isSpin: false
+ isSpin: false,
+ classNames: 'pause'
},
READY_STOP: {
id: 4,
@@ -197,14 +201,16 @@ const tasksState = {
desc: `${i18n.$t('Failed')}`,
color: '#000000',
icoUnicode: 'el-icon-circle-close',
- isSpin: false
+ isSpin: false,
+ classNames: 'failed'
},
SUCCESS: {
id: 7,
desc: `${i18n.$t('Success')}`,
color: '#33cc00',
icoUnicode: 'el-icon-circle-check',
- isSpin: false
+ isSpin: false,
+ classNames: 'success'
},
NEED_FAULT_TOLERANCE: {
id: 8,
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue
b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue
index deda3f1..47d94a7 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue
@@ -23,6 +23,7 @@
size=""
:with-header="false"
:wrapperClosable="false"
+ class="task-drawer"
>
<!-- fix the bug that Element-ui(2.13.2) auto focus on the first input
-->
<div style="width: 0px; height: 0px; overflow: hidden">
diff --git
a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue
b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue
index ac21d6a..e80f03f 100644
---
a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue
+++
b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue
@@ -60,6 +60,7 @@
:disabled="isDetails"
:placeholder="$t('Please enter name (required)')"
maxlength="100"
+ id="input-node-name"
>
</el-input>
</div>
@@ -437,6 +438,7 @@
:loading="spinnerLoading"
@click="ok()"
:disabled="isDetails"
+ id="button-submit"
>{{ spinnerLoading ? $t("Loading...") : $t("Confirm") }}
</el-button>
</div>
diff --git
a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/_source/localParams.vue
b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/_source/localParams.vue
index ae9be4c..b468cf9 100644
---
a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/_source/localParams.vue
+++
b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/_source/localParams.vue
@@ -26,6 +26,7 @@
size="small"
v-model="localParamsList[$index].prop"
:placeholder="$t('prop(required)')"
+ class="input-param-key"
maxlength="256"
@blur="_verifProp()"
:style="inputStyle">
@@ -64,6 +65,7 @@
size="small"
v-model="localParamsList[$index].value"
:placeholder="$t('value(optional)')"
+ class="input-param-val"
maxlength="256"
@blur="_handleValue()"
:style="inputStyle">
diff --git
a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/udp/_source/selectTenant.vue
b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/udp/_source/selectTenant.vue
index 0a491f9..50a789f 100644
---
a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/udp/_source/selectTenant.vue
+++
b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/udp/_source/selectTenant.vue
@@ -20,9 +20,11 @@
@change="_onChange"
v-model="selectedValue"
size="small"
+ id="select-tenant"
style="width: 180px">
<el-option
v-for="item in itemList"
+ class="option-tenants"
:key="item.id"
:value="item.id"
:label="item.tenantCode">
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/udp/udp.vue
b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/udp/udp.vue
index 100f47f..d39ac79 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/udp/udp.vue
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/udp/udp.vue
@@ -22,6 +22,7 @@
type="text"
size="small"
v-model="name"
+ id="input-name"
:disabled="router.history.current.name ===
'projects-instance-details'"
:placeholder="$t('Please enter name (required)')">
</el-input>
@@ -101,7 +102,7 @@
</div>
</template>
<el-button type="text" size="small" @click="close()"> {{$t('Cancel')}}
</el-button>
- <el-button type="primary" size="small" round :disabled="isDetails"
@click="ok()">{{$t('Add')}}</el-button>
+ <el-button type="primary" size="small" round :disabled="isDetails"
@click="ok()" id="button-submit">{{$t('Add')}}</el-button>
</div>
</div>
</div>
diff --git
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/list.vue
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/list.vue
index 0af0b5a..b7d99bf 100644
---
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/list.vue
+++
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/list.vue
@@ -17,8 +17,8 @@
<template>
<div class="list-model" style="position: relative;">
<div class="table-box">
- <el-table :data="list" size="mini" style="width: 100%"
@selection-change="_arrDelChange">
- <el-table-column type="selection" width="50"
:selectable="selectable"></el-table-column>
+ <el-table :data="list" size="mini" style="width: 100%"
@selection-change="_arrDelChange" row-class-name="rows-workflow-definitions">
+ <el-table-column type="selection" width="50" :selectable="selectable"
class-name="select-all"></el-table-column>
<el-table-column prop="id" :label="$t('#')"
width="50"></el-table-column>
<el-table-column :label="$t('Process Name')" min-width="200">
<template slot-scope="scope">
@@ -26,7 +26,7 @@
<p>{{ scope.row.name }}</p>
<div slot="reference" class="name-wrapper">
<router-link :to="{ path:
`/projects/${projectCode}/definition/list/${scope.row.code}` }" tag="a"
class="links">
- <span class="ellipsis">{{scope.row.name}}</span>
+ <span class="ellipsis name">{{scope.row.name}}</span>
</router-link>
</div>
</el-popover>
@@ -66,16 +66,16 @@
<span><el-button type="primary" size="mini"
icon="el-icon-edit-outline" :disabled="scope.row.releaseState === 'ONLINE'"
@click="_edit(scope.row)" circle></el-button></span>
</el-tooltip>
<el-tooltip :content="$t('Start')" placement="top"
:enterable="false">
- <span><el-button type="success" size="mini"
:disabled="scope.row.releaseState !== 'ONLINE'" icon="el-icon-video-play"
@click="_start(scope.row)" circle></el-button></span>
+ <span><el-button type="success" size="mini"
:disabled="scope.row.releaseState !== 'ONLINE'" icon="el-icon-video-play"
@click="_start(scope.row)" circle class="button-run"></el-button></span>
</el-tooltip>
<el-tooltip :content="$t('Timing')" placement="top"
:enterable="false">
<span><el-button type="primary" size="mini" icon="el-icon-time"
:disabled="scope.row.releaseState !== 'ONLINE' ||
scope.row.scheduleReleaseState !== null" @click="_timing(scope.row)"
circle></el-button></span>
</el-tooltip>
<el-tooltip :content="$t('online')" placement="top"
:enterable="false">
- <span><el-button type="warning" size="mini"
v-if="scope.row.releaseState === 'OFFLINE'" icon="el-icon-upload2"
@click="_poponline(scope.row)" circle></el-button></span>
+ <span><el-button type="warning" size="mini"
v-if="scope.row.releaseState === 'OFFLINE'" icon="el-icon-upload2"
@click="_poponline(scope.row)" circle class="button-publish"></el-button></span>
</el-tooltip>
<el-tooltip :content="$t('offline')" placement="top"
:enterable="false">
- <span><el-button type="danger" size="mini"
icon="el-icon-download" v-if="scope.row.releaseState === 'ONLINE'"
@click="_downline(scope.row)" circle></el-button></span>
+ <span><el-button type="danger" size="mini"
icon="el-icon-download" v-if="scope.row.releaseState === 'ONLINE'"
@click="_downline(scope.row)" circle
class="button-cancel-publish"></el-button></span>
</el-tooltip>
<el-tooltip :content="$t('Copy Workflow')" placement="top"
:enterable="false">
<span><el-button type="primary" size="mini"
:disabled="scope.row.releaseState === 'ONLINE'" icon="el-icon-document-copy"
@click="_copyProcess(scope.row)" circle></el-button></span>
@@ -115,7 +115,7 @@
:title="$t('Delete?')"
@onConfirm="_delete({},-1)"
>
- <el-button style="position: absolute; bottom: -48px; left: 19px;"
type="primary" size="mini" :disabled="!strSelectCodes"
slot="reference">{{$t('Delete')}}</el-button>
+ <el-button style="position: absolute; bottom: -48px; left: 19px;"
type="primary" size="mini" :disabled="!strSelectCodes" slot="reference"
class="button-delete-all">{{$t('Delete')}}</el-button>
</el-popconfirm>
</el-tooltip>
<el-button type="primary" size="mini" :disabled="!strSelectCodes"
style="position: absolute; bottom: -48px; left: 80px;"
@click="_batchExport(item)" >{{$t('Export')}}</el-button>
diff --git
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/start.vue
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/start.vue
index 740bbcd..8c99f8b 100644
---
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/start.vue
+++
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/start.vue
@@ -191,7 +191,7 @@
</div>
<div class="submit">
<el-button type="text" size="small" @click="close()"> {{$t('Cancel')}}
</el-button>
- <el-button type="primary" size="small" round :loading="spinnerLoading"
@click="ok()">{{spinnerLoading ? $t('Loading...') : $t('Start')}} </el-button>
+ <el-button type="primary" size="small" round :loading="spinnerLoading"
@click="ok()" id="button-submit">{{spinnerLoading ? $t('Loading...') :
$t('Start')}} </el-button>
</div>
</div>
</template>
diff --git
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/index.vue
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/index.vue
index e17f22b..9fca167 100644
---
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/index.vue
+++
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/index.vue
@@ -20,7 +20,7 @@
<template slot="conditions">
<m-conditions @on-conditions="_onConditions">
<template slot="button-group">
- <el-button size="mini" @click="() => this.$router.push({name:
'definition-create'})">{{$t('Create process')}}</el-button>
+ <el-button size="mini" @click="() => this.$router.push({name:
'definition-create'})" id="button-create-process">{{$t('Create
process')}}</el-button>
<el-button size="mini" @click="_uploading">{{$t('Import
process')}}</el-button>
</template>
</m-conditions>
diff --git
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/instance/pages/list/_source/list.vue
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/instance/pages/list/_source/list.vue
index a4f4f9b..ba572b8 100644
---
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/instance/pages/list/_source/list.vue
+++
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/instance/pages/list/_source/list.vue
@@ -17,8 +17,8 @@
<template>
<div class="list-model" style="position: relative;">
<div class="table-box">
- <el-table class="fixed" :data="list" size="mini" style="width: 100%"
@selection-change="_arrDelChange">
- <el-table-column type="selection" width="50"></el-table-column>
+ <el-table class="fixed" :data="list" size="mini" style="width: 100%"
@selection-change="_arrDelChange" row-class-name="rows-workflow-instances">
+ <el-table-column type="selection" width="50"
class-name="select-all"></el-table-column>
<el-table-column prop="id" :label="$t('#')"
width="50"></el-table-column>
<el-table-column :label="$t('Process Name')" min-width="200">
<template slot-scope="scope">
@@ -61,7 +61,7 @@
<span>{{scope.row.duration | filterNull}}</span>
</template>
</el-table-column>
- <el-table-column prop="runTimes" :label="$t('Run
Times')"></el-table-column>
+ <el-table-column prop="runTimes" :label="$t('Run Times')"
class-name="execution-time"></el-table-column>
<el-table-column prop="recovery" :label="$t('fault-tolerant
sign')"></el-table-column>
<el-table-column :label="$t('Dry-run flag')" width="100">
<template slot-scope="scope">
@@ -80,7 +80,7 @@
</span>
</el-tooltip>
<el-tooltip :content="$t('Rerun')" placement="top"
:enterable="false">
- <span><el-button type="primary" size="mini"
:disabled="scope.row.state !== 'SUCCESS' && scope.row.state !== 'PAUSE' &&
scope.row.state !== 'FAILURE' && scope.row.state !== 'STOP'"
icon="el-icon-refresh" @click="_reRun(scope.row,scope.$index)"
circle></el-button></span>
+ <span><el-button type="primary" size="mini"
:disabled="scope.row.state !== 'SUCCESS' && scope.row.state !== 'PAUSE' &&
scope.row.state !== 'FAILURE' && scope.row.state !== 'STOP'"
icon="el-icon-refresh" @click="_reRun(scope.row,scope.$index)" circle
class="button-rerun"></el-button></span>
</el-tooltip>
<el-tooltip :content="$t('Recovery Failed')" placement="top"
:enterable="false">
<span>
@@ -233,7 +233,7 @@
:title="$t('Delete?')"
@onConfirm="_delete({},-1)"
>
- <el-button style="position: absolute; bottom: -48px; left: 19px;"
type="primary" size="mini" :disabled="!strDelete"
slot="reference">{{$t('Delete')}}</el-button>
+ <el-button style="position: absolute; bottom: -48px; left: 19px;"
type="primary" size="mini" :disabled="!strDelete" slot="reference"
class="button-delete-all">{{$t('Delete')}}</el-button>
</el-popconfirm>
</el-tooltip>
</div>
@@ -273,7 +273,7 @@
*/
_rtState (code) {
let o = tasksState[code]
- return `<em class="fa ansfont ${o.icoUnicode} ${o.isSpin ? 'as
as-spin' : ''}" style="color:${o.color}" data-toggle="tooltip"
data-container="body" title="${o.desc}"></em>`
+ return `<em class="fa ansfont ${o.classNames} ${o.icoUnicode}
${o.isSpin ? 'as as-spin' : ''}" style="color:${o.color}" data-toggle="tooltip"
data-container="body" title="${o.desc}"></em>`
},
/**
* delete
diff --git
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/list/_source/createProject.vue
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/list/_source/createProject.vue
index a49be0e..443c219 100644
---
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/list/_source/createProject.vue
+++
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/list/_source/createProject.vue
@@ -16,13 +16,14 @@
*/
<template>
<m-popover ref="popover" :nameText="item ? $t('Edit') : $t('Create
Project')" :ok-text="item ? $t('Edit') : $t('Submit')"
- @close="_close" @ok="_ok">
+ @close="_close" @ok="_ok" ok-id="button-submit">
<template slot="content">
<div class="projects-create-model">
<m-list-box-f>
<template slot="name"><strong>*</strong>{{ $t('Project Name')
}}</template>
<template slot="content">
<el-input
+ id="input-project-name"
v-model="projectName"
:placeholder="$t('Please enter name')"
maxlength="60"
diff --git
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/list/_source/list.vue
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/list/_source/list.vue
index 05fedbb..3b93932 100644
---
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/list/_source/list.vue
+++
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/list/_source/list.vue
@@ -17,14 +17,14 @@
<template>
<div class="list-model">
<div class="table-box">
- <el-table :data="list" size="mini" style="width: 100%">
+ <el-table :data="list" size="mini" style="width: 100%"
row-class-name="rows-project">
<el-table-column type="index" :label="$t('#')"
width="50"></el-table-column>
<el-table-column :label="$t('Project Name')">
<template slot-scope="scope">
<el-popover trigger="hover" placement="top">
<p>{{ scope.row.name }}</p>
<div slot="reference" class="name-wrapper">
- <a href="javascript:" class="links"
@click="_switchProjects(scope.row)">{{ scope.row.name }}</a>
+ <a href="javascript:" class="links project-name"
@click="_switchProjects(scope.row)">{{ scope.row.name }}</a>
</div>
</el-popover>
</template>
@@ -61,7 +61,7 @@
:title="$t('Delete?')"
@onConfirm="_delete(scope.row,scope.row.id)"
>
- <el-button type="danger" size="mini" icon="el-icon-delete"
circle slot="reference"></el-button>
+ <el-button type="danger" size="mini" icon="el-icon-delete"
circle slot="reference" class="delete"></el-button>
</el-popconfirm>
</el-tooltip>
</template>
diff --git
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/list/index.vue
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/list/index.vue
index 57a9e7d..7457f3c 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/list/index.vue
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/list/index.vue
@@ -19,7 +19,7 @@
<template slot="conditions">
<m-conditions @on-conditions="_onConditions">
<template slot="button-group">
- <el-button size="mini" @click="_create('')">{{ $t('Create Project')
}}</el-button>
+ <el-button size="mini" @click="_create('')"
id="button-create-project">{{ $t('Create Project') }}</el-button>
<el-dialog
:title="item ? $t('Edit') : $t('Create Project')"
v-if="createProjectDialog"
diff --git
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/taskDefinition/index.vue
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/taskDefinition/index.vue
index 67aa2b3..40d83f3 100644
---
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/taskDefinition/index.vue
+++
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/taskDefinition/index.vue
@@ -1,307 +1,307 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-<template>
- <div class="task-definition" v-if="!isLoading">
- <m-list-construction :title="$t('Task Definition')">
- <template slot="conditions">
- <m-conditions @on-conditions="_onConditions" :taskTypeShow="true">
- <template v-slot:button-group>
- <el-button size="mini" @click="createTask">
- {{ $t("Create task") }}
- </el-button>
- </template>
- </m-conditions>
- </template>
- <template v-slot:content>
- <template v-if="tasksList.length || total > 0">
- <m-list
- :tasksList="tasksList"
- @on-update="_onUpdate"
- @editTask="editTask"
- @viewTaskDetail="viewTaskDetail"
- ></m-list>
- <div class="page-box">
- <el-pagination
- background
- @current-change="_page"
- @size-change="_pageSize"
- :page-size="searchParams.pageSize"
- :current-page.sync="searchParams.pageNo"
- :page-sizes="[10, 30, 50]"
- :total="total"
- layout="sizes, prev, pager, next, jumper"
- >
- </el-pagination>
- </div>
- </template>
- <template v-if="!tasksList.length">
- <m-no-data></m-no-data>
- </template>
- <m-spin :is-spin="isLoading"></m-spin>
- </template>
- </m-list-construction>
- <el-drawer
- :visible.sync="taskDrawer"
- size=""
- :with-header="false"
- @close="closeTaskDrawer"
- >
- <!-- fix the bug that Element-ui(2.13.2) auto focus on the first input
-->
- <div style="width: 0px; height: 0px; overflow: hidden">
- <el-input type="text" />
- </div>
- <m-form-model
- v-if="taskDrawer"
- :nodeData="nodeData"
- type="task-definition"
- @changeTaskType="changeTaskType"
- @close="closeTaskDrawer"
- @addTaskInfo="saveTask"
- :taskDefinition="editingTask"
- >
- </m-form-model>
- </el-drawer>
- </div>
-</template>
-<script>
- import mListConstruction from
'@/module/components/listConstruction/listConstruction'
- import mConditions from '@/module/components/conditions/conditions'
- import mList from './_source/list'
- import mNoData from '@/module/components/noData/noData'
- import mSpin from '@/module/components/spin/spin'
- import { mapActions, mapMutations } from 'vuex'
- import listUrlParamHandle from '@/module/mixin/listUrlParamHandle'
- import mFormModel from
'@/conf/home/pages/dag/_source/formModel/formModel.vue'
- /**
- * tasksType
- */
- import { tasksType } from '@/conf/home/pages/dag/_source/config.js'
-
- const DEFAULT_NODE_DATA = {
- id: -1,
- taskType: 'SHELL',
- instanceId: -1
- }
- export default {
- name: 'task-definition-index',
- data () {
- // tasksType
- const tasksTypeList = Object.keys(tasksType)
- return {
- total: null,
- tasksList: [],
- isLoading: true,
- searchParams: {
- pageSize: 10,
- pageNo: 1,
- searchVal: '',
- taskType: '',
- userId: ''
- },
- // whether the task config drawer is visible
- taskDrawer: false,
- // nodeData
- nodeData: { ...DEFAULT_NODE_DATA },
- // tasksType
- tasksTypeList,
- // editing task definition
- editingTask: null
- }
- },
- mixins: [listUrlParamHandle],
- methods: {
- ...mapActions('dag', [
- 'getTaskDefinitionsList',
- 'genTaskCodeList',
- 'saveTaskDefinition',
- 'updateTaskDefinition'
- ]),
- ...mapActions('dag', [
- 'getProcessList',
- 'getProjectList',
- 'getResourcesList',
- 'getResourcesListJar',
- 'getResourcesListJar'
- ]),
- ...mapMutations('dag', ['resetParams', 'setIsDetails']),
- ...mapActions('security', [
- 'getTenantList',
- 'getWorkerGroupsAll',
- 'getAlarmGroupsAll'
- ]),
- /**
- * Toggle task drawer
- */
- showTaskDrawer () {
- this.taskDrawer = true
- },
- closeTaskDrawer () {
- this.setIsDetails(false)
- this.taskDrawer = false
- },
- saveTask ({ item }) {
- const isEditing = !!this.editingTask
- if (isEditing) {
- this.updateTaskDefinition(item)
- .then((res) => {
- this.$message.success(res.msg)
- this._onUpdate()
- this.closeTaskDrawer()
- })
- .catch((e) => {
- this.$message.error(e.msg || '')
- })
- } else {
- this.genTaskCodeList({
- genNum: 1
- })
- .then((res) => {
- const [code] = res
- return code
- })
- .then((code) => {
- return this.saveTaskDefinition({
- taskDefinitionJson: [
- {
- ...item,
- code
- }
- ]
- })
- })
- .then((res) => {
- this.$message.success(res.msg)
- this._onUpdate()
- this.closeTaskDrawer()
- })
- .catch((e) => {
- this.$message.error(e.msg || '')
- })
- }
- },
- createTask () {
- this.editingTask = null
- this.nodeData.taskType = DEFAULT_NODE_DATA.taskType
- this.showTaskDrawer()
- },
- editTask (task) {
- this.editingTask = task
- this.nodeData.id = task.code
- this.nodeData.taskType = task.taskType
- this.showTaskDrawer()
- },
- viewTaskDetail (task) {
- this.setIsDetails(true)
- this.editTask(task)
- },
- /**
- * pageNo
- */
- _page (val) {
- this.searchParams.pageNo = val
- },
- _pageSize (val) {
- this.searchParams.pageSize = val
- },
- /**
- * conditions
- */
- _onConditions (o) {
- this.searchParams.searchVal = o.searchVal
- this.searchParams.taskType = o.taskType
- this.searchParams.pageNo = 1
- },
- /**
- * get task definition list
- */
- _getList (flag) {
- this.isLoading = !flag
- this.getTaskDefinitionsList(this.searchParams)
- .then((res) => {
- if (this.searchParams.pageNo > 1 && res.totalList.length === 0) {
- this.searchParams.pageNo = this.searchParams.pageNo - 1
- } else {
- this.tasksList = []
- this.tasksList = res.totalList
- this.total = res.total
- this.isLoading = false
- }
- })
- .catch((e) => {
- this.isLoading = false
- })
- },
- /**
- * update task dataList
- */
- _onUpdate () {
- this._debounceGET('false')
- },
- /**
- * change form modal task type
- */
- changeTaskType (value) {
- this.nodeData.taskType = value
- }
- },
- created () {
- this.isLoading = true
- // Initialization parameters
- this.resetParams()
- // Promise Get node needs data
- Promise.all([
- // get process definition
- this.getProcessList(),
- // get project
- this.getProjectList(),
- // get jar
- this.getResourcesListJar(),
- // get resource
- this.getResourcesList(),
- // get jar
- this.getResourcesListJar(),
- // get worker group list
- this.getWorkerGroupsAll(),
- // get alarm group list
- this.getAlarmGroupsAll(),
- this.getTenantList()
- ])
- .then((data) => {
- this.isLoading = false
- })
- .catch(() => {
- this.isLoading = false
- })
- },
- mounted () {},
- components: {
- mListConstruction,
- mConditions,
- mList,
- mNoData,
- mSpin,
- mFormModel
- }
- }
-</script>
-<style lang="scss" scoped>
-.task-definition {
- .taskGroupBtn {
- width: 300px;
- }
-}
-</style>
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+<template>
+ <div class="task-definition" v-if="!isLoading">
+ <m-list-construction :title="$t('Task Definition')">
+ <template slot="conditions">
+ <m-conditions @on-conditions="_onConditions" :taskTypeShow="true">
+ <template v-slot:button-group>
+ <el-button size="mini" @click="createTask">
+ {{ $t("Create task") }}
+ </el-button>
+ </template>
+ </m-conditions>
+ </template>
+ <template v-slot:content>
+ <template v-if="tasksList.length || total > 0">
+ <m-list
+ :tasksList="tasksList"
+ @on-update="_onUpdate"
+ @editTask="editTask"
+ @viewTaskDetail="viewTaskDetail"
+ ></m-list>
+ <div class="page-box">
+ <el-pagination
+ background
+ @current-change="_page"
+ @size-change="_pageSize"
+ :page-size="searchParams.pageSize"
+ :current-page.sync="searchParams.pageNo"
+ :page-sizes="[10, 30, 50]"
+ :total="total"
+ layout="sizes, prev, pager, next, jumper"
+ >
+ </el-pagination>
+ </div>
+ </template>
+ <template v-if="!tasksList.length">
+ <m-no-data></m-no-data>
+ </template>
+ <m-spin :is-spin="isLoading"></m-spin>
+ </template>
+ </m-list-construction>
+ <el-drawer
+ :visible.sync="taskDrawer"
+ size=""
+ :with-header="false"
+ @close="closeTaskDrawer"
+ >
+ <!-- fix the bug that Element-ui(2.13.2) auto focus on the first input
-->
+ <div style="width: 0px; height: 0px; overflow: hidden">
+ <el-input type="text" />
+ </div>
+ <m-form-model
+ v-if="taskDrawer"
+ :nodeData="nodeData"
+ type="task-definition"
+ @changeTaskType="changeTaskType"
+ @close="closeTaskDrawer"
+ @addTaskInfo="saveTask"
+ :taskDefinition="editingTask"
+ >
+ </m-form-model>
+ </el-drawer>
+ </div>
+</template>
+<script>
+ import mListConstruction from
'@/module/components/listConstruction/listConstruction'
+ import mConditions from '@/module/components/conditions/conditions'
+ import mList from './_source/list'
+ import mNoData from '@/module/components/noData/noData'
+ import mSpin from '@/module/components/spin/spin'
+ import { mapActions, mapMutations } from 'vuex'
+ import listUrlParamHandle from '@/module/mixin/listUrlParamHandle'
+ import mFormModel from
'@/conf/home/pages/dag/_source/formModel/formModel.vue'
+ /**
+ * tasksType
+ */
+ import { tasksType } from '@/conf/home/pages/dag/_source/config.js'
+
+ const DEFAULT_NODE_DATA = {
+ id: -1,
+ taskType: 'SHELL',
+ instanceId: -1
+ }
+ export default {
+ name: 'task-definition-index',
+ data () {
+ // tasksType
+ const tasksTypeList = Object.keys(tasksType)
+ return {
+ total: null,
+ tasksList: [],
+ isLoading: true,
+ searchParams: {
+ pageSize: 10,
+ pageNo: 1,
+ searchVal: '',
+ taskType: '',
+ userId: ''
+ },
+ // whether the task config drawer is visible
+ taskDrawer: false,
+ // nodeData
+ nodeData: { ...DEFAULT_NODE_DATA },
+ // tasksType
+ tasksTypeList,
+ // editing task definition
+ editingTask: null
+ }
+ },
+ mixins: [listUrlParamHandle],
+ methods: {
+ ...mapActions('dag', [
+ 'getTaskDefinitionsList',
+ 'genTaskCodeList',
+ 'saveTaskDefinition',
+ 'updateTaskDefinition'
+ ]),
+ ...mapActions('dag', [
+ 'getProcessList',
+ 'getProjectList',
+ 'getResourcesList',
+ 'getResourcesListJar',
+ 'getResourcesListJar'
+ ]),
+ ...mapMutations('dag', ['resetParams', 'setIsDetails']),
+ ...mapActions('security', [
+ 'getTenantList',
+ 'getWorkerGroupsAll',
+ 'getAlarmGroupsAll'
+ ]),
+ /**
+ * Toggle task drawer
+ */
+ showTaskDrawer () {
+ this.taskDrawer = true
+ },
+ closeTaskDrawer () {
+ this.setIsDetails(false)
+ this.taskDrawer = false
+ },
+ saveTask ({ item }) {
+ const isEditing = !!this.editingTask
+ if (isEditing) {
+ this.updateTaskDefinition(item)
+ .then((res) => {
+ this.$message.success(res.msg)
+ this._onUpdate()
+ this.closeTaskDrawer()
+ })
+ .catch((e) => {
+ this.$message.error(e.msg || '')
+ })
+ } else {
+ this.genTaskCodeList({
+ genNum: 1
+ })
+ .then((res) => {
+ const [code] = res
+ return code
+ })
+ .then((code) => {
+ return this.saveTaskDefinition({
+ taskDefinitionJson: [
+ {
+ ...item,
+ code
+ }
+ ]
+ })
+ })
+ .then((res) => {
+ this.$message.success(res.msg)
+ this._onUpdate()
+ this.closeTaskDrawer()
+ })
+ .catch((e) => {
+ this.$message.error(e.msg || '')
+ })
+ }
+ },
+ createTask () {
+ this.editingTask = null
+ this.nodeData.taskType = DEFAULT_NODE_DATA.taskType
+ this.showTaskDrawer()
+ },
+ editTask (task) {
+ this.editingTask = task
+ this.nodeData.id = task.code
+ this.nodeData.taskType = task.taskType
+ this.showTaskDrawer()
+ },
+ viewTaskDetail (task) {
+ this.setIsDetails(true)
+ this.editTask(task)
+ },
+ /**
+ * pageNo
+ */
+ _page (val) {
+ this.searchParams.pageNo = val
+ },
+ _pageSize (val) {
+ this.searchParams.pageSize = val
+ },
+ /**
+ * conditions
+ */
+ _onConditions (o) {
+ this.searchParams.searchVal = o.searchVal
+ this.searchParams.taskType = o.taskType
+ this.searchParams.pageNo = 1
+ },
+ /**
+ * get task definition list
+ */
+ _getList (flag) {
+ this.isLoading = !flag
+ this.getTaskDefinitionsList(this.searchParams)
+ .then((res) => {
+ if (this.searchParams.pageNo > 1 && res.totalList.length === 0) {
+ this.searchParams.pageNo = this.searchParams.pageNo - 1
+ } else {
+ this.tasksList = []
+ this.tasksList = res.totalList
+ this.total = res.total
+ this.isLoading = false
+ }
+ })
+ .catch((e) => {
+ this.isLoading = false
+ })
+ },
+ /**
+ * update task dataList
+ */
+ _onUpdate () {
+ this._debounceGET('false')
+ },
+ /**
+ * change form modal task type
+ */
+ changeTaskType (value) {
+ this.nodeData.taskType = value
+ }
+ },
+ created () {
+ this.isLoading = true
+ // Initialization parameters
+ this.resetParams()
+ // Promise Get node needs data
+ Promise.all([
+ // get process definition
+ this.getProcessList(),
+ // get project
+ this.getProjectList(),
+ // get jar
+ this.getResourcesListJar(),
+ // get resource
+ this.getResourcesList(),
+ // get jar
+ this.getResourcesListJar(),
+ // get worker group list
+ this.getWorkerGroupsAll(),
+ // get alarm group list
+ this.getAlarmGroupsAll(),
+ this.getTenantList()
+ ])
+ .then((data) => {
+ this.isLoading = false
+ })
+ .catch(() => {
+ this.isLoading = false
+ })
+ },
+ mounted () {},
+ components: {
+ mListConstruction,
+ mConditions,
+ mList,
+ mNoData,
+ mSpin,
+ mFormModel
+ }
+ }
+</script>
+<style lang="scss" scoped>
+.task-definition {
+ .taskGroupBtn {
+ width: 300px;
+ }
+}
+</style>
diff --git a/dolphinscheduler-ui/src/js/module/components/nav/nav.vue
b/dolphinscheduler-ui/src/js/module/components/nav/nav.vue
index dbbafec..0c6fb23 100644
--- a/dolphinscheduler-ui/src/js/module/components/nav/nav.vue
+++ b/dolphinscheduler-ui/src/js/module/components/nav/nav.vue
@@ -29,7 +29,7 @@
</div>
<div class="clearfix list">
<div class="nav-links">
- <router-link :to="{ path: '/projects'}" tag="a"
active-class="active">
+ <router-link :to="{ path: '/projects'}" tag="a"
active-class="active" id="project-tab">
<span><em class="ansiconfont el-icon-tickets"></em>{{$t('Project
Manage')}}</span><strong></strong>
</router-link>
</div>
@@ -57,7 +57,7 @@
</div>
<div class="clearfix list" >
<div class="nav-links">
- <router-link :to="{ path: '/security'}" tag="a"
active-class="active" v-ps="['ADMIN_USER']">
+ <router-link :to="{ path: '/security'}" tag="a"
active-class="active" v-ps="['ADMIN_USER']" id="security-tab">
<span><em class="ansfont
ri-shield-check-line"></em>{{$t('Security')}}</span><strong></strong>
</router-link>
</div>
diff --git
a/dolphinscheduler-ui/src/js/module/components/secondaryMenu/_source/menu.js
b/dolphinscheduler-ui/src/js/module/components/secondaryMenu/_source/menu.js
index 1a0a432..9c02958 100644
--- a/dolphinscheduler-ui/src/js/module/components/secondaryMenu/_source/menu.js
+++ b/dolphinscheduler-ui/src/js/module/components/secondaryMenu/_source/menu.js
@@ -51,13 +51,15 @@ const menu = {
name: `${i18n.$t('Process definition')}`,
path: 'definition',
id: 0,
- enabled: true
+ enabled: true,
+ classNames: 'process-definition'
},
{
name: `${i18n.$t('Process Instance')}`,
path: 'instance',
id: 1,
- enabled: true
+ enabled: true,
+ classNames: 'process-instance'
},
{
name: `${i18n.$t('Task Instance')}`,
@@ -95,7 +97,8 @@ const menu = {
isOpen: true,
enabled: true,
icon: 'el-icon-user-solid',
- children: []
+ children: [],
+ classNames: 'tenant-manage'
},
{
name: `${i18n.$t('User Manage')}`,
diff --git
a/dolphinscheduler-ui/src/js/module/components/secondaryMenu/secondaryMenu.vue
b/dolphinscheduler-ui/src/js/module/components/secondaryMenu/secondaryMenu.vue
index eb135d0..0d0845b 100644
---
a/dolphinscheduler-ui/src/js/module/components/secondaryMenu/secondaryMenu.vue
+++
b/dolphinscheduler-ui/src/js/module/components/secondaryMenu/secondaryMenu.vue
@@ -23,7 +23,7 @@
<div class="leven-1" v-for="(item,$index) in menuList" :key="$index">
<div v-if="item.enabled">
<template v-if="item.path">
- <router-link :to="{ name: item.path}">
+ <router-link :to="{ name: item.path}" :class="item.classNames">
<div class="name" @click="_toggleSubMenu(item)">
<a href="javascript:">
<em class="fa icon" :class="item.icon"></em>
@@ -44,7 +44,7 @@
</template>
<ul v-if="item.isOpen && item.children.length">
<template v-for="(el,index) in item.children">
- <router-link :to="{ name: el.path}" tag="li" active-class="active"
v-if="el.enabled" :key="index">
+ <router-link :to="{ name: el.path}" tag="li" active-class="active"
v-if="el.enabled" :key="index" :class="el.classNames">
<span>{{el.name}}</span>
</router-link>
</template>