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/11 10:11:07 UTC

[submarine] branch master updated: SUBMARINE-607. [WEB] Create and Delete of notebook instances

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 e73377d  SUBMARINE-607. [WEB] Create and Delete of notebook instances
e73377d is described below

commit e73377d149754729e9613bab7d1f43cbd74cc2a1
Author: kobe860219 <ko...@gmail.com>
AuthorDate: Thu Sep 10 15:25:11 2020 +0800

    SUBMARINE-607. [WEB] Create and Delete of notebook instances
    
    ### What is this PR for?
    Make WEB UI support create/delete notebook instances.
    
    ### What type of PR is it?
    [ Feature]
    
    ### Todos
    
    ### What is the Jira issue?
    https://issues.apache.org/jira/browse/SUBMARINE-607
    
    ### How should this be tested?
    https://travis-ci.org/github/kobe860219/submarine/builds/724836645
    
    ### Screenshots (if appropriate)
    ![螢幕錄製 2020-09-07 下午4](https://user-images.githubusercontent.com/48027290/92365878-04840180-f127-11ea-9395-5c8f4e2369b7.gif)
    
    ![螢幕錄製 2020-09-10 下午3](https://user-images.githubusercontent.com/48027290/92694709-0560a180-f37a-11ea-9c21-462189be9545.gif)
    
    ### Questions:
    * Does the licenses files need update? No
    * Is there breaking changes for older versions? No
    * Does this needs documentation? No
    
    Author: kobe860219 <ko...@gmail.com>
    
    Closes #395 from kobe860219/SUBMARINE-607 and squashes the following commits:
    
    666e7ec [kobe860219] SUBMARINE-607. [WEB] Create and Delete of notebook instances
    1e14996 [kobe860219] [WEB] Create and Delete of notebook instances
    dc8a3b1 [kobe860219] [WEB] Create and Delete of notebook instances
    4f6ba3d [kobe860219] [WEB] Create and Delete of notebook instances
    43dd052 [kobe860219] [WEB] Create and Delete of notebook instances
    e4805b6 [kobe860219] Delete Done
---
 .../src/app/interfaces/notebook-spec.ts            |  12 +-
 .../workbench/notebook/notebook.component.html     | 121 +++++----
 .../workbench/notebook/notebook.component.scss     |  20 +-
 .../pages/workbench/notebook/notebook.component.ts | 279 +++++++++++++++++----
 .../pages/workbench/notebook/notebook.module.ts    |   4 +-
 .../src/app/services/notebook.service.ts           |  37 +++
 6 files changed, 368 insertions(+), 105 deletions(-)

diff --git a/submarine-workbench/workbench-web-ng/src/app/interfaces/notebook-spec.ts b/submarine-workbench/workbench-web-ng/src/app/interfaces/notebook-spec.ts
index 1648d2d..261e604 100644
--- a/submarine-workbench/workbench-web-ng/src/app/interfaces/notebook-spec.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/interfaces/notebook-spec.ts
@@ -31,14 +31,18 @@ export class Meta {
 export class Environment {
   name: string;
   dockerImage: string;
-  kernelSpec: string;
-  description: string;
+  kernelSpec: {
+    name: string;
+    channels: string[];
+    dependencies: string[];
+  };
+  description?: string;
   image: string;
 }
 
 export class Spec {
-  envVars: {
-    TEST_ENV: string;
+  envVars?: {
+    [key: string]: string;
   };
   resources: string;
 }
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/notebook/notebook.component.html b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/notebook/notebook.component.html
index cce4926..cdd0ed9 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/notebook/notebook.component.html
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/notebook/notebook.component.html
@@ -40,12 +40,8 @@
     <div nz-row>
       <div nz-col nzSpan="12">
         <label style="font-size: large; color: black;">Namespaces :</label>
-        <nz-select
-          style="margin-left: 5px; width: 240px;"
-          [(ngModel)]="currentNamespace"
-          (ngModelChange)="switchNamespace($event)"
-        >
-          <nz-option *ngFor="let namespace of namespacesList" [nzValue]="namespace" [nzLabel]="namespace"></nz-option>
+        <nz-select style="margin-left: 5px; width: 240px;" [(ngModel)]="currentNamespace">
+          <nz-option [nzValue]="currentNamespace" [nzLabel]="currentNamespace"></nz-option>
         </nz-select>
       </div>
       <div nz-col nzSpan="12" align="right">
@@ -55,37 +51,33 @@
         <ng-template #suffixIconButton>
           <button nz-button nzType="primary" nzSearch><i nz-icon nzType="search"></i></button>
         </ng-template>
-        <button id="btnNewNotebook" nz-button nzType="primary" style="margin-right: 5px;" (click)="isVisible = true">
+        <button
+          id="btnNewNotebook"
+          nz-button
+          nzType="primary"
+          style="margin-right: 5px;"
+          (click)="initNotebookStatus()"
+        >
           <i nz-icon nzType="plus"></i>
           New Notebook
         </button>
-        <button id="btnDelNotebook" nz-button nzType="primary">
-          <i nz-icon nzType="delete"></i>
-          Delete
-        </button>
       </div>
     </div>
     <div>
       <nz-table style="padding-top: 5px;" #basicTable [nzData]="notebookTable">
         <thead>
           <tr>
-            <th>
-              <label nz-checkbox [(ngModel)]="checked" (ngModelChange)="selectAllNotebook()"></label>
-            </th>
             <th>Name</th>
             <th>Environment</th>
             <th>Docker Image</th>
             <th>Resources</th>
             <th>Status</th>
-            <th>Environment</th>
+            <th>Action</th>
           </tr>
         </thead>
         <tbody>
           <tr *ngFor="let data of basicTable.data; let i = index">
             <td>
-              <label nz-checkbox [(ngModel)]="checkedList[i]"></label>
-            </td>
-            <td>
               <a>{{ data.name }}</a>
             </td>
             <td>{{ data.spec.environment.name }}</td>
@@ -94,7 +86,7 @@
               {{ data.spec.spec.resources }}
             </td>
             <td>{{ data.status }}</td>
-            <td>{{ data.spec.environment.name }}</td>
+            <td><a (click)="deleteNotebook(data.notebookId)">Delete</a></td>
           </tr>
         </tbody>
       </nz-table>
@@ -105,57 +97,100 @@
 <nz-modal
   [(nzVisible)]="isVisible"
   nzTitle="Create Notebook"
-  [(nzCancelText)]="cancelText"
   [(nzOkText)]="okText"
-  [nzOkLoading]="isOkLoading"
   (nzOnCancel)="isVisible = false"
-  (nzOnOk)="isVisible = false"
   [nzWidth]="700"
 >
-  <div>
-    <form [formGroup]="notebookForm">
+  <form [formGroup]="notebookForm">
+    <div *nzModalFooter>
+      <button nz-button nzType="default" (click)="isVisible = false">Cancel</button>
+      <button id="go" nz-button nzType="primary" [disabled]="checkStatus()" (click)="handleOk()">
+        Create
+      </button>
+    </div>
+    <div style="margin-top: 30px;">
       <div class="newNotebookForm">
         <label for="notebookName">
           <span class="red-star">*</span>
-          Notebook Name :
+          Notebook Name
         </label>
-        <input nz-input type="text" name="notebookName" id="notebookName" formControlName="notebookName" required />
+        <input nz-input required type="text" name="notebookName" id="notebookName" formControlName="notebookName" />
       </div>
       <div class="newNotebookForm">
-        <label for="namespaces">
+        <label for="namespace">
           <span class="red-star">*</span>
-          Namespaces :
+          Namespace
         </label>
-        <input nz-input type="text" name="namespaces" id="namespaces" formControlName="namespaces" />
+        <input
+          nz-input
+          [(ngModel)]="currentNamespace"
+          disabled="disabled"
+          name="namespace"
+          id="namespace"
+          formControlName="namespace"
+        />
       </div>
       <div class="newNotebookForm">
         <label for="environment">
           <span class="red-star">*</span>
-          Environment :
+          Environment
         </label>
-        <input nz-input type="text" name="environment" id="environment" formControlName="environment" />
+        <nz-select required name="select-envName" formControlName="envName">
+          <nz-option *ngFor="let env of envNameList" [nzValue]="env" [nzLabel]="env"></nz-option>
+        </nz-select>
       </div>
       <div class="newNotebookForm">
-        <label for="cpu">
+        <label for="cpus">
           <span class="red-star">*</span>
-          CPU :
+          CPU
         </label>
-        <input nz-input type="text" name="cpu" id="cpu" formControlName="cpu" />
+        <input nz-input required type="number" name="cpus" id="cpus" formControlName="cpus" />
       </div>
       <div class="newNotebookForm">
-        <label for="gpu">
-          <span class="red-star">*</span>
-          GPU :
+        <label for="gpus">
+          GPU
         </label>
-        <input nz-input type="text" name="gpu" id="gpu" formControlName="gpu" />
+        <input nz-input type="number" name="gpus" id="gpus" formControlName="gpus" />
       </div>
       <div class="newNotebookForm">
-        <label for="memory">
+        <label for="memoryNum">
           <span class="red-star">*</span>
-          Memory :
+          Memory
         </label>
-        <input nz-input type="text" name="memory" id="memory" formControlName="memory" />
+        <div class="memory-input-group">
+          <input nz-input required name="memoryNum" formControlName="memoryNum" />
+          <nz-select formControlName="unit">
+            <nz-option *ngFor="let unit of MEMORY_UNITS" [nzValue]="unit" [nzLabel]="unit"></nz-option>
+          </nz-select>
+        </div>
       </div>
-    </form>
-  </div>
+      <div formArrayName="envVars">
+        <ng-container *ngFor="let envVar of envVars.controls; index as i">
+          <div [formGroupName]="i" class="newNotebookForm">
+            <label for="envVar{{ i }}">EnvVar {{ i + 1 }}</label>
+            <div>
+              <input style="width: 30%;" nz-input name="key{{ i }}" placeholder="Key" formControlName="key" />
+              <input
+                style="width: 50%; margin-left: 10px;"
+                nz-input
+                name="value{{ i }}"
+                placeholder="Value"
+                formControlName="value"
+              />
+              <i
+                nz-icon
+                style="margin-left: 5px;"
+                nzType="close-circle"
+                nzTheme="fill"
+                (click)="deleteItem(envVars, i)"
+              ></i>
+            </div>
+          </div>
+        </ng-container>
+      </div>
+    </div>
+    <button nz-button style="display: block; margin: auto;" id="envVar-btn" type="default" (click)="onCreateEnvVar()">
+      Add new env
+    </button>
+  </form>
 </nz-modal>
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/notebook/notebook.component.scss b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/notebook/notebook.component.scss
index 201a781..68e727f 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/notebook/notebook.component.scss
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/notebook/notebook.component.scss
@@ -29,12 +29,13 @@
      padding: 10px;
      overflow: auto;
  }
+
  .red-star {
      color: red;
  }
 
  .newNotebookForm {
-    display: flex;
+   display: flex;
    align-items: center;
    margin-bottom: 1.5rem;
    & label {
@@ -48,4 +49,19 @@
    & input {
       flex: 0 0 48%;
    }
- }
\ No newline at end of file
+
+   & nz-select {
+      flex: 0 0 48%;
+   }
+ }
+
+ .memory-input-group {
+   display: flex;
+   & input {
+      width: 70%;
+   }
+
+   & > * {
+      width: 30%;
+   }
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/notebook/notebook.component.ts b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/notebook/notebook.component.ts
index c427ff1..6c4d5ca 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/notebook/notebook.component.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/notebook/notebook.component.ts
@@ -18,8 +18,11 @@
  */
 
 import { Component, OnInit } from '@angular/core';
-import { FormControl, FormGroup, Validators } from '@angular/forms';
+import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms';
 import { NotebookService } from '@submarine/services/notebook.service';
+import { NzMessageService } from 'ng-zorro-antd/message';
+import { EnvironmentService } from '@submarine/services/environment.service';
+import { ExperimentValidatorService } from '@submarine/services/experiment.validator.service';
 
 @Component({
   selector: 'submarine-notebook',
@@ -27,79 +30,101 @@ import { NotebookService } from '@submarine/services/notebook.service';
   styleUrls: ['./notebook.component.scss']
 })
 export class NotebookComponent implements OnInit {
-  // Checkbox
-  checkedList: boolean[] = [];
-  checked: boolean = false;
-
-  // New notebook modal
-  cancelText = 'Cancel';
-  okText = 'Create';
-  isVisible = false;
-
-  // New notebook(form)
-  notebookForm: FormGroup;
+  // Environment
+  envList;
+  envNameList = [];
 
   // Namesapces
-  namespacesList = [];
+  allNamespaceList = [];
   currentNamespace;
 
   // Notebook list
-  notebookList;
+  allNotebookList;
   notebookTable;
 
-  constructor(private notebookService: NotebookService) {}
+  // New Notebook Form
+  notebookForm: FormGroup;
+  isVisible = false;
+  MEMORY_UNITS = ['M', 'Gi'];
 
-  statusColor: { [key: string]: string } = {
-    Running: 'green',
-    Stop: 'blue'
-  };
+  constructor(
+    private notebookService: NotebookService,
+    private nzMessageService: NzMessageService,
+    private environmentService: EnvironmentService,
+    private experimentValidatorService: ExperimentValidatorService
+  ) {}
 
   ngOnInit() {
     this.notebookForm = new FormGroup({
-      notebookName: new FormControl(null, [Validators.required]),
-      namespaces: new FormControl(this.currentNamespace, [Validators.required]),
-      environment: new FormControl('env1', [Validators.required]),
-      cpu: new FormControl(null, [Validators.required]),
-      gpu: new FormControl(null, [Validators.required]),
-      memory: new FormControl(null, [Validators.required])
+      notebookName: new FormControl(null, Validators.required),
+      namespace: new FormControl(this.currentNamespace),
+      envName: new FormControl(null, Validators.required), // Environment
+      envVars: new FormArray([], [this.experimentValidatorService.nameValidatorFactory('key')]),
+      cpus: new FormControl(null, [Validators.min(1), Validators.required]),
+      gpus: new FormControl(null),
+      memoryNum: new FormControl(null, [Validators.required]),
+      unit: new FormControl(this.MEMORY_UNITS[0], [Validators.required])
     });
-
     this.fetchNotebookList();
+    this.fetchEnvList();
   }
 
-  fetchNotebookList() {
-    this.notebookService.fetchNotebookList().subscribe((list) => {
-      this.notebookList = list;
-      this.checkedList = [];
-      for (let i = 0; i < this.notebookList.length; i++) {
-        this.checkedList.push(false);
-      }
-      // Get namespaces
-      this.notebookList.forEach((element) => {
-        if (this.namespacesList.indexOf(element.spec.meta.namespace) < 0) {
-          this.namespacesList.push(element.spec.meta.namespace);
+  // Get all environment
+  fetchEnvList() {
+    this.environmentService.fetchEnvironmentList().subscribe((list) => {
+      this.envList = list;
+      this.envList.forEach((env) => {
+        if (this.envNameList.indexOf(env.environmentSpec.name) < 0) {
+          this.envNameList.push(env.environmentSpec.name);
         }
       });
-      // Set default namespace and table
-      this.currentNamespace = this.namespacesList[0];
+    });
+  }
+
+  // Get all notebooks, then set default namespace.
+  fetchNotebookList() {
+    this.notebookService.fetchNotebookList().subscribe((list) => {
+      this.allNotebookList = list;
+      this.currentNamespace = 'default';
       this.notebookTable = [];
-      this.notebookList.forEach((item) => {
+      this.allNotebookList.forEach((item) => {
         if (item.spec.meta.namespace == this.currentNamespace) {
           this.notebookTable.push(item);
         }
       });
+
+      // Get namespaces
+      //this.getAllNamespaces();
+
+      // Set default namespace and table
+      //this.setDefaultTable();
     });
   }
 
-  selectAllNotebook() {
-    for (let i = 0; i < this.checkedList.length; i++) {
-      this.checkedList[i] = this.checked;
-    }
+  // Future work. If we need a api for get all namespaces.
+  getAllNamespaces() {
+    this.allNotebookList.forEach((element) => {
+      if (this.allNamespaceList.indexOf(element.spec.meta.namespace) < 0) {
+        this.allNamespaceList.push(element.spec.meta.namespace);
+      }
+    });
+  }
+
+  // Future work. If we have a api for get all namespaces.
+  setDefaultTable() {
+    this.currentNamespace = this.allNamespaceList[0];
+    this.notebookTable = [];
+    this.allNotebookList.forEach((item) => {
+      if (item.spec.meta.namespace == this.currentNamespace) {
+        this.notebookTable.push(item);
+      }
+    });
   }
 
+  // Future work. If we have a api for get all namespaces.
   switchNamespace(namespace: string) {
     this.notebookTable = [];
-    this.notebookList.forEach((item) => {
+    this.allNotebookList.forEach((item) => {
       if (item.spec.meta.namespace == namespace) {
         this.notebookTable.push(item);
       }
@@ -107,19 +132,163 @@ export class NotebookComponent implements OnInit {
     console.log(this.notebookTable);
   }
 
-  // TODO(kobe860219): Make a notebook run
-  runNotebook(data) {
-    data.status = 'Running';
+  deleteNotebook(id: string) {
+    this.notebookService.deleteNotebook(id).subscribe(
+      () => {
+        this.nzMessageService.success('Delete Notebook Successfully!');
+        this.updateNotebookTable();
+      },
+      (err) => {
+        this.nzMessageService.error(err.message);
+      }
+    );
   }
 
-  // TODO(kobe860219): Stop a running notebook
-  stopNotebook(data) {
-    data.status = 'Stop';
+  // Create or Delete, then update Notebook Table
+  updateNotebookTable() {
+    this.notebookService.fetchNotebookList().subscribe((list) => {
+      this.allNotebookList = list;
+      this.notebookTable = [];
+      this.allNotebookList.forEach((item) => {
+        if (item.spec.meta.namespace == this.currentNamespace) {
+          this.notebookTable.push(item);
+        }
+      });
+    });
   }
 
-  // TODO(kobe860219): Create new notebook
-  createNotebook() {}
+  get notebookName() {
+    return this.notebookForm.get('notebookName');
+  }
+  get namespace() {
+    return this.notebookForm.get('namespace');
+  }
+  get envName() {
+    return this.notebookForm.get('envName');
+  }
+  get envVars() {
+    return this.notebookForm.get('envVars') as FormArray;
+  }
+  get cpus() {
+    return this.notebookForm.get('cpus');
+  }
+  get gpus() {
+    return this.notebookForm.get('gpus');
+  }
+  get memoryNum() {
+    return this.notebookForm.get('memoryNum');
+  }
+  get unit() {
+    return this.notebookForm.get('unit');
+  }
+
+  // Init form when click create-btn
+  initNotebookStatus() {
+    this.isVisible = true;
+    this.notebookName.reset();
+    this.envName.reset(this.envNameList[0]);
+    this.envVars.clear();
+    this.cpus.reset(1);
+    this.gpus.reset(0);
+    this.memoryNum.reset();
+    this.unit.reset(this.MEMORY_UNITS[0]);
+  }
+
+  // Check form
+  checkStatus() {
+    return (
+      this.notebookName.invalid ||
+      this.envName.invalid ||
+      this.cpus.invalid ||
+      this.gpus.invalid ||
+      this.memoryNum.invalid ||
+      this.envVars.invalid
+    );
+  }
+
+  // Submmit
+  handleOk() {
+    this.createNotebookSpec();
+  }
+
+  // EnvVars Form
+  createEnvVar(defaultKey: string = '', defaultValue: string = '') {
+    // Create a new FormGroup
+    return new FormGroup(
+      {
+        key: new FormControl(defaultKey, [Validators.required]),
+        value: new FormControl(defaultValue, [Validators.required])
+      },
+      [this.experimentValidatorService.envValidator]
+    );
+  }
 
-  // TODO(kobe860219): Delete notebook
-  deleteNotebook() {}
+  // EnvVars Form
+  onCreateEnvVar() {
+    const env = this.createEnvVar();
+    this.envVars.push(env);
+  }
+
+  // Delete item in EnvVars Form
+  deleteItem(arr: FormArray, index: number) {
+    arr.removeAt(index);
+  }
+
+  // Develope submmit spec
+  createNotebookSpec() {
+    // Check GPU, then develope resources spec
+    let resourceSpec;
+    if (this.notebookForm.get('gpus').value === 0) {
+      resourceSpec = `cpu=${this.notebookForm.get('cpus').value},memory=${this.notebookForm.get('memoryNum').value}${
+        this.notebookForm.get('unit').value
+      }`;
+    } else {
+      resourceSpec = `cpu=${this.notebookForm.get('cpus').value},gpu=${this.notebookForm.get('gpus').value},memory=${
+        this.notebookForm.get('memoryNum').value
+      }${this.notebookForm.get('unit').value}`;
+    }
+
+    // Develope submmit spec
+    const newNotebookSpec = {
+      meta: {
+        name: this.notebookForm.get('notebookName').value,
+        namespace: this.notebookForm.get('namespace').value
+      },
+      environment: {
+        name: this.notebookForm.get('envName').value
+      },
+      spec: {
+        envVars: {},
+        resources: resourceSpec
+      }
+    };
+
+    for (const envVar of this.envVars.controls) {
+      if (envVar.get('key').value) {
+        newNotebookSpec.spec.envVars[envVar.get('key').value] = envVar.get('value').value;
+      }
+    }
+
+    // Post
+    this.notebookService.createNotebook(newNotebookSpec).subscribe({
+      next: (result) => {
+        this.updateNotebookTable();
+      },
+      error: (msg) => {
+        this.nzMessageService.error(`${msg}, please try again`, {
+          nzPauseOnHover: true
+        });
+      },
+      complete: () => {
+        this.nzMessageService.success('Notebook creation succeeds');
+        this.isVisible = false;
+      }
+    });
+  }
+
+  // TODO(kobe860219): Make a notebook run
+  runNotebook() {}
+
+  // TODO(kobe860219): Stop a running notebook
+  stopNotebook() {}
 }
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/notebook/notebook.module.ts b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/notebook/notebook.module.ts
index 6f2c732..9a99d23 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/workbench/notebook/notebook.module.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/workbench/notebook/notebook.module.ts
@@ -23,9 +23,11 @@ import { NotebookComponent } from './notebook.component';
 import { NotebookRoutingModule } from './notebook-routing.module';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { NgZorroAntdModule } from 'ng-zorro-antd';
+import { PipeSharedModule } from '@submarine/pipe/pipe-shared.module';
 
 @NgModule({
+  exports: [ReactiveFormsModule],
   declarations: [NotebookComponent],
-  imports: [CommonModule, NotebookRoutingModule, FormsModule, ReactiveFormsModule, NgZorroAntdModule]
+  imports: [CommonModule, NotebookRoutingModule, FormsModule, ReactiveFormsModule, NgZorroAntdModule, PipeSharedModule]
 })
 export class NotebookModule {}
diff --git a/submarine-workbench/workbench-web-ng/src/app/services/notebook.service.ts b/submarine-workbench/workbench-web-ng/src/app/services/notebook.service.ts
index 3b54db9..1c7f409 100644
--- a/submarine-workbench/workbench-web-ng/src/app/services/notebook.service.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/services/notebook.service.ts
@@ -44,4 +44,41 @@ export class NotebookService {
       })
     );
   }
+
+  createNotebook(newNotebook: object): Observable<Notebook> {
+    const apiUrl = this.baseApi.getRestApi('/v1/notebook');
+    return this.httpClient.post<Rest<Notebook>>(apiUrl, newNotebook).pipe(
+      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 {
+          console.log(e);
+          if (e.status === 409) {
+            message = 'You might have a duplicate notebook name';
+          } else if (e.status >= 500) {
+            message = `${e.message}`;
+          } else {
+            message = e.error.message;
+          }
+        }
+        return throwError(message);
+      })
+    );
+  }
+
+  deleteNotebook(id: string): Observable<Notebook> {
+    const apiUrl = this.baseApi.getRestApi(`/v1/notebook/${id}`);
+    return this.httpClient.delete<Rest<Notebook>>(apiUrl).pipe(
+      switchMap((res) => {
+        if (res.success) {
+          return of(res.result);
+        } else {
+          throw this.baseApi.createRequestError(res.message, res.code, apiUrl, 'delete', id);
+        }
+      })
+    );
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@submarine.apache.org
For additional commands, e-mail: dev-help@submarine.apache.org