You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@submarine.apache.org by li...@apache.org on 2020/09/22 11:47:18 UTC
[submarine] branch master updated: SUBMARINE-625. [WEB] Support
running standalone script
This is an automated email from the ASF dual-hosted git repository.
liuxun 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 d5b5a26 SUBMARINE-625. [WEB] Support running standalone script
d5b5a26 is described below
commit d5b5a265765468888c0313f7434001d606dbb1b0
Author: pingsutw <pi...@gmail.com>
AuthorDate: Mon Sep 21 14:48:45 2020 +0800
SUBMARINE-625. [WEB] Support running standalone script
### What is this PR for?
Support running any python script in a single node.
### What type of PR is it?
[Feature]
### Todos
* [ ] - Task
### What is the Jira issue?
https://issues.apache.org/jira/browse/SUBMARINE-625
### How should this be tested?
https://travis-ci.org/github/pingsutw/hadoop-submarine/builds/727226256
### Screenshots (if appropriate)
![Screenshot from 2020-09-15 09-30-11](https://user-images.githubusercontent.com/37936015/93154945-88e21000-f737-11ea-8ce8-8a3def39a3b3.png)
![Screenshot from 2020-09-18 17-51-01](https://user-images.githubusercontent.com/37936015/93584977-b0a3d300-f9d8-11ea-97fc-ae4c309607ca.png)
![Screenshot from 2020-09-18 17-51-18](https://user-images.githubusercontent.com/37936015/93584978-b13c6980-f9d8-11ea-8fe0-dbd0bb780890.png)
![Screenshot from 2020-09-18 17-51-24](https://user-images.githubusercontent.com/37936015/93584982-b26d9680-f9d8-11ea-8b62-811051d7fd7f.png)
![Screenshot from 2020-09-18 17-51-32](https://user-images.githubusercontent.com/37936015/93584987-b39ec380-f9d8-11ea-8018-477ac501762a.png)
### Questions:
* Does the licenses files need update? No
* Is there breaking changes for older versions? No
* Does this needs documentation? No
Author: pingsutw <pi...@gmail.com>
Author: Kevin Su <pi...@gmail.com>
Closes #401 from pingsutw/standalone and squashes the following commits:
6d5be07 [Kevin Su] Update ui
7c6658e [pingsutw] Update test
f1decfe [pingsutw] Update test
ec00a93 [pingsutw] Update test
0de9208 [pingsutw] Update test
a786575 [pingsutw] Update test
9c2855f [pingsutw] Update test
f445724 [pingsutw] Update test
f14efb2 [Kevin Su] Update ui
a2c2eed [Kevin Su] Add standalone option
---
.../submarine/server/api/spec/ExperimentSpec.java | 6 +-
.../apache/submarine/integration/experimentIT.java | 21 ++-
.../integration/pages/ExperimentPage.java | 51 ++++---
.../src/app/interfaces/experiment-spec.ts | 15 +-
.../environment/environment.component.html | 26 ++--
.../experiment-customized-form.component.html | 163 +++++++++++++++------
.../experiment-customized-form.component.scss | 8 +-
.../experiment-customized-form.component.ts | 108 +++++++++-----
.../experiment-info/experiment-info.component.html | 8 +-
.../experiment-info/experiment-info.component.ts | 21 ++-
.../experiment-info/outputs/outputs.component.html | 2 +-
.../workbench/experiment/experiment.component.html | 14 +-
.../workbench/experiment/experiment.component.ts | 8 +-
.../workbench/experiment/experiment.module.ts | 8 +-
.../src/app/services/environment.service.ts | 5 +-
.../app/services/experiment.validator.service.ts | 4 +-
16 files changed, 301 insertions(+), 167 deletions(-)
diff --git a/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/spec/ExperimentSpec.java b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/spec/ExperimentSpec.java
index 2a43f28..5e8b23d 100644
--- a/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/spec/ExperimentSpec.java
+++ b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/spec/ExperimentSpec.java
@@ -30,9 +30,7 @@ public class ExperimentSpec {
private Map<String, ExperimentTaskSpec> spec;
private CodeSpec code;
- public ExperimentSpec() {
-
- }
+ public ExperimentSpec() {}
public ExperimentMeta getMeta() {
return meta;
@@ -57,7 +55,7 @@ public class ExperimentSpec {
public void setSpec(Map<String, ExperimentTaskSpec> spec) {
this.spec = spec;
}
-
+
public CodeSpec getCode() {
return code;
}
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 477277e..ed8782a 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
@@ -61,20 +61,21 @@ public class experimentIT extends AbstractSubmarineIT {
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/workbench/experiment");
// Test create new experiment
- LOG.info("new experiment");
+ LOG.info("First step");
experimentPage.newExperimentButtonClick();
experimentPage.customizedBtnClick();
- String experimentName = "experiment-e2e-test";
- experimentPage.fillMeta(experimentName, "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.advancedButtonCLick();
experimentPage.envBtnClick();
- experimentPage.fillEnv("ENV_1", "ENV1");
+ String experimentName = "experiment-e2e-test";
+ experimentPage.fillExperimentMeta(experimentName, "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",
+ "ENV_1", "ENV1");
Assert.assertTrue(experimentPage.getGoButton().isEnabled());
experimentPage.goButtonClick();
+ LOG.info("Second step");
// Fail due to incorrect spec name
LOG.info("In spec fail");
experimentPage.fillTfSpec(1, new String[]{"Master"}, new int[]{-1}, new int[]{-1}, new int[]{512});
@@ -84,7 +85,11 @@ public class experimentIT extends AbstractSubmarineIT {
experimentPage.deleteSpec();
experimentPage.fillTfSpec(2, new String[]{"Ps", "Worker"}, new int[]{1, 1}, new int[]{1, 1}, new int[]{1024, 1024});
Assert.assertTrue(experimentPage.getGoButton().isEnabled());
+ experimentPage.goButtonClick();
+ LOG.info("Preview experiment spec");
+ 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
index eefe45d..fabbdbb 100644
--- 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
@@ -26,6 +26,7 @@ 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.Select;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,10 +39,13 @@ public class ExperimentPage {
private WebElement dataSection;
@FindBy(id = "go")
- private WebElement goButton;
+ private WebElement goBtn;
+
+ @FindBy(id = "advancedBtn")
+ private WebElement advancedBtn;
@FindBy(id = "openExperiment")
- private WebElement newExperimentButton;
+ private WebElement newExperimentBtn;
@FindBy(id = "customized")
private WebElement customizedBtn;
@@ -59,13 +63,13 @@ public class ExperimentPage {
@FindBy(name = "description")
private WebElement description;
- @FindBy(name = "namespace")
+ @FindBy(id = "namespace")
private WebElement namespace;
@FindBy(name = "cmd")
private WebElement cmd;
- @FindBy(name = "image")
+ @FindBy(id = "image")
private WebElement image;
// Env form
@@ -116,16 +120,20 @@ public class ExperimentPage {
// Getter
public WebElement getGoButton() {
- return goButton;
+ return goBtn;
}
// button click actions
public void goButtonClick() {
- wait.until(ExpectedConditions.elementToBeClickable(goButton)).click();
+ wait.until(ExpectedConditions.elementToBeClickable(goBtn)).click();
+ }
+
+ public void advancedButtonCLick() {
+ wait.until(ExpectedConditions.elementToBeClickable(advancedBtn)).click();
}
public void newExperimentButtonClick() {
- wait.until(ExpectedConditions.elementToBeClickable(newExperimentButton)).click();
+ wait.until(ExpectedConditions.elementToBeClickable(newExperimentBtn)).click();
}
public void customizedBtnClick() {
@@ -141,22 +149,15 @@ public class ExperimentPage {
}
// 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 fillExperimentMeta(String name, String description, String namespace, String cmd, String image,
+ String envKey, String envValue) {
+ this.experimentName.clear();
+ this.experimentName.sendKeys(name);
+ this.description.clear();
+ this.description.sendKeys(description);
+ this.cmd.sendKeys(cmd);
+ this.envKey.sendKeys(envKey);
+ this.envValue.sendKeys(envValue);
}
public void deleteSpec() {
@@ -165,7 +166,8 @@ public class ExperimentPage {
}
}
- public void fillTfSpec(int specCount, String[] inputNames, int[] replicaCount, int[] cpuCount, int[] inputMemory) {
+ public void fillTfSpec(int specCount, String[] inputNames,
+ int[] replicaCount, int[] cpuCount, int[] inputMemory) {
for (int i = 0; i < specCount; i++) {
specBtnClick();
}
@@ -183,6 +185,7 @@ public class ExperimentPage {
replicas.get(i).sendKeys(Integer.toString(replicaCount[i]));
cpus.get(i).clear();
cpus.get(i).sendKeys(Integer.toString(cpuCount[i]));
+ memory.get(i).clear();
memory.get(i).sendKeys(Integer.toString(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 e10ceb6..64524bb 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,7 +17,7 @@
* under the License.
*/
-export interface SpecMeta {
+export interface ExperimentMeta {
name: string;
description?: string;
namespace: string;
@@ -28,7 +28,7 @@ export interface SpecMeta {
};
}
-export interface SpecEnviroment {
+export interface EnvironmentSpec {
image: string;
}
@@ -36,13 +36,16 @@ export interface Specs {
[name: string]: {
replicas: string;
resources: string;
+ resourceMap?: {
+ memory: string;
+ cpu: string;
+ gpu: string;
+ };
};
}
export interface ExperimentSpec {
- meta: SpecMeta;
- environment: {
- image: string;
- };
+ meta: ExperimentMeta;
+ environment: EnvironmentSpec;
spec: Specs;
}
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/environment/environment.component.html b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/environment/environment.component.html
index 8fcdd17..f750614 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/environment/environment.component.html
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/environment/environment.component.html
@@ -17,7 +17,7 @@
~ under the License.
-->
-<nz-layout style="margin: -24px -24px 16px;">
+<nz-layout style="margin: -24px -24px 16px">
<nz-layout class="inner-layout">
<div id="environmentOuter">
<nz-breadcrumb>
@@ -44,7 +44,7 @@
<div align="right">
<nz-input-group
nzSearch
- style="width: 300px; margin-top: 15px; margin-left: 10px; margin-right: 5px;"
+ style="width: 300px; margin-top: 15px; margin-left: 10px; margin-right: 5px"
[nzAddOnAfter]="suffixIconButton"
>
<input type="text" nz-input placeholder="input search text" />
@@ -57,7 +57,7 @@
nz-button
id="createEnvironment"
nzType="primary"
- style="margin-right: 5px; margin-bottom: 15px; margin-top: 15px;"
+ style="margin-right: 5px; margin-bottom: 15px; margin-top: 15px"
(click)="initEnvForm()"
>
<i nz-icon nzType="plus"></i>
@@ -67,7 +67,7 @@
nz-button
id="deleteEnvironment"
nzType="primary"
- style="margin-bottom: 15px; margin-top: 15px;"
+ style="margin-bottom: 15px; margin-top: 15px"
nz-popconfirm
nzTitle="Confirm to delete?"
nzCancelText="Cancel"
@@ -120,9 +120,7 @@
<nz-modal [(nzVisible)]="isVisible" nzTitle="Create Environment" [nzWidth]="700">
<div *nzModalFooter>
<button nz-button nzType="default" (click)="closeModal()">Cancel</button>
- <button id="go" nz-button nzType="primary" [disabled]="checkStatus()" (click)="createEnvironment()">
- Create
- </button>
+ <button id="go" nz-button nzType="primary" [disabled]="checkStatus()" (click)="createEnvironment()">Create</button>
</div>
<div>
<form [formGroup]="environmentForm">
@@ -163,7 +161,7 @@
<input required nz-input type="text" name="channel{{ i }}" id="channel{{ i }}" [formControlName]="i" />
<i
nz-icon
- style="margin-left: 5px;"
+ style="margin-left: 5px"
nzType="close-circle"
nzTheme="fill"
(click)="deleteItem(channels, i)"
@@ -171,10 +169,10 @@
</div>
</div>
</div>
- <div style="margin: 10px;">
+ <div style="margin: 10px">
<button
nz-button
- style="display: block; margin: auto;"
+ style="display: block; margin: auto"
id="addChannel-btn"
type="default"
(click)="addChannel()"
@@ -196,7 +194,7 @@
/>
<i
nz-icon
- style="margin-left: 5px;"
+ style="margin-left: 5px"
nzType="close-circle"
nzTheme="fill"
(click)="deleteItem(dependencies, i)"
@@ -204,11 +202,11 @@
</div>
</div>
</div>
- <div style="margin: 10px;">
+ <div style="margin: 10px">
<button
- style="margin-top: 10px;"
+ style="margin-top: 10px"
nz-button
- style="display: block; margin: auto;"
+ style="display: block; margin: auto"
id="addDep-btn"
type="default"
(click)="addDependencies()"
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-customized-form/experiment-customized-form.component.html b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-customized-form/experiment-customized-form.component.html
index 493f274..8b2b58e 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-customized-form/experiment-customized-form.component.html
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-customized-form/experiment-customized-form.component.html
@@ -19,15 +19,15 @@
<div>
<nz-steps [nzCurrent]="step">
- <nz-step nzTitle="Meta"></nz-step>
- <nz-step nzTitle="Env"></nz-step>
- <nz-step nzTitle="Spec"></nz-step>
+ <nz-step nzTitle="First Step"></nz-step>
+ <nz-step nzTitle="Second Step"></nz-step>
+ <nz-step nzTitle="Preview"></nz-step>
</nz-steps>
</div>
<div>
<form [formGroup]="experiment">
- <div [ngSwitch]="step" style="margin-top: 30px;">
- <div *ngSwitchCase="0">
+ <div [ngSwitch]="step" style="margin-top: 30px">
+ <div *ngSwitchCase="0" id="firstStep">
<div class="single-field-group">
<label for="experimentName">
<span class="red-star">*</span>
@@ -36,53 +36,85 @@
<input nz-input type="text" name="experimentName" id="experimentName" formControlName="experimentName" />
</div>
<div class="single-field-group">
- <label for="description">
- Description
- </label>
+ <label for="description">Description</label>
<textarea
nz-input
- [nzAutosize]="{ minRows: 3, maxRows: 6 }"
+ [nzAutosize]="{ minRows: 1, maxRows: 4 }"
name="description"
formControlName="description"
+ id="description"
></textarea>
</div>
<div class="single-field-group">
- <label for="namespace">
- <span class="red-star">*</span>
- Namespace
- </label>
- <input nz-input name="namespace" formControlName="namespace" />
- </div>
- <div class="single-field-group">
<label for="cmd">
<span class="red-star">*</span>
Command
</label>
- <input nz-input name="cmd" formControlName="cmd" placeholder="Command to run" />
+ <textarea
+ nz-input
+ name="cmd"
+ formControlName="cmd"
+ placeholder="Command to run"
+ id="cmd"
+ [nzAutosize]="{ minRows: 2, maxRows: 4 }"
+ ></textarea>
</div>
<div class="single-field-group">
<label for="image">
<span class="red-star">*</span>
Image
</label>
- <input nz-input name="image" formControlName="image" placeholder="Image to use" />
+ <nz-select
+ nzShowSearch
+ [nzDropdownRender]="renderTemplate"
+ formControlName="image"
+ id="image"
+ >
+ <nz-option *ngFor="let image of imageList" [nzValue]="image" [nzLabel]="image"></nz-option>
+ </nz-select>
+ <ng-template #renderTemplate>
+ <nz-divider></nz-divider>
+ <div class="container">
+ <input type="text" nz-input #inputElement />
+ <a class="add-item" (click)="addItem(inputElement)">
+ <i nz-icon nzType="plus"></i>
+ Add item
+ </a>
+ </div>
+ </ng-template>
</div>
- </div>
- <div *ngSwitchCase="1" id="page2">
- <div>
- <button nz-button id="env-btn" type="default" (click)="onCreateEnv()">
- Add new env
+ <div style="margin-bottom: 10px">
+ <button
+ nz-button
+ style="display: block; margin: auto"
+ id="advancedBtn"
+ nzType="default"
+ (click)="ADVANCED = !ADVANCED"
+ >
+ Advanced
+ <i nz-icon [nzType]="ADVANCED ? 'up' : 'down'"></i>
</button>
+ </div>
+ <div *ngIf="ADVANCED" class="single-field-group">
+ <label for="namespace">
+ <span class="red-star">*</span>
+ Namespace
+ </label>
+ <nz-select formControlName="namespace" id="namespace">
+ <nz-option *ngFor="let namespace of nameSpaceList" [nzValue]="namespace" [nzLabel]="namespace"></nz-option>
+ </nz-select>
+ </div>
+ <div *ngIf="ADVANCED">
<ul formArrayName="envs" class="list-container">
<ng-container *ngFor="let env of envs.controls; index as i">
- <li *ngIf="i | indexInRange: currentEnvPage:PAGESIZE" [formGroupName]="i" class="input-group">
- <div>
+ <li [formGroupName]="i" class="input-group">
+ <div style="margin-left: 30%">
<label for="key{{ i }}">Key</label>
- <input nz-input name="key{{ i }}" placeholder="Key" formControlName="key" />
+ <input nz-input name="key{{ i }}" placeholder="Key" formControlName="key" id="key{{ i }}" />
</div>
- <div>
+ <div style="margin-left: 0">
<label for="value{{ i }}">Value</label>
- <input nz-input name="value{{ i }}" placeholder="Value" formControlName="value" />
+ <input nz-input name="value{{ i }}" placeholder="Value" formControlName="value" id="value{{ i }}" />
</div>
<i nz-icon nzType="close-circle" nzTheme="fill" (click)="deleteItem(envs, i)"></i>
</li>
@@ -100,29 +132,37 @@
</div>
</ng-container>
</ul>
- <nz-pagination
- [(nzPageIndex)]="currentEnvPage"
- [nzPageSize]="PAGESIZE"
- [nzTotal]="envs.controls.length"
- nzSimple
- ></nz-pagination>
+ <button nz-button id="env-btn" style="display: block; margin: auto" nzType="primary" (click)="onCreateEnv()">
+ Add new environment variable
+ </button>
</div>
</div>
- <div *ngSwitchCase="2">
- <nz-radio-group [(ngModel)]="jobTypes" [ngModelOptions]="{ standalone: true }" id="jobs-container">
- <label nz-radio nzValue="Tensorflow">Distributed Tensorflow</label>
- <label nz-radio nzValue="Pytorch">Distributed PyTorch</label>
+ <div *ngSwitchCase="1" id="secondStep">
+ <nz-radio-group [(ngModel)]="framework" [ngModelOptions]="{ standalone: true }" id="jobs-container">
+ <label nz-radio nzValue="Tensorflow" (click)="deleteAllItem(specs); jobTypes = 'Distributed Tensorflow'">
+ Distributed Tensorflow
+ </label>
+ <label nz-radio nzValue="Pytorch" (click)="deleteAllItem(specs); jobTypes = 'Distributed Pytorch'">
+ Distributed PyTorch
+ </label>
+ <label
+ nz-radio
+ nzValue="Standalone"
+ (click)="deleteAllItem(specs); onCreateSpec(); jobTypes = 'Standalone Script'"
+ >
+ Standalone Script
+ </label>
</nz-radio-group>
<label class="pg3-form-label"></label>
- <button nz-button id="spec-btn" type="default" (click)="onCreateSpec()">
+ <button *ngIf="framework !== 'Standalone'" nz-button id="spec-btn" nzType="default" (click)="onCreateSpec()">
Add new spec
</button>
<ul formArrayName="specs" class="list-container">
<ng-container *ngFor="let spec of specs.controls; index as i">
<li *ngIf="i | indexInRange: currentSpecPage:PAGESIZE" [formGroupName]="i" class="input-group">
- <div id="spec{{ i }}">
+ <div id="spec{{ i }}" *ngIf="framework !== 'Standalone'">
<label>Spec name</label>
- <nz-select formControlName="name" nzPlaceHolder="Spec name" [ngSwitch]="jobTypes">
+ <nz-select formControlName="name" nzPlaceHolder="Spec name" [ngSwitch]="framework">
<div *ngSwitchCase="'Tensorflow'">
<nz-option *ngFor="let spec of TF_SPECNAMES" [nzValue]="spec" [nzLabel]="spec"></nz-option>
</div>
@@ -131,7 +171,7 @@
</div>
</nz-select>
</div>
- <div>
+ <div *ngIf="framework !== 'Standalone'">
<label>Number of Replica</label>
<input
nz-input
@@ -145,10 +185,21 @@
<label>Number of cpu</label>
<input nz-input name="cpu{{ i }}" type="number" placeholder="number of cpu" formControlName="cpus" />
</div>
+ <div>
+ <label>Number of gpu</label>
+ <input nz-input name="gpu{{ i }}" type="number" placeholder="number of gpu" formControlName="gpus" />
+ </div>
<div id="memory{{ i }}">
<label>Memory</label>
<div formGroupName="memory" class="memory-input-group">
- <input nz-input name="memory{{ i }}" placeholder="Enter number" formControlName="num" />
+ <input
+ nz-input
+ name="memory{{ i }}"
+ type="number"
+ step="1024"
+ placeholder="Enter number"
+ formControlName="num"
+ />
<nz-select formControlName="unit">
<nz-option *ngFor="let unit of MEMORY_UNITS" [nzValue]="unit" [nzLabel]="unit"></nz-option>
</nz-select>
@@ -162,6 +213,7 @@
>
{{ spec.getError('specError') }}
</div>
+
<div
class="alert-message"
*ngIf="spec.get('name').dirty | logicalAnd: spec.get('name').hasError('duplicateError')"
@@ -183,6 +235,9 @@
<div class="alert-message" *ngIf="spec.get('cpus').dirty | logicalAnd: spec.get('cpus').hasError('min')">
cpus must be at least 1
</div>
+ <div class="alert-message" *ngIf="spec.get('gpus').dirty | logicalAnd: spec.get('gpus').hasError('min')">
+ cpus must be at least 0
+ </div>
</ng-container>
</ul>
<nz-pagination
@@ -192,6 +247,28 @@
nzSimple
></nz-pagination>
</div>
+ <div *ngSwitchCase="2" id="previewPage">
+ <nz-descriptions nzTitle="{{ jobTypes }}" nzBordered [nzColumn]="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 2, xs: 1 }">
+ <nz-descriptions-item nzTitle="Name">{{ finialExperimentSpec.meta.name }}</nz-descriptions-item>
+ <nz-descriptions-item nzTitle="Namespace">{{ finialExperimentSpec.meta.namespace }}</nz-descriptions-item>
+ <nz-descriptions-item nzTitle="Command" [nzSpan]="2">
+ {{ finialExperimentSpec.meta.cmd }}
+ </nz-descriptions-item>
+ <nz-descriptions-item nzTitle="Image" [nzSpan]="2">
+ {{ finialExperimentSpec.environment.image }}
+ </nz-descriptions-item>
+ <nz-descriptions-item nzTitle="Environment Variables" [nzSpan]="2">
+ <span *ngFor="let item of finialExperimentSpec.meta.envVars | keyvalue; let isLast = last">
+ {{ item.key }}={{ item.value }}{{ isLast ? '' : ', ' }}
+ </span>
+ </nz-descriptions-item>
+ <div *ngFor="let item of finialExperimentSpec.spec | keyvalue">
+ <nz-descriptions-item nzTitle="{{ item.key }}" [nzSpan]="2">
+ {{ item.value.resources }}
+ </nz-descriptions-item>
+ </div>
+ </nz-descriptions>
+ </div>
</div>
</form>
</div>
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-customized-form/experiment-customized-form.component.scss b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-customized-form/experiment-customized-form.component.scss
index adf39b2..a158387 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-customized-form/experiment-customized-form.component.scss
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-customized-form/experiment-customized-form.component.scss
@@ -78,7 +78,7 @@ textarea.ng-invalid.ng-touched {
}
& > div:nth-child(1), & > div:nth-child(4) {
- flex: 0 1 25%;
+ flex: 0 1 20%;
}
& i {
@@ -112,14 +112,12 @@ textarea.ng-invalid.ng-touched {
margin-right: 1.5rem;
}
}
- & input {
+ & input, nz-select, textarea {
flex: 0 0 48%;
}
- & textarea {
- flex: 0 0 55%;
- }
}
+
.alert-message {
color: red;
margin-top: .3rem;
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-customized-form/experiment-customized-form.component.ts b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-customized-form/experiment-customized-form.component.ts
index 1022e71..2a8b8a4 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-customized-form/experiment-customized-form.component.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-customized-form/experiment-customized-form.component.ts
@@ -17,41 +17,56 @@
* under the License.
*/
-import { Component, OnInit, Input, OnDestroy } from '@angular/core';
+import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms';
-import { ExperimentSpec, Specs, SpecEnviroment, SpecMeta } from '@submarine/interfaces/experiment-spec';
+import { EnvironmentSpec, ExperimentMeta, ExperimentSpec, Specs } from '@submarine/interfaces/experiment-spec';
+import { ExperimentFormService } from '@submarine/services/experiment.form.service';
import { ExperimentService } from '@submarine/services/experiment.service';
import { ExperimentValidatorService } from '@submarine/services/experiment.validator.service';
-import { NzMessageService } from 'ng-zorro-antd';
import { nanoid } from 'nanoid';
+import { NzMessageService } from 'ng-zorro-antd';
import { Subscription } from 'rxjs';
-import { ExperimentFormService } from '@submarine/services/experiment.form.service';
@Component({
- selector: 'experiment-customized-form',
+ selector: 'submarine-experiment-customized-form',
templateUrl: './experiment-customized-form.component.html',
styleUrls: ['./experiment-customized-form.component.scss']
})
-export class ExperimentCustomizedForm implements OnInit, OnDestroy {
+export class ExperimentCustomizedFormComponent implements OnInit, OnDestroy {
@Input() mode: 'create' | 'update' | 'clone';
// About new experiment
experiment: FormGroup;
+ finialExperimentSpec: ExperimentSpec;
step: number = 0;
subscriptions: Subscription[] = [];
+ // TODO: Fetch all namespaces from submarine server
+ defaultNameSpace = 'default';
+ nameSpaceList = [this.defaultNameSpace, 'submarine'];
+
+ // TODO: Fetch all images from submarine server
+ imageIndex = 0;
+ defaultImage = 'gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0'
+ imageList = [this.defaultImage];
+
// Constants
TF_SPECNAMES = ['Master', 'Worker', 'Ps'];
PYTORCH_SPECNAMES = ['Master', 'Worker'];
+ defaultSpecName = 'worker';
MEMORY_UNITS = ['M', 'G'];
- TOTAL_STEPS = 2;
+
+ SECOND_STEP = 1;
+ PREVIEW_STEP = 2;
+ ADVANCED = false;
// About env page
currentEnvPage = 1;
PAGESIZE = 5;
// About spec
- jobTypes = 'Tensorflow';
+ jobTypes = 'Distributed Tensorflow';
+ framework = 'Tensorflow';
currentSpecPage = 1;
// About update
@@ -69,9 +84,9 @@ export class ExperimentCustomizedForm implements OnInit, OnDestroy {
this.experiment = new FormGroup({
experimentName: new FormControl(null, Validators.required),
description: new FormControl(null, [Validators.required]),
- namespace: new FormControl('default', [Validators.required]),
+ namespace: new FormControl(this.defaultNameSpace, [Validators.required]),
cmd: new FormControl('', [Validators.required]),
- image: new FormControl('', [Validators.required]),
+ image: new FormControl(this.defaultImage, [Validators.required]),
envs: new FormArray([], [this.experimentValidatorService.nameValidatorFactory('key')]),
specs: new FormArray([], [this.experimentValidatorService.nameValidatorFactory('name')])
});
@@ -90,8 +105,11 @@ export class ExperimentCustomizedForm implements OnInit, OnDestroy {
const sub2 = this.experimentFormService.stepService.subscribe((n) => {
if (n > 0) {
- if (this.step === this.TOTAL_STEPS) {
+ if (this.step === this.PREVIEW_STEP) {
this.handleSubmit();
+ } else if (this.step === this.SECOND_STEP) {
+ this.onPreview();
+ this.step += 1;
} else {
this.step += 1;
}
@@ -100,7 +118,7 @@ export class ExperimentCustomizedForm implements OnInit, OnDestroy {
}
// Send the current step and okText back to parent
this.experimentFormService.modalPropsChange({
- okText: this.step !== this.TOTAL_STEPS ? 'Next step' : 'Submit',
+ okText: this.step !== this.PREVIEW_STEP ? 'Next step' : 'Submit',
currentStep: this.step
});
// Run check after step is changed
@@ -117,6 +135,13 @@ export class ExperimentCustomizedForm implements OnInit, OnDestroy {
});
}
+ addItem(input: HTMLInputElement): void {
+ const value = input.value;
+ if (this.imageList.indexOf(value) === -1) {
+ this.imageList = [...this.imageList, input.value || `New item ${this.imageIndex++}`];
+ }
+ }
+
// Getters of experiment request form
get experimentName() {
return this.experiment.get('experimentName');
@@ -141,7 +166,7 @@ export class ExperimentCustomizedForm implements OnInit, OnDestroy {
}
/**
- * Reset properies in parent component when the form is about to closed
+ * Reset properties in parent component when the form is about to closed
*/
closeModal() {
this.experimentFormService.modalPropsClear();
@@ -153,22 +178,25 @@ export class ExperimentCustomizedForm implements OnInit, OnDestroy {
checkStatus() {
if (this.step === 0) {
this.experimentFormService.btnStatusChange(
- this.experimentName.invalid || this.namespace.invalid || this.cmd.invalid || this.image.invalid
+ this.experimentName.invalid ||
+ this.namespace.invalid ||
+ this.cmd.invalid ||
+ this.image.invalid ||
+ this.envs.invalid
);
} else if (this.step === 1) {
- this.experimentFormService.btnStatusChange(this.envs.invalid);
- } else if (this.step === this.TOTAL_STEPS) {
this.experimentFormService.btnStatusChange(this.specs.invalid);
}
}
-
+ onPreview() {
+ this.finialExperimentSpec = this.constructSpec();
+ }
/**
* Event handler for Next step/Submit button
*/
handleSubmit() {
if (this.mode === 'create') {
- const newSpec = this.constructSpec();
- this.experimentService.createExperiment(newSpec).subscribe({
+ this.experimentService.createExperiment(this.finialExperimentSpec).subscribe({
next: () => {},
error: (msg) => {
this.nzMessageService.error(`${msg}, please try again`, {
@@ -182,8 +210,7 @@ export class ExperimentCustomizedForm implements OnInit, OnDestroy {
}
});
} else if (this.mode === 'update') {
- const newSpec = this.constructSpec();
- this.experimentService.updateExperiment(this.targetId, newSpec).subscribe(
+ this.experimentService.updateExperiment(this.targetId, this.finialExperimentSpec).subscribe(
null,
(msg) => {
this.nzMessageService.error(`${msg}, please try again`, {
@@ -197,8 +224,7 @@ export class ExperimentCustomizedForm implements OnInit, OnDestroy {
}
);
} else if (this.mode === 'clone') {
- const newSpec = this.constructSpec();
- this.experimentService.createExperiment(newSpec).subscribe(
+ this.experimentService.createExperiment(this.finialExperimentSpec).subscribe(
null,
(msg) => {
this.nzMessageService.error(`${msg}, please try again`, {
@@ -231,10 +257,11 @@ export class ExperimentCustomizedForm implements OnInit, OnDestroy {
* Create a new spec
*/
createSpec(
- defaultName: string = '',
+ defaultName: string = 'Worker',
defaultReplica: number = 1,
defaultCpu: number = 1,
- defaultMemory: string = '',
+ defaultGpu: number = 0,
+ defaultMemory: number = 1024,
defaultUnit: string = 'M'
): FormGroup {
return new FormGroup(
@@ -242,6 +269,7 @@ export class ExperimentCustomizedForm implements OnInit, OnDestroy {
name: new FormControl(defaultName, [Validators.required]),
replicas: new FormControl(defaultReplica, [Validators.min(1), Validators.required]),
cpus: new FormControl(defaultCpu, [Validators.min(1), Validators.required]),
+ gpus: new FormControl(defaultGpu, [Validators.min(0), Validators.required]),
memory: new FormGroup(
{
num: new FormControl(defaultMemory, [Validators.required]),
@@ -283,10 +311,10 @@ export class ExperimentCustomizedForm implements OnInit, OnDestroy {
*/
constructSpec(): ExperimentSpec {
// Construct the spec
- const meta: SpecMeta = {
+ const meta: ExperimentMeta = {
name: this.experimentName.value,
namespace: this.namespace.value,
- framework: this.jobTypes,
+ framework: this.framework === 'Standalone' ? 'Tensorflow' : this.framework,
cmd: this.cmd.value,
envVars: {}
};
@@ -301,14 +329,14 @@ export class ExperimentCustomizedForm implements OnInit, OnDestroy {
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').get('num').value}${
- spec.get('memory').get('unit').value
- }`
+ resources: `cpu=${spec.get('cpus').value},nvidia.com/gpu=${spec.get('gpus').value},memory=${
+ spec.get('memory').get('num').value
+ }${spec.get('memory').get('unit').value}`
};
}
}
- const environment: SpecEnviroment = {
+ const environment: EnvironmentSpec = {
image: this.image.value
};
@@ -331,6 +359,10 @@ export class ExperimentCustomizedForm implements OnInit, OnDestroy {
arr.removeAt(index);
}
+ deleteAllItem(arr: FormArray) {
+ arr.clear();
+ }
+
updateExperimentInit() {
// Prevent user from modifying the name
this.experimentName.disable();
@@ -349,6 +381,7 @@ export class ExperimentCustomizedForm implements OnInit, OnDestroy {
const cloneExperimentName = spec.meta.name + '-' + id;
this.experimentName.setValue(cloneExperimentName.toLocaleLowerCase());
this.cloneExperiment(spec);
+ this.checkStatus();
}
cloneExperiment(spec: ExperimentSpec) {
@@ -361,8 +394,17 @@ export class ExperimentCustomizedForm implements OnInit, OnDestroy {
this.envs.push(env);
}
for (const [specName, info] of Object.entries(spec.spec)) {
- const [cpuCount, memory, unit] = info.resources.match(/\d+|[MG]/g);
- const newSpec = this.createSpec(specName, parseInt(info.replicas, 10), parseInt(cpuCount, 10), memory, unit);
+ const cpuCount = info.resourceMap.cpu;
+ const gpuCount = info.resourceMap.gpu === undefined ? '0' : '1';
+ const [memory, unit] = info.resourceMap.memory.match(/\d+|[MG]/g);
+ const newSpec = this.createSpec(
+ specName,
+ parseInt(info.replicas, 10),
+ parseInt(cpuCount, 10),
+ parseInt(gpuCount, 10),
+ parseInt(memory, 10),
+ unit
+ );
this.specs.push(newSpec);
}
}
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-info/experiment-info.component.html b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-info/experiment-info.component.html
index a076514..699523a 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-info/experiment-info.component.html
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-info/experiment-info.component.html
@@ -19,7 +19,7 @@
<nz-table
*ngIf="!isLoading"
- style="margin-top: 10px;"
+ style="margin-top: 10px"
id="experimentTable"
#basicTable
[nzData]="[experimentInfo]"
@@ -46,7 +46,9 @@
<td>{{ experimentInfo.runningTime | date: 'M/d/yyyy, h:mm a' }}</td>
<td>{{ experimentInfo.finishedTime | date: 'M/d/yyyy, h:mm a' }}</td>
<td>{{ experimentInfo.duration }}</td>
- <td>{{ experimentInfo.status }}</td>
+ <td>
+ <nz-tag [nzColor]="statusColor[experimentInfo.status]">{{ experimentInfo.status }}</nz-tag>
+ </td>
<td class="td-action">
<a (click)="startExperiment()">Start</a>
<nz-divider nzType="vertical"></nz-divider>
@@ -75,7 +77,7 @@
</tbody>
</nz-table>
<nz-spin *ngIf="isLoading"></nz-spin>
-<div style="background-color: white;">
+<div style="background-color: white">
<nz-select id="nzSelect_selectPod" [(ngModel)]="selectedPod">
<nz-option *ngFor="let pod of podNameArr" [nzValue]="pod" [nzLabel]="pod"></nz-option>
</nz-select>
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-info/experiment-info.component.ts b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-info/experiment-info.component.ts
index a31ae34..c00b2ee 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-info/experiment-info.component.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-info/experiment-info.component.ts
@@ -46,21 +46,28 @@ export class ExperimentInfoComponent implements OnInit {
private nzMessageService: NzMessageService
) {}
+ statusColor: { [key: string]: string } = {
+ Accepted: 'gold',
+ Created: 'white',
+ Running: 'green',
+ Succeeded: 'blue'
+ };
+
ngOnInit() {
this.experimentID = this.route.snapshot.params.id;
this.experimentService.querySpecificExperiment(this.experimentID).subscribe(
(item) => {
this.experimentInfo = item;
this.isLoading = false;
- if (this.experimentInfo.status == 'Succeeded') {
- var finTime = new Date(this.experimentInfo.finishedTime);
- var runTime = new Date(this.experimentInfo.runningTime);
- var result = (finTime.getTime() - runTime.getTime()) / 1000;
+ if (this.experimentInfo.status === 'Succeeded') {
+ const finTime = new Date(this.experimentInfo.finishedTime);
+ const runTime = new Date(this.experimentInfo.runningTime);
+ const result = (finTime.getTime() - runTime.getTime()) / 1000;
this.experimentInfo.duration = this.experimentService.durationHandle(result);
} else {
- var currentTime = new Date();
- var runTime = new Date(this.experimentInfo.runningTime);
- var result = (currentTime.getTime() - runTime.getTime()) / 1000;
+ const currentTime = new Date();
+ const runTime = new Date(this.experimentInfo.runningTime);
+ const result = (currentTime.getTime() - runTime.getTime()) / 1000;
this.experimentInfo.duration = this.experimentService.durationHandle(result);
}
},
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-info/outputs/outputs.component.html b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-info/outputs/outputs.component.html
index 4ee4d33..81889d8 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-info/outputs/outputs.component.html
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment-info/outputs/outputs.component.html
@@ -18,5 +18,5 @@
-->
<div id="showLogDiv">
- <p style="white-space: nowrap;" *ngFor="let log of podLog; let j = index">[{{ j }}] {{ log }}</p>
+ <p style="white-space: nowrap" *ngFor="let log of podLog; let j = index">[{{ j }}] {{ log }}</p>
</div>
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 30a750a..b23e2c4 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
@@ -17,7 +17,7 @@
~ under the License.
-->
-<nz-layout style="margin: -24px -24px 16px;">
+<nz-layout style="margin: -24px -24px 16px">
<nz-layout class="inner-layout">
<div id="experimentOuter">
<nz-breadcrumb>
@@ -49,7 +49,7 @@
</nz-radio-group>
<nz-input-group
nzSearch
- style="width: 300px; margin-top: 15px; margin-left: 10px; margin-right: 5px;"
+ style="width: 300px; margin-top: 15px; margin-left: 10px; margin-right: 5px"
[nzAddOnAfter]="suffixIconButton"
>
<input type="text" nz-input placeholder="input search text" />
@@ -62,7 +62,7 @@
nz-button
id="openExperiment"
nzType="primary"
- style="margin-right: 5px; margin-bottom: 15px; margin-top: 15px;"
+ style="margin-right: 5px; margin-bottom: 15px; margin-top: 15px"
(click)="initModal('create')"
>
<i nz-icon nzType="plus"></i>
@@ -71,7 +71,7 @@
<button
nz-button
nzType="primary"
- style="margin-bottom: 15px; margin-top: 15px;"
+ style="margin-bottom: 15px; margin-top: 15px"
nz-popconfirm
nzTitle="Confirm to delete?"
nzCancelText="Cancel"
@@ -170,15 +170,15 @@
>
{{ modalProps.okText }}
</button>
- <button *ngIf="modalProps.currentStep > 0" nz-button nzType="default" style="float: left;" (click)="prevForm()">
+ <button *ngIf="modalProps.currentStep > 0" nz-button nzType="default" style="float: left" (click)="prevForm()">
Prev Step
</button>
</div>
- <experiment-customized-form
+ <submarine-experiment-customized-form
[mode]="mode"
[targetId]="targetId"
[targetSpec]="targetSpec"
*ngIf="this.modalProps.formType === 'customized'"
- ></experiment-customized-form>
+ ></submarine-experiment-customized-form>
</nz-modal>
</nz-layout>
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 4732450..b81741f 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
@@ -20,11 +20,11 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, NavigationStart, Router } from '@angular/router';
import { ExperimentInfo } from '@submarine/interfaces/experiment-info';
-import { ExperimentService } from '@submarine/services/experiment.service';
-import { NzMessageService } from 'ng-zorro-antd';
import { ExperimentSpec } from '@submarine/interfaces/experiment-spec';
-import { ExperimentFormService } from '@submarine/services/experiment.form.service';
import { ModalProps } from '@submarine/interfaces/modal-props';
+import { ExperimentFormService } from '@submarine/services/experiment.form.service';
+import { ExperimentService } from '@submarine/services/experiment.service';
+import { NzMessageService } from 'ng-zorro-antd';
@Component({
selector: 'submarine-experiment',
@@ -114,7 +114,7 @@ export class ExperimentComponent implements OnInit {
this.modalProps.isVisible = true;
this.modalProps.formType = initFormType;
- if (initMode === 'update') {
+ if (initMode === 'update' || initMode === 'clone') {
// Keep id for later request
this.targetId = id;
this.targetSpec = spec;
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment.module.ts b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment.module.ts
index c5563d2..6db316a 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment.module.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/experiment/experiment.module.ts
@@ -1,17 +1,17 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { RouterModule } from '@angular/router';
+import { PipeSharedModule } from '@submarine/pipe/pipe-shared.module';
import { NgxChartsModule } from '@swimlane/ngx-charts';
import { NgZorroAntdModule } from 'ng-zorro-antd';
+import { ExperimentCustomizedFormComponent } from './experiment-customized-form/experiment-customized-form.component';
import { ChartsComponent } from './experiment-info/charts/charts.component';
import { ExperimentInfoComponent } from './experiment-info/experiment-info.component';
import { HyperParamsComponent } from './experiment-info/hyper-params/hyper-params.component';
import { MetricsComponent } from './experiment-info/metrics/metrics.component';
import { OutputsComponent } from './experiment-info/outputs/outputs.component';
-import { ExperimentCustomizedForm } from './experiment-customized-form/experiment-customized-form.component';
-import { PipeSharedModule } from '@submarine/pipe/pipe-shared.module';
import { ExperimentComponent } from './experiment.component';
-import { RouterModule } from '@angular/router';
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -49,7 +49,7 @@ import { RouterModule } from '@angular/router';
MetricsComponent,
ChartsComponent,
OutputsComponent,
- ExperimentCustomizedForm
+ ExperimentCustomizedFormComponent
]
})
export class ExperimentModule {}
diff --git a/submarine-workbench/workbench-web-ng/src/app/services/environment.service.ts b/submarine-workbench/workbench-web-ng/src/app/services/environment.service.ts
index 9a14d3d..07001fd 100644
--- a/submarine-workbench/workbench-web-ng/src/app/services/environment.service.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/services/environment.service.ts
@@ -22,9 +22,8 @@ import { Injectable } from '@angular/core';
import { Rest } from '@submarine/interfaces';
import { Environment } from '@submarine/interfaces/environment-info';
import { BaseApiService } from '@submarine/services/base-api.service';
-import { of, throwError, Observable } from 'rxjs';
-import { catchError, map, switchMap } from 'rxjs/operators';
-import { EnvironmentSpec } from '@submarine/interfaces/environment-spec';
+import { of, Observable } from 'rxjs';
+import { switchMap } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
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 d15ca2a..a2716fb 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
@@ -70,7 +70,9 @@ export class ExperimentValidatorService {
for (let i = 0; i < arr.length; i++) {
const nameControl = arr.controls[i].get(fieldName);
// We don't consider empty string
- if (!nameControl.value) continue;
+ if (!nameControl.value) {
+ continue;
+ }
if (duplicateSet.has(nameControl.value)) {
// Found duplicates, manually set errors on FormControl level
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@submarine.apache.org
For additional commands, e-mail: dev-help@submarine.apache.org