You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@submarine.apache.org by ji...@apache.org on 2021/01/02 14:32:31 UTC

[submarine] branch master updated: SUBMARINE-693. Sync the status of notebook on workbench web

This is an automated email from the ASF dual-hosted git repository.

jiwq 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 2aaecb8  SUBMARINE-693. Sync the status of notebook on workbench web
2aaecb8 is described below

commit 2aaecb89be66d26f042649df76c970f1d7f257c4
Author: kobe860219 <ko...@gmail.com>
AuthorDate: Mon Dec 28 13:44:12 2020 +0800

    SUBMARINE-693. Sync the status of notebook on workbench web
    
    ### What is this PR for?
    This improvement make workbench web could sync status of notebooks. It's make UI more friendly for users.
    The pr is related of [SUBMARINE-675](https://github.com/apache/submarine/commit/d104080a926d30bcc45ccd740f9489dd7365d0b5)
    
    ### What type of PR is it?
    [Improvement]
    
    ### Todos
    * [ ] - Task
    
    ### What is the Jira issue?
    [SUBMARINE-693](https://issues.apache.org/jira/browse/SUBMARINE-693)
    
    ### How should this be tested?
    [Travis CI](https://travis-ci.org/github/kobe860219/submarine/builds/751380815)
    
    ### Screenshots (if appropriate)
    ![Sync status of notebook on web](https://user-images.githubusercontent.com/48027290/103102477-2ada9800-4657-11eb-8f4d-97df393bf1fa.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 #482 from kobe860219/SUBMARINE-693 and squashes the following commits:
    
    0f60967 [kobe860219] SUBMARINE-693. Sync the status of notebook on workbench web
    593d8ac [kobe860219] SUBMARINE-693. Sync the status of notebook on workbench web
    95d0fc9 [kobe860219] SUBMARINE-693. Sync the status of notebook on workbench web
    fc15452 [kobe860219] SUBMARINE-693. Sync the status of notebook on workbench web
    0ca618b [kobe860219] SUBMARINE-693. Sync the status of notebook on workbench web
    787ce20 [kobe860219] SUBMARINE-693. Sync the status of notebook on workbench web
---
 .../workbench/notebook/notebook.component.html     |  30 +++++-
 .../pages/workbench/notebook/notebook.component.ts |  44 +++++++--
 .../workbench-web/src/app/services/polling.ts      | 105 +++++++++++++++++++++
 3 files changed, 170 insertions(+), 9 deletions(-)

diff --git a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.html b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.html
index 36f7ef5..9e7d814 100644
--- a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.html
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.html
@@ -71,6 +71,7 @@
       <nz-table id="notebookListTable" style="padding-top: 5px;" #basicTable [nzData]="allNotebookList">
         <thead>
           <tr>
+            <th></th>
             <th>Name</th>
             <th>Environment</th>
             <th>Docker Image</th>
@@ -81,16 +82,39 @@
         </thead>
         <tbody>
           <tr *ngFor="let data of basicTable.data; let i = index">
-            <td>
+            <td *ngIf="data.status === 'running'">
+              <i
+                nz-icon
+                [nzType]="'check-circle'" 
+                [nzTheme]="'twotone'" 
+                [nzTwotoneColor]="'#52c41a'"
+                style="color: #08c; font-size: 24px;">
+              </i>
+            </td>
+            <td *ngIf="data.status !== 'running'">
+              <i 
+                nz-icon 
+                nzType="loading" 
+                nzTheme="outline" 
+                style="color: #08c; font-size: 24px;">
+              </i>
+            </td>
+            <td *ngIf="data.status === 'running'">
               <a href="{{ data.url }}" target="_blank">{{ data.name }}</a>
             </td>
+            <td *ngIf="data.status !== 'running'">
+              {{ data.name }}
+            </td>
             <td>{{ data.spec.environment.name }}</td>
             <td>{{ data.spec.environment.dockerImage }}</td>
             <td>
               {{ data.spec.spec.resources }}
             </td>
-            <td>{{ data.status }}</td>
-            <td><a id="delete{{ i }}" (click)="deleteNotebook(data.notebookId)">Delete</a></td>
+            <td>
+              <a (click)=showReason(data.reason)>{{ data.status }}</a>
+            </td>
+            <td>
+              <a id="delete{{ i }}" (click)="deleteNotebook(data.notebookId)">Delete</a></td>
           </tr>
         </tbody>
       </nz-table>
diff --git a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.ts b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.ts
index b271c88..e0d1533 100644
--- a/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.ts
+++ b/submarine-workbench/workbench-web/src/app/pages/workbench/notebook/notebook.component.ts
@@ -25,6 +25,10 @@ import { EnvironmentService } from '@submarine/services/environment.service';
 import { ExperimentValidatorService } from '@submarine/services/experiment.validator.service';
 import { UserService } from '@submarine/services/user.service';
 import { nullSafeIsEquivalent } from '@angular/compiler/src/output/output_ast';
+import { Subscription } from 'rxjs';
+import { ExponentialBackoff } from '@submarine/services/polling';
+import { isEqual } from "lodash";
+import { NzNotificationService } from 'ng-zorro-antd/notification';
 
 @Component({
   selector: 'submarine-notebook',
@@ -50,22 +54,40 @@ export class NotebookComponent implements OnInit {
   isVisible = false;
   MEMORY_UNITS = ['M', 'Gi'];
 
+  // User Information
   userId;
 
+  // Sync //
+  // Subscription
+  subscriptions = new Subscription();
+  // Poller
+  poller: ExponentialBackoff;
+
   constructor(
     private notebookService: NotebookService,
     private nzMessageService: NzMessageService,
     private environmentService: EnvironmentService,
     private experimentValidatorService: ExperimentValidatorService,
     private userService: UserService,
-    private fb: FormBuilder
+    private fb: FormBuilder,
+    private nzNotificationService: NzNotificationService
   ) {}
 
   ngOnInit() {
-    this.userService.fetchUserInfo().subscribe((res) => {
-      this.userId = res.id;
-      this.fetchNotebookList(this.userId);
+    this.poller = new ExponentialBackoff({ interval: 1000, retries: 3 });
+    const resourcesSub = this.poller.start().subscribe(() => {
+      this.userService.fetchUserInfo().subscribe((res) => {
+        this.userId = res.id;
+        this.notebookService.fetchNotebookList(this.userId).subscribe(resources => {
+          if (!isEqual(this.allNotebookList, resources)) {
+            this.allNotebookList = resources;
+            this.poller.reset();
+          }
+        });
+      });
     });
+    
+    this.subscriptions.add(resourcesSub);
 
     this.notebookForm = this.fb.group({
       notebookName: [null, [
@@ -82,6 +104,10 @@ export class NotebookComponent implements OnInit {
     this.fetchEnvList();
   }
 
+  ngOnDestroy() {
+    this.subscriptions.unsubscribe();
+  }
+
   // Get all environment
   fetchEnvList() {
     this.environmentService.fetchEnvironmentList().subscribe((list) => {
@@ -99,6 +125,7 @@ export class NotebookComponent implements OnInit {
   fetchNotebookList(id: string) {
     this.notebookService.fetchNotebookList(id).subscribe((list) => {
       this.allNotebookList = list;
+      console.log(this.allNotebookList);
     });
   }
 
@@ -139,7 +166,6 @@ export class NotebookComponent implements OnInit {
   deleteNotebook(id: string) {
     this.notebookService.deleteNotebook(id).subscribe(
       () => {
-        this.nzMessageService.success('Delete Notebook Successfully!');
         this.updateNotebookTable(this.userId);
       },
       (err) => {
@@ -283,12 +309,18 @@ export class NotebookComponent implements OnInit {
         });
       },
       complete: () => {
-        this.nzMessageService.success('Notebook creation succeeds');
         this.isVisible = false;
       }
     });
   }
 
+  showReason(reason: string) {
+    this.nzNotificationService.blank(
+      'Notebook Status',
+      reason
+      );
+  }
+
   // TODO(kobe860219): Make a notebook run
   runNotebook() {}
 
diff --git a/submarine-workbench/workbench-web/src/app/services/polling.ts b/submarine-workbench/workbench-web/src/app/services/polling.ts
new file mode 100644
index 0000000..1a9a4a4
--- /dev/null
+++ b/submarine-workbench/workbench-web/src/app/services/polling.ts
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ */
+
+ import { Subscription, Subject, interval, timer } from "rxjs";
+
+ export interface BackoffConfig {
+     retries?: number;
+     interval?: number;
+     maxInterval?: number;
+ }
+
+ const defaultConfig: BackoffConfig = {
+     retries: 1,
+     interval: 1000,
+     maxInterval: 16000
+ }
+
+ export class ExponentialBackoff {
+     private retries: number;
+     private interval: number;
+     private maxInterval: number;
+
+     private scheduler: Subscription;
+     private poller: Subject<number>;
+     private n: number;
+     
+     private remainingTries: number;
+     private currInterval: number;
+
+     constructor(config: BackoffConfig = defaultConfig) {
+         const conf = { ...defaultConfig, ...config };
+
+         this.retries = conf.retries;
+         this.interval = conf.interval;
+         this.maxInterval = conf.maxInterval;
+
+         this.poller = new Subject<number>();
+
+         this.n = 0;
+         this.remainingTries = this.retries + 1;
+         this.currInterval = this.interval;
+     }
+
+     public start() {
+         if(this.scheduler) {
+             this.scheduler.unsubscribe();
+         }
+
+         this.scheduler = timer(0, this.interval).subscribe(() => {
+             this.iterate();
+         });
+
+         return this.poller;
+     }
+
+     private iterate() {
+        this.n++;
+        this.poller.next(this.n);
+
+        this.scheduler.unsubscribe();
+        this.remainingTries--;
+        if (this.remainingTries === 0) {
+            this.remainingTries = this.retries;
+            this.currInterval = Math.min(this.currInterval * 2, this.maxInterval);
+        }
+
+        this.scheduler = interval(this.currInterval).subscribe(() => {
+            this.iterate();
+        });
+    }
+
+    public reset() {
+        this.n = 0;
+        this.currInterval = this.interval;
+        this.remainingTries = this.retries + 1;
+
+        this.start();
+    }
+
+    public stop() {
+        if (this.scheduler) {
+            this.scheduler.unsubscribe();
+        }
+    }
+
+    public getPoller() {
+        return this.poller;
+    }
+}


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