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/02/11 18:34:45 UTC

[submarine] branch master updated: SUBMARINE-360. [WEB] Implement the frontend of sign-up page with Angular

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 d67950a  SUBMARINE-360. [WEB] Implement the frontend of sign-up page with Angular
d67950a is described below

commit d67950a69d1756897d877e5c31c19a14ade79b41
Author: kevin85421 <b0...@ntu.edu.tw>
AuthorDate: Tue Feb 11 13:53:36 2020 +0800

    SUBMARINE-360. [WEB] Implement the frontend of sign-up page with Angular
    
    ### What is this PR for?
    Implement the frontend of the sign-up page with Angular
    
    Validators :
    1. Username: (1) empty (2) existed
    2. Email: (1) empty (2) existed
    3. Password: (1) empty (2) length
    4. Re-enter Password: (1) empty (2) must be the same with the Password
    5. Sign Up button: the button can be clicked only when the form is valid.
    
    ### What type of PR is it?
    [Feature]
    
    ### Todos
    the backend of the sign-up page
    
    ### What is the Jira issue?
    https://issues.apache.org/jira/projects/SUBMARINE/issues/SUBMARINE-360
    
    ### How should this be tested?
    https://travis-ci.org/kevin85421/hadoop-submarine?utm_medium=notification&utm_source=github_status
    
    ### Screenshots (if appropriate)
    ![ζˆͺεœ– 2020-02-11 δΈ‹εˆ2 24 53](https://user-images.githubusercontent.com/20109646/74214924-4b696000-4cda-11ea-8982-800ee0fe32ee.png)
    
    ### Questions:
    * Does the licenses files need an update? No
    * Are there breaking changes for older versions? No
    * Does this needs documentation? No
    
    Author: kevin85421 <b0...@ntu.edu.tw>
    
    Closes #182 from kevin85421/SUBMARINE-360 and squashes the following commits:
    
    3cd2145 [kevin85421] SUBMARINE-360. [WEB]Implement the frontend of sign-up page with Angular
---
 .../apache/submarine/integration/registerIT.java   | 111 +++++++++++++++++++++
 .../apache/submarine/integration/sidebarIT.java    |   8 ++
 .../src/app/pages/user/login/login.component.html  |   5 +-
 .../pages/user/register/register.component.html    |  89 ++++++++++++++++-
 .../pages/user/register/register.component.scss    |   7 ++
 .../app/pages/user/register/register.component.ts  |  76 +++++++++++++-
 6 files changed, 290 insertions(+), 6 deletions(-)

diff --git a/submarine-test/e2e/src/test/java/org/apache/submarine/integration/registerIT.java b/submarine-test/e2e/src/test/java/org/apache/submarine/integration/registerIT.java
new file mode 100644
index 0000000..1e3ec91
--- /dev/null
+++ b/submarine-test/e2e/src/test/java/org/apache/submarine/integration/registerIT.java
@@ -0,0 +1,111 @@
+/*
+ * 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;
+
+import org.apache.submarine.AbstractSubmarineIT;
+import org.apache.submarine.WebDriverManager;
+import org.openqa.selenium.By;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.testng.Assert;
+
+public class registerIT extends AbstractSubmarineIT {
+
+  public final static Logger LOG = LoggerFactory.getLogger(registerIT.class);
+
+  @BeforeClass
+  public static void startUp(){
+    driver =  WebDriverManager.getWebDriver();
+  }
+
+  @AfterClass
+  public static void tearDown(){
+    driver.quit();
+  }
+
+  @Test
+  public void registerFrontEndInvalidTest() throws Exception {
+    // Navigate from Login page to Registration page
+    LOG.info("Navigate from Login page to Registration page");
+    pollingWait(By.xpath("//a[contains(text(), \"Create an account!\")]"), MAX_BROWSER_TIMEOUT_SEC).click();
+    Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/user/register");
+
+    // Username test
+    //   Case1: empty username
+    pollingWait(By.cssSelector("input[formcontrolname='username']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys(" \b");
+    Assert.assertEquals( driver.findElements(By.xpath("//div[contains(text(), \"Enter your username!\")]")).size(), 1);
+    //   Case2: existed username
+    pollingWait(By.cssSelector("input[formcontrolname='username']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("test");
+    Assert.assertEquals( driver.findElements(By.xpath("//div[contains(text(), \"The username already exists!\")]")).size(), 1);
+
+    // Email test
+    //   Case1: empty email
+    pollingWait(By.cssSelector("input[formcontrolname='email']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys(" \b");
+    Assert.assertEquals( driver.findElements(By.xpath("//div[contains(text(), \"Type your email!\")]")).size(), 1);
+    //   Case2: existed email
+    String existedEmailTestCase = "test@gmail.com";
+    pollingWait(By.cssSelector("input[formcontrolname='email']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys(existedEmailTestCase);
+    Assert.assertEquals( driver.findElements(By.xpath("//div[contains(text(), \"The email is already used!\")]")).size(), 1); 
+    //   Case3: invalid email
+    String backspaceKeys = "";
+    for ( int i=0; i < (existedEmailTestCase.length() - existedEmailTestCase.indexOf("@")); i++) {
+        backspaceKeys += "\b";
+    };
+    pollingWait(By.cssSelector("input[formcontrolname='email']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys(backspaceKeys);
+    Assert.assertEquals( driver.findElements(By.xpath("//div[contains(text(), \"The email is invalid!\")]")).size(), 1); 
+    
+    // Password test
+    //   Case1: empty password
+    pollingWait(By.cssSelector("input[formcontrolname='password']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys(" \b");
+    Assert.assertEquals( driver.findElements(By.xpath("//div[contains(text(), \"Type your password!\")]")).size(), 1);
+    //   Case2: string length must be in 6 ~ 20 characters
+    pollingWait(By.cssSelector("input[formcontrolname='password']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("testtesttesttesttesttest"); // length = 24
+    Assert.assertEquals( driver.findElements(By.xpath("//div[contains(text(), \"Password's length must be in 6 ~ 20 characters.\")]")).size(), 1);
+    pollingWait(By.cssSelector("input[formcontrolname='password']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("\b\b\b\b\b\b\b\b\b\b\b\b"); // length = 12
+    Assert.assertEquals( driver.findElements(By.xpath("//div[contains(text(), \"Password's length must be in 6 ~ 20 characters.\")]")).size(), 0);
+
+    // Re-enter password test
+    //   Case1: empty re-enter password
+    pollingWait(By.cssSelector("input[formcontrolname='checkPassword']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys(" \b");
+    Assert.assertEquals( driver.findElements(By.xpath("//div[contains(text(), \"Type your password again!\")]")).size(), 1);
+    //   Case2: re-enter password != password    
+    pollingWait(By.cssSelector("input[formcontrolname='checkPassword']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("1234"); // "1234" != "testtesttest"
+    Assert.assertEquals( driver.findElements(By.xpath("//div[contains(text(), \"Passwords must match!\")]")).size(), 1);
+    pollingWait(By.xpath("//a[@href='/user/login']"), MAX_BROWSER_TIMEOUT_SEC).click();
+    Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/user/login"); 
+  }
+
+  @Test
+  public void registerFrontEndValidTest() throws Exception {
+    // Sign-Up successfully
+    pollingWait(By.xpath("//a[contains(text(), \"Create an account!\")]"), MAX_BROWSER_TIMEOUT_SEC).click();
+    Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/user/register");
+    pollingWait(By.cssSelector("input[formcontrolname='username']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("validusername");
+    pollingWait(By.cssSelector("input[formcontrolname='email']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("validemail@gmail.com");
+    pollingWait(By.cssSelector("input[formcontrolname='password']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("validpassword");
+    pollingWait(By.cssSelector("input[formcontrolname='checkPassword']"), MAX_BROWSER_TIMEOUT_SEC).sendKeys("validpassword");
+    pollingWait(By.cssSelector("label[formcontrolname='agree']"), MAX_BROWSER_TIMEOUT_SEC).click();
+    pollingWait(By.cssSelector("button[class='ant-btn ant-btn-primary ant-btn-block']"), MAX_BROWSER_TIMEOUT_SEC).click(); 
+    Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/user/login");
+  }
+}
diff --git a/submarine-test/e2e/src/test/java/org/apache/submarine/integration/sidebarIT.java b/submarine-test/e2e/src/test/java/org/apache/submarine/integration/sidebarIT.java
index daf44a3..dd66881 100644
--- a/submarine-test/e2e/src/test/java/org/apache/submarine/integration/sidebarIT.java
+++ b/submarine-test/e2e/src/test/java/org/apache/submarine/integration/sidebarIT.java
@@ -20,6 +20,8 @@ package org.apache.submarine.integration;
 import org.apache.submarine.AbstractSubmarineIT;
 import org.apache.submarine.WebDriverManager;
 import org.apache.submarine.SubmarineITUtils;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
 import org.openqa.selenium.By;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -66,7 +68,13 @@ public class sidebarIT extends AbstractSubmarineIT {
     pollingWait(By.xpath("//span[contains(text(), \"Manager\")]"), MAX_BROWSER_TIMEOUT_SEC).click();
     pollingWait(By.xpath("//a[@href='/workbench/manager/user']"), MAX_BROWSER_TIMEOUT_SEC).click();
     SubmarineITUtils.sleep( 5000, true);
+
+    // Lazy-loading
+    WebDriverWait wait = new WebDriverWait( driver, 15, 5000);
+    pollingWait(By.xpath("//a[@href='/workbench/manager/user']"), MAX_BROWSER_TIMEOUT_SEC).click();
+    wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//span[@class='ant-breadcrumb-link ng-star-inserted']")));
     Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/workbench/manager/user");
+
     pollingWait(By.xpath("//a[@href='/workbench/manager/data-dict']"), MAX_BROWSER_TIMEOUT_SEC).click();
     Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/workbench/manager/data-dict");
     pollingWait(By.xpath("//span[contains(text(), \"Home\")]"), MAX_BROWSER_TIMEOUT_SEC).click();
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/user/login/login.component.html b/submarine-workbench/workbench-web-ng/src/app/pages/user/login/login.component.html
index 294cc71..2f1804d 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/user/login/login.component.html
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/user/login/login.component.html
@@ -38,9 +38,8 @@
         <span>Remember me</span>
       </label>
       <a class="login-form-forgot" [routerLink]="['/user/register']">Forgot password</a>
-      <button nz-button class="login-form-button" [nzType]="'primary'">Log in</button>
-      Or
-      <a [routerLink]="['/user/register']">register account!</a>
+      <button nz-button class="login-form-button" [nzType]="'primary'">Sign In</button>
+      <span>New to Submarine? <a [routerLink]="['/user/register']">Create an account!</a></span>
     </nz-form-control>
   </nz-form-item>
 </form>
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.html b/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.html
index 8955bef..e974c77 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.html
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.html
@@ -17,4 +17,91 @@
   ~ under the License.
   -->
 
-<p>register works!</p>
+  <form nz-form [formGroup]="validateForm" (ngSubmit)="submitForm()" style="width: 550px;">
+    <nz-form-item>
+      <nz-form-control [nzSm]="14" [nzXs]="24" [nzErrorTip]="usernameTpl" [nzOffset]="5">
+        <nz-input-group nzPrefixIcon="user">
+          <input type="text" id ="username" nz-input formControlName="username" placeholder="Username" />
+        </nz-input-group>
+        <ng-template #usernameTpl let-control>
+          <ng-container *ngIf="control.hasError('required')">
+            Enter your username!
+          </ng-container>
+          <ng-container *ngIf="control.hasError('usernameIsExisted')">
+            The username already exists!
+          </ng-container>
+        </ng-template>
+      </nz-form-control>
+    </nz-form-item>
+    <nz-form-item>
+      <nz-form-control [nzSm]="14" [nzXs]="24" [nzErrorTip]="emailTpl" [nzOffset]="5">
+        <nz-input-group nzPrefixIcon="mail">
+          <input nz-input formControlName="email" id="email" placeholder="Email" />
+        </nz-input-group>
+        <ng-template #emailTpl let-control>
+          <ng-container *ngIf="control.hasError('required')">
+            Type your email!
+          </ng-container>
+          <ng-container *ngIf="control.hasError('emailIsExisted')">
+            The email is already used!
+          </ng-container>
+          <ng-container *ngIf="control.hasError('email')">
+            The email is invalid!
+          </ng-container>
+        </ng-template>
+      </nz-form-control>
+    </nz-form-item>
+    <nz-form-item>
+      <nz-form-control [nzSm]="14" [nzXs]="24" [nzErrorTip]="passwordTpl" [nzOffset]="5">
+        <nz-input-group nzPrefixIcon="lock">
+            <input 
+              type="password" 
+              nz-input 
+              formControlName="password" 
+              placeholder="Password"
+              (ngModelChange)="updateConfirmValidator()" 
+            />
+        </nz-input-group>
+        <ng-template #passwordTpl let-control>
+          <ng-container *ngIf="!control.hasError('validPasswordLength') && !control.hasError('required')">
+            Password's length must be in 6 ~ 20 characters.
+          </ng-container>
+          <ng-container *ngIf="control.hasError('required')">
+            Type your password!
+          </ng-container>
+        </ng-template>
+      </nz-form-control>
+    </nz-form-item>
+    <nz-form-item>
+      <nz-form-control [nzSm]="14" [nzXs]="24" [nzErrorTip]="errorTpl" [nzOffset]="5">
+        <nz-input-group nzPrefixIcon="lock">
+          <input nz-input type="password" formControlName="checkPassword" id="checkPassword" placeholder="Re-enter password"/>
+        </nz-input-group>
+        <ng-template #errorTpl let-control>
+          <ng-container *ngIf="control.hasError('required')">
+            Type your password again!
+          </ng-container>
+          <ng-container *ngIf="control.hasError('confirm')">
+            Passwords must match!
+          </ng-container>
+        </ng-template>
+      </nz-form-control>
+    </nz-form-item>
+    <nz-form-item nz-row class="register-area">
+      <nz-form-control [nzSpan]="14" [nzOffset]="5">
+        <label nz-checkbox formControlName="agree">
+          <span>I have read the <a>agreement</a></span>
+        </label>
+      </nz-form-control>
+    </nz-form-item>
+    <nz-form-item nz-row class="register-area">
+      <nz-form-control [nzSpan]="14" [nzOffset]="5">
+        <button nz-button nzType="primary" nzBlock [disabled]="!validateForm.valid">Sign Up</button>
+      </nz-form-control>
+    </nz-form-item>
+    <nz-form-item>
+      <nz-form-control [nzSpan]="14" [nzOffset]="5">
+        <span>Already have an account? <a [routerLink]="['/user/login']">Sign-In</a></span>
+      </nz-form-control>
+    </nz-form-item>
+  </form>
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.scss b/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.scss
index 510f082..7f267f9 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.scss
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.scss
@@ -17,3 +17,10 @@
  * under the License.
  */
 
+[nz-form] {
+    max-width: 600px;
+}
+
+.register-area {
+    margin-bottom: 8px;
+}
diff --git a/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.ts b/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.ts
index c8e9c62..1bd05cb 100644
--- a/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.ts
+++ b/submarine-workbench/workbench-web-ng/src/app/pages/user/register/register.component.ts
@@ -18,6 +18,8 @@
  */
 
 import { Component, OnInit } from '@angular/core';
+import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
+import { Router } from '@angular/router';
 
 @Component({
   selector: 'submarine-register',
@@ -25,7 +27,77 @@ import { Component, OnInit } from '@angular/core';
   styleUrls: ['./register.component.scss']
 })
 export class RegisterComponent implements OnInit {
-  constructor() {}
+  validateForm: FormGroup;
+  // TODO(kevin85421): the mock data must be removed in the future
+  existedUsernames = [ 'test', 'haha'];
+  existedEmails = ['test@gmail.com'];
 
-  ngOnInit() {}
+  constructor(private fb: FormBuilder, private router: Router) {}
+  ngOnInit(): void {
+    this.validateForm = this.fb.group({
+      username: [null, [Validators.required, this.checkExistedUsernames.bind(this)]],
+      email: [null, [Validators.email, Validators.required, this.checkExistedEmails.bind(this)]],
+      password: [null, [Validators.required, this.checkPasswordLength.bind(this)]],
+      checkPassword: [null, [Validators.required, this.confirmationValidator.bind(this)]],
+      agree: [false, this.agreeValidator.bind(this)]
+    });
+  }
+
+  submitForm(): void {
+    for (const i in this.validateForm.controls) {
+      this.validateForm.controls[i].markAsDirty();
+      this.validateForm.controls[i].updateValueAndValidity();
+    }
+    this.router.navigate(['/user/login']);
+    console.log("SubmitForm (Sign up Page)");
+  }
+
+  updateConfirmValidator(): void {
+    /** wait for refresh value */
+    Promise.resolve().then(() => this.validateForm.get('checkPassword').updateValueAndValidity());
+  }
+
+  // Re-enter password must be the same with the password
+  confirmationValidator = (control: FormControl): { [s: string]: boolean } => {
+    if (!control.value) {
+      return { required: true };
+    } else if (control.value !== this.validateForm.get('password').value) {
+      return { confirm: true, error: true };
+    }
+    return null;
+  };
+
+  // Agreement must be true
+  agreeValidator = (control: FormControl): { [s: string]: boolean } => {
+    if (control.value) {
+      return null;
+    } else {
+      return { agree: false };
+    }
+  }
+
+  // Username cannot be the same with existed usernames
+  checkExistedUsernames(control: FormControl): {[s: string]: boolean} {
+    if (this.existedUsernames.indexOf(control.value) !== -1) {
+      return { 'usernameIsExisted': true };
+    }
+    return null;
+  }
+
+  // Email cannot be the same with existed emails
+  checkExistedEmails(control: FormControl): {[s: string]: boolean} {
+    if (this.existedEmails.indexOf(control.value) !== -1) {
+      return { 'emailIsExisted': true };
+    }
+    return null;
+  }
+
+  // Password must be longer than 6 characters and shorter than 20 characters
+  checkPasswordLength(control: FormControl): {[s: string]: boolean} {
+    const controlValue = String(control.value);
+    if (controlValue.length < 6 || controlValue.length > 20) {
+      return { 'validPasswordLength': false };
+    }
+    return null;
+  }
 }


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