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