You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@submarine.apache.org by pi...@apache.org on 2020/07/28 12:12:29 UTC
[submarine] branch master updated: SUBMARINE-566. [WEB] Create a
new experiment through UI
This is an automated email from the ASF dual-hosted git repository.
pingsutw pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/submarine.git
The following commit(s) were added to refs/heads/master by this push:
new 5e8f0e9 SUBMARINE-566. [WEB] Create a new experiment through UI
5e8f0e9 is described below
commit 5e8f0e90823b927ce0785e545bb83da0323d18ad
Author: wang0630 <j2...@gmail.com>
AuthorDate: Thu Jul 23 15:59:40 2020 +0800
SUBMARINE-566. [WEB] Create a new experiment through UI
### What is this PR for?
New experiment creation through UI.
### What type of PR is it
[Feature]
### Todos
* More user-feedback should be added later
### What is the Jira issue?
[SUBMARINE-566](https://issues.apache.org/jira/browse/SUBMARINE-566?filter=-1)
### How should this be tested?
https://travis-ci.com/github/wang0630/submarine/jobs/364539196
### Screenshots (if appropriate)
![first](https://user-images.githubusercontent.com/26138982/88267743-d9268e00-cd03-11ea-95d1-a07856c5c0eb.gif)
### Questions:
* Does the licenses files need update? No
* Is there breaking changes for older versions? No
* Does this needs documentation? No
Author: wang0630 <j2...@gmail.com>
Closes #354 from wang0630/SUBMARINE-566 and squashes the following commits:
f25a54b [wang0630] SUBMARINE-566. [WEB] Create a new experiment through UI
f94fa5b [wang0630] Before refactoring the e2e testing
dff392a [wang0630] Error handling finished
c64448b [wang0630] Fire the request succeed
---
.../server/submitter/k8s/K8sSubmitter.java | 2 +-
.../apache/submarine/integration/experimentIT.java | 71 ++++-----
.../integration/pages/ExperimentPage.java | 172 +++++++++++++++++++++
.../src/app/interfaces/experiment-spec.ts | 29 +++-
.../workbench/experiment/experiment.component.html | 2 +-
.../workbench/experiment/experiment.component.scss | 4 -
.../workbench/experiment/experiment.component.ts | 91 +++++++++--
.../src/app/services/base-api.service.ts | 3 +-
.../src/app/services/experiment.service.ts | 23 ++-
.../app/services/experiment.validator.service.ts | 4 +-
10 files changed, 332 insertions(+), 69 deletions(-)
diff --git a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/K8sSubmitter.java b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/K8sSubmitter.java
index 91db0b9..9b495bf 100644
--- a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/K8sSubmitter.java
+++ b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/K8sSubmitter.java
@@ -104,7 +104,7 @@ public class K8sSubmitter implements Submitter {
experiment = parseResponseObject(object, ParseOp.PARSE_OP_RESULT);
} catch (InvalidSpecException e) {
LOG.error("K8s submitter: parse Job object failed by " + e.getMessage(), e);
- throw new SubmarineRuntimeException(200, e.getMessage());
+ throw new SubmarineRuntimeException(400, e.getMessage());
} catch (ApiException e) {
LOG.error("K8s submitter: parse Job object failed by " + e.getMessage(), e);
throw new SubmarineRuntimeException(e.getCode(), e.getMessage());
diff --git a/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/experimentIT.java b/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/experimentIT.java
index a525749..7dce204 100644
--- a/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/experimentIT.java
+++ b/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/experimentIT.java
@@ -17,25 +17,16 @@
package org.apache.submarine.integration;
-import org.apache.commons.io.FileUtils;
import org.apache.submarine.AbstractSubmarineIT;
import org.apache.submarine.WebDriverManager;
+import org.apache.submarine.integration.pages.ExperimentPage;
import org.openqa.selenium.By;
-import org.openqa.selenium.OutputType;
-import org.openqa.selenium.TakesScreenshot;
-import org.openqa.selenium.WebElement;
-import org.openqa.selenium.interactions.Actions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
-import org.testng.Assert;
-import org.openqa.selenium.support.ui.WebDriverWait;
-import org.openqa.selenium.support.ui.ExpectedConditions;
-import sun.rmi.runtime.Log;
-
-import java.io.File;
public class experimentIT extends AbstractSubmarineIT {
@@ -43,8 +34,8 @@ public class experimentIT extends AbstractSubmarineIT {
@BeforeClass
public static void startUp(){
- LOG.info("[Testcase]: experimentIT");
- driver = WebDriverManager.getWebDriver();
+ LOG.info("[Testcase]: experimentNew");
+ driver = WebDriverManager.getWebDriver();
}
@AfterClass
@@ -54,6 +45,8 @@ public class experimentIT extends AbstractSubmarineIT {
@Test
public void experimentNavigation() throws Exception {
+ // Init the page object
+ ExperimentPage experimentPage = new ExperimentPage(driver);
// Login
LOG.info("Login");
pollingWait(By.cssSelector("input[ng-reflect-name='userName']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("admin");
@@ -68,35 +61,29 @@ public class experimentIT extends AbstractSubmarineIT {
// Test create new experiment
LOG.info("new experiment");
- pollingWait(By.xpath("//button[@id='openExperiment']"), MAX_BROWSER_TIMEOUT_SEC).click();
- Assert.assertTrue(pollingWait(By.xpath("//form"), MAX_BROWSER_TIMEOUT_SEC).isDisplayed());
- WebDriverWait wait = new WebDriverWait( driver, 15);
- // Basic information section
- pollingWait(By.name("experimentName"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("e2e test Experiment");
- pollingWait(By.name("description"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("e2e test Project description");
- pollingWait(By.name("namespace"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("e2e namespace");
- pollingWait(By.name("cmd"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("python3 -m e2e cmd");
- pollingWait(By.name("image"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("e2e custom image");
- pollingWait(By.xpath("//button[@id='go']"), MAX_BROWSER_TIMEOUT_SEC).click();
- // env variables section
- LOG.info("in env");
- Assert.assertTrue(pollingWait(By.xpath("//button[@id='env-btn']"), MAX_BROWSER_TIMEOUT_SEC).isDisplayed());
- WebElement envBtn = buttonCheck(By.id("env-btn"), MAX_BROWSER_TIMEOUT_SEC);
- envBtn.click();
- wait.until(ExpectedConditions.visibilityOfAllElementsLocatedBy(By.xpath("//input[@name='key0' or name='value0']")));
- pollingWait(By.name("key0"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("e2e key");
- pollingWait(By.name("value0"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("e2e value");
+ experimentPage.newExperimentButtonClick();
+ experimentPage.fillMeta("good-e2e-test", "e2e des", "default", "python /var/tf_mnist/mnist_with_summaries.py --log_dir=/train/log --learning_rate=0.01 --batch_size=150", "gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0");
+ Assert.assertTrue(experimentPage.getGoButton().isEnabled());
+ experimentPage.goButtonClick();
+
+ LOG.info("In env");
+ experimentPage.envBtnClick();
+ experimentPage.fillEnv("ENV_1", "ENV1");
+ Assert.assertTrue(experimentPage.getGoButton().isEnabled());
+ experimentPage.goButtonClick();
- pollingWait(By.xpath("//button[@id='go']"), MAX_BROWSER_TIMEOUT_SEC).click();
- // Spec section
- LOG.info("in spec");
- WebElement specBtn = wait.until(ExpectedConditions.elementToBeClickable(By.id("spec-btn")));
- specBtn.click();
- pollingWait(By.name("spec0"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("e2e spec");
- pollingWait(By.name("replica0"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("1");
- pollingWait(By.name("cpu0"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("1");
- pollingWait(By.name("memory0"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("512M");
- Assert.assertTrue(pollingWait(By.xpath("//button[@id='go']"), MAX_BROWSER_TIMEOUT_SEC).isEnabled());
-// pollingWait(By.xpath("//button[@id='go']"), MAX_BROWSER_TIMEOUT_SEC).click();
+ // Fail due to incorrect spec name
+ LOG.info("In spec fail");
+ experimentPage.fillTfSpec(1, new String[]{"wrong name"}, new int[]{1}, new int[]{1}, new String[]{"512M"});
+ Assert.assertTrue(experimentPage.getGoButton().isEnabled());
+ experimentPage.goButtonClick();
+ Assert.assertTrue(experimentPage.getErrorNotification().isDisplayed());
+ // Successful request
+ LOG.info("In spec success");
+ experimentPage.deleteSpec();
+ Assert.assertEquals(experimentPage.getSpecs(), 0);
+ experimentPage.fillTfSpec(2, new String[]{"Ps", "Worker"}, new int[]{1, 1}, new int[]{1, 1}, new String[]{"1024M", "1024M"});
+ Assert.assertTrue(experimentPage.getGoButton().isEnabled());
+ experimentPage.goButtonClick();
}
}
diff --git a/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/pages/ExperimentPage.java b/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/pages/ExperimentPage.java
new file mode 100644
index 0000000..b173ffb
--- /dev/null
+++ b/submarine-test/test-e2e/src/test/java/org/apache/submarine/integration/pages/ExperimentPage.java
@@ -0,0 +1,172 @@
+/*
+ * 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.submarine.integration.pages;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.PageFactory;
+import org.openqa.selenium.support.pagefactory.AjaxElementLocatorFactory;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import java.util.List;
+
+public class ExperimentPage {
+
+ @FindBy(id = "go")
+ private WebElement goButton;
+
+ @FindBy(id = "openExperiment")
+ private WebElement newExperimentButton;
+
+ /*
+ * For svg/path/g element tag, we must use //*[name = 'svg'] to select
+ * //svg will fail
+ */
+ @FindBy(xpath = "//*[name() = 'svg' and @data-icon = 'close-circle']")
+ private List<WebElement> deleteBtns;
+
+ // Meta form
+ @FindBy(name = "experimentName")
+ private WebElement experimentName;
+
+ @FindBy(name = "description")
+ private WebElement description;
+
+ @FindBy(name = "namespace")
+ private WebElement namespace;
+
+ @FindBy(name = "cmd")
+ private WebElement cmd;
+
+ @FindBy(name = "image")
+ private WebElement image;
+
+ // Env form
+ @FindBy(id = "env-btn")
+ private WebElement envBtn;
+
+ @FindBy(xpath = "//input[contains(@name, 'key')]")
+ private WebElement envKey;
+
+ @FindBy(xpath = "//input[contains(@name, 'value')]")
+ private WebElement envValue;
+
+ // Spec
+ @FindBy(id = "spec-btn")
+ private WebElement specBtn;
+
+ @FindBy(xpath = "//input[contains(@name, 'spec')]")
+ private List<WebElement> specNames;
+
+ @FindBy(xpath = "//input[contains(@name, 'replica')]")
+ private List<WebElement> replicas;
+
+ @FindBy(xpath = "//input[contains(@name, 'cpu')]")
+ private List<WebElement> cpus;
+
+ @FindBy(xpath = "//input[contains(@name, 'memory')]")
+ private List<WebElement> memory;
+
+ // Notification
+ @FindBy(xpath = "//div[contains(@class, 'ant-message-error')]//span")
+ private WebElement errorNotification;
+
+ @FindBy(xpath = "//div[contains(@class, 'ant-message-success')]//span")
+ private WebElement successNotification;
+
+ private WebDriverWait wait;
+
+ public ExperimentPage(WebDriver driver) {
+ // NoSuchElementException will be thrown if the elements are not found
+ PageFactory.initElements(new AjaxElementLocatorFactory(driver, 5), this);
+ wait = new WebDriverWait(driver, 15);
+ }
+
+ // Getter
+ public WebElement getGoButton() {
+ return goButton;
+ }
+
+ public WebElement getErrorNotification() {
+ return errorNotification;
+ }
+
+
+ public int getSpecs() {
+ return specNames.size();
+ }
+
+ // button click actions
+ public void goButtonClick() {
+ wait.until(ExpectedConditions.elementToBeClickable(goButton)).click();
+ }
+
+ public void newExperimentButtonClick() {
+ wait.until(ExpectedConditions.elementToBeClickable(newExperimentButton)).click();
+ }
+
+ public void envBtnClick() {
+ wait.until(ExpectedConditions.elementToBeClickable(envBtn)).click();
+ }
+
+ public void specBtnClick() {
+ wait.until(ExpectedConditions.elementToBeClickable(specBtn)).click();
+ }
+
+
+ // Real actions
+ public void fillMeta(String name, String des, String namespaceStr, String cmdStr, String imageStr) {
+ experimentName.clear();
+ experimentName.sendKeys(name);
+ description.clear();
+ description.sendKeys(des);
+ namespace.clear();
+ namespace.sendKeys(namespaceStr);
+ cmd.clear();
+ cmd.sendKeys(cmdStr);
+ image.clear();
+ image.sendKeys(imageStr);
+ }
+
+ public void fillEnv(String key, String value) {
+ envKey.sendKeys(key);
+ envValue.sendKeys(value);
+ }
+
+ public void deleteSpec() {
+ for (WebElement d : deleteBtns) {
+ d.click();
+ }
+ }
+
+ public void fillTfSpec(int specCount, String[] inputNames, int[] replicaCount, int[] cpuCount, String[] inputMemory) {
+ for (int i = 0; i < specCount; i++) {
+ specBtnClick();
+ }
+
+ for (int i = 0; i < specCount; i++) {
+ specNames.get(i).sendKeys(inputNames[i]);
+ replicas.get(i).sendKeys(Integer.toString(replicaCount[i]));
+ cpus.get(i).sendKeys(Integer.toString(cpuCount[i]));
+ memory.get(i).sendKeys(inputMemory[i]);
+ }
+
+ }
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/interfaces/experiment-spec.ts b/submarine-workbench/workbench-web-ng/src/app/interfaces/experiment-spec.ts
index 98906bf..2ea061b 100644
--- a/submarine-workbench/workbench-web-ng/src/app/interfaces/experiment-spec.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/interfaces/experiment-spec.ts
@@ -17,6 +17,31 @@
* under the License.
*/
-export class ExperimentSpec {
- // TODO(pingsutw): After refactor submarine experiment spec, we could start implementing it.
+export interface SpecMeta {
+ name: string;
+ namespace: string;
+ framework: string;
+ cmd: string;
+ envVars?: {
+ [key: string]: string;
+ };
+}
+
+export interface SpecEnviroment {
+ image: string;
+}
+
+export interface Specs {
+ [name: string]: {
+ replicas: string;
+ resources: string;
+ };
+}
+
+export interface ExperimentSpec {
+ meta: SpecMeta;
+ environment: {
+ image: string;
+ };
+ spec: Specs;
}
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment.component.html b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment.component.html
index 39dacd9..480b32e 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment.component.html
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment.component.html
@@ -155,7 +155,7 @@
</nz-steps>
</div>
<div>
- <form [formGroup]="createExperiment">
+ <form [formGroup]="experiment">
<div *nzModalFooter>
<button nz-button nzType="default" (click)="isVisible = false">Cancel</button>
<button id="go" nz-button nzType="primary" [disabled]="checkStatus()" (click)="handleOk()">
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment.component.scss b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment.component.scss
index 1cc8199..57998b8 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment.component.scss
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment.component.scss
@@ -113,7 +113,3 @@
flex-direction: column;
align-items: center;
}
-
-.pg3-form-label {
-
-}
\ No newline at end of file
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment.component.ts b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment.component.ts
index 8b07030..37af51c 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment.component.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment.component.ts
@@ -24,6 +24,7 @@ import { ExperimentInfo } from '@submarine/interfaces/experiment-info';
import { ExperimentService } from '@submarine/services/experiment.service';
import { ExperimentFormService } from '@submarine/services/experiment.validator.service';
import { NzMessageService } from 'ng-zorro-antd';
+import { SpecMeta, Specs, SpecEnviroment, ExperimentSpec } from '@submarine/interfaces/experiment-spec';
@Component({
selector: 'submarine-experiment',
@@ -41,7 +42,7 @@ export class ExperimentComponent implements OnInit {
searchText = '';
// About new experiment
- createExperiment: FormGroup;
+ experiment: FormGroup;
current = 0;
okText = 'Next Step';
isVisible = false;
@@ -65,7 +66,7 @@ export class ExperimentComponent implements OnInit {
) {}
ngOnInit() {
- this.createExperiment = new FormGroup({
+ this.experiment = new FormGroup({
experimentName: new FormControl(null, Validators.required),
description: new FormControl(null, [Validators.required]),
// experimentSpec: new FormControl('Adhoc'),
@@ -97,28 +98,28 @@ export class ExperimentComponent implements OnInit {
// Getters of experiment request form
get experimentName() {
- return this.createExperiment.get('experimentName');
+ return this.experiment.get('experimentName');
}
get description() {
- return this.createExperiment.get('description');
+ return this.experiment.get('description');
}
get frameworks() {
- return this.createExperiment.get('frameworks');
+ return this.experiment.get('frameworks');
}
get namespace() {
- return this.createExperiment.get('namespace');
+ return this.experiment.get('namespace');
}
get cmd() {
- return this.createExperiment.get('cmd');
+ return this.experiment.get('cmd');
}
get envs() {
- return this.createExperiment.get('envs') as FormArray;
+ return this.experiment.get('envs') as FormArray;
}
get image() {
- return this.createExperiment.get('image');
+ return this.experiment.get('image');
}
get specs() {
- return this.createExperiment.get('specs') as FormArray;
+ return this.experiment.get('specs') as FormArray;
}
/**
* Check the validity of the experiment page
@@ -141,9 +142,38 @@ export class ExperimentComponent implements OnInit {
}
}
+ /**
+ * Init a new experiment form, clear all status
+ */
+ initExperimentStatus() {
+ this.isVisible = false;
+ this.current = 0;
+ this.okText = 'Next step';
+ }
+
+ /**
+ * Event handler for Next step/Submit button
+ */
handleOk() {
if (this.current === 1) {
this.okText = 'Submit';
+ } else if (this.current === 2) {
+ const newSpec = this.constructSpec();
+ this.experimentService.createExperiment(newSpec).subscribe({
+ next: (result) => {
+ // Must reconstruct a new array for re-rendering
+ this.experimentList = [...this.experimentList, result];
+ },
+ error: (msg) => {
+ this.nzMessageService.error(`${msg}, please try again`, {
+ nzPauseOnHover: true
+ });
+ },
+ complete: () => {
+ this.nzMessageService.success('Experiment creation succeeds');
+ this.initExperimentStatus();
+ }
+ });
}
if (this.current < 2) {
@@ -191,6 +221,47 @@ export class ExperimentComponent implements OnInit {
}
/**
+ * Construct spec for new experiment creation
+ */
+ constructSpec(): ExperimentSpec {
+ // Construct the spec
+ const meta: SpecMeta = {
+ name: this.experimentName.value,
+ namespace: this.namespace.value,
+ framework: this.frameworks.value,
+ cmd: this.cmd.value,
+ envVars: {}
+ };
+ for (const env of this.envs.controls) {
+ if (env.get('key').value) {
+ meta.envVars[env.get('key').value] = env.get('value').value;
+ }
+ }
+
+ const specs: Specs = {};
+ for (const spec of this.specs.controls) {
+ if (spec.get('name').value) {
+ specs[spec.get('name').value] = {
+ replicas: spec.get('replicas').value,
+ resources: `cpu=${spec.get('cpus').value},memory=${spec.get('memory').value}`
+ };
+ }
+ }
+
+ const enviroment: SpecEnviroment = {
+ image: this.image.value
+ };
+
+ const newExperimentSpec: ExperimentSpec = {
+ meta: meta,
+ environment: enviroment,
+ spec: specs
+ };
+
+ return newExperimentSpec;
+ }
+
+ /**
* Delete list items(envs or specs)
*
* @param arr - The FormArray containing the item
diff --git a/submarine-workbench/workbench-web-ng/src/app/services/base-api.service.ts b/submarine-workbench/workbench-web-ng/src/app/services/base-api.service.ts
index 86b2710..b9506d8 100644
--- a/submarine-workbench/workbench-web-ng/src/app/services/base-api.service.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/services/base-api.service.ts
@@ -35,7 +35,8 @@ class HttpError extends Error {
this.params = params;
if (!environment.production) {
- this.logError();
+ // comment out because weird this behavior
+ // this.logError();
}
}
diff --git a/submarine-workbench/workbench-web-ng/src/app/services/experiment.service.ts b/submarine-workbench/workbench-web-ng/src/app/services/experiment.service.ts
index 41876ae..18ab660 100644
--- a/submarine-workbench/workbench-web-ng/src/app/services/experiment.service.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/services/experiment.service.ts
@@ -22,8 +22,8 @@ import { Injectable } from '@angular/core';
import { Rest } from '@submarine/interfaces';
import { ExperimentInfo } from '@submarine/interfaces/experiment-info';
import { BaseApiService } from '@submarine/services/base-api.service';
-import { of, Observable } from 'rxjs';
-import { switchMap } from 'rxjs/operators';
+import { of, Observable, throwError } from 'rxjs';
+import { switchMap, catchError, map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
@@ -61,12 +61,23 @@ export class ExperimentService {
createExperiment(experimentSpec): Observable<ExperimentInfo> {
const apiUrl = this.baseApi.getRestApi('/v1/experiment');
return this.httpClient.post<Rest<ExperimentInfo>>(apiUrl, experimentSpec).pipe(
- switchMap((res) => {
- if (res.success) {
- return of(res.result);
+ map((res) => res.result), // return result directly if succeeding
+ catchError((e) => {
+ let message: string;
+ if (e.error instanceof ErrorEvent) {
+ // client side error
+ message = 'Something went wrong with network or workbench';
} else {
- throw this.baseApi.createRequestError(res.message, res.code, apiUrl, 'post', experimentSpec);
+ console.log(e);
+ if (e.status === 409) {
+ message = 'You might have a duplicate experiment name';
+ } else if (e.status >= 500) {
+ message = `${e.message}`;
+ } else {
+ message = e.error.message;
+ }
}
+ return throwError(message);
})
);
}
diff --git a/submarine-workbench/workbench-web-ng/src/app/services/experiment.validator.service.ts b/submarine-workbench/workbench-web-ng/src/app/services/experiment.validator.service.ts
index b9b2461..923e98b 100644
--- a/submarine-workbench/workbench-web-ng/src/app/services/experiment.validator.service.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/services/experiment.validator.service.ts
@@ -56,8 +56,8 @@ export class ExperimentFormService {
* @param memory - The memory field in Spec
*/
memoryValidator: ValidatorFn = (memory: FormControl): ValidationErrors | null => {
- // Must match number + digit ex. 512M
- return memory.value && /^\d+M$/.test(memory.value)
+ // Must match number + digit ex. 512M or empty
+ return !memory.value || /^\d+M$/.test(memory.value)
? null
: { memoryPatternError: 'Memory pattern must match number + M ex. 512M' };
};
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@submarine.apache.org
For additional commands, e-mail: dev-help@submarine.apache.org