Skip to content

Commit

Permalink
added a validation layer on the client build config.
Browse files Browse the repository at this point in the history
  • Loading branch information
yghokim committed Aug 29, 2018
1 parent 08aac7e commit e7b43e9
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 96 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
<button mat-icon-button ngxClipboard matTooltip="Copy a shortened URL" (click)="onShortUrlClicked(null)">
<mat-icon class="material-icons">link</mat-icon>
</button>

<table class="table table-data">
<thead>
<tr class="singleline">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,66 +1,72 @@
<div class="config-row flex-container flex-align-items-center" *ngIf="variableName && config">
<div class="label">
<span>{{label}}</span>
<sup class="changed-mark" [style.visibility]="compareValues(originalValue, config[variableName])? 'hidden' : null">Edited</sup>
</div>
<ng-container [ngSwitch]="type">
<input *ngSwitchCase="'text'" type="text" class="text-type form-control" [placeholder]="hintText" [value]="config[variableName]==undefined? null : config[variableName]"
(input)="onTextChanged($event.target.value)">
<input *ngSwitchCase="'password'" type="password" class="text-type form-control" [placeholder]="hintText" [value]="config[variableName]==undefined? null : config[variableName]"
(input)="onTextChanged($event.target.value)">
<mat-slide-toggle *ngSwitchCase="'boolean'" [checked]="config[variableName]" (change)="config[variableName] = $event.checked"></mat-slide-toggle>
<div *ngSwitchCase="'jsonFile'" class="json-file-type flex-filler">
<div class="flex-container flex-align-items-center">
<button class="mat-button-small" *ngIf="config[variableName] && !compareValues(originalValue, config[variableName])" mat-button
(click)="rollback()">Discard change</button>
<div class="flex-filler"></div>
<div class="fileopen">
<label for="packageFileInput">
<small>Read from JSON File...</small>
</label>
<input id="packageFileInput" type="file" accept="application/json" (change)="onJsonFileChanged($event.target.files)">
</div>
</div>
<div class="validation-container" [class.invalid]="validationFailedMessage">

<textarea class="form-control" rows="5" *ngIf="config[variableName]" [disabled]="true">{{jsonObjToString(config[variableName])}}</textarea>
<div class="config-row flex-container flex-align-items-center" *ngIf="variableName && config">
<div class="label">
<span>{{label}}</span>
<sup class="changed-mark" [style.visibility]="compareValues(originalValue, config[variableName])? 'hidden' : null">Edited</sup>
</div>
<ng-container [ngSwitch]="type">
<input *ngSwitchCase="'text'" type="text" class="text-type form-control" [class.is-invalid]="validationFailedMessage!=null" [placeholder]="hintText" [value]="config[variableName]==undefined? null : config[variableName]"
(input)="onTextChanged($event.target.value)">
<input *ngSwitchCase="'password'" type="password" class="text-type form-control" [class.is-invalid]="validationFailedMessage!=null" [placeholder]="hintText" [value]="config[variableName]==undefined? null : config[variableName]"
(input)="onTextChanged($event.target.value)">
<mat-slide-toggle *ngSwitchCase="'boolean'" [checked]="config[variableName]" (change)="config[variableName] = $event.checked"></mat-slide-toggle>
<div *ngSwitchCase="'jsonFile'" class="json-file-type flex-filler">
<div class="flex-container flex-align-items-center">
<button class="mat-button-small" *ngIf="config[variableName] && !compareValues(originalValue, config[variableName])" mat-button
(click)="rollback()">Discard change</button>
<div class="flex-filler"></div>
<div class="fileopen">
<label for="packageFileInput">
<small>Read from JSON File...</small>
</label>
<input id="packageFileInput" type="file" accept="application/json" (change)="onJsonFileChanged($event.target.files)">
</div>
</div>

<div *ngSwitchCase="'jksFile'" class="jks-file-type flex-filler">
<div class="file-description small" *ngIf="originalValue">
<b>A kestore file is registered. original file hash:</b>
<span class="hash">
{{originalValue}}
</span>
<textarea class="form-control" [class.is-invalid]="validationFailedMessage!=null" rows="5" *ngIf="config[variableName]" [disabled]="true">{{jsonObjToString(config[variableName])}}</textarea>
</div>
<div class="flex-container flex-align-items-center">
<button class="mat-button-small" *ngIf="config[variableName] && !compareValues(originalValue, config[variableName])" mat-button
(click)="rollback()">Discard change</button>
<div class="flex-filler"></div>
<input class="text-right" id="android-keystore-input" type="file" accept=".jks" (change)="onAndroidKeystoreFileChanged($event.target.files)">

<div *ngSwitchCase="'jksFile'" class="jks-file-type flex-filler">
<div class="file-description small" *ngIf="originalValue">
<b>A kestore file is registered. original file hash:</b>
<span class="hash">
{{originalValue}}
</span>
</div>
<div class="flex-container flex-align-items-center">
<button class="mat-button-small" *ngIf="config[variableName] && !compareValues(originalValue, config[variableName])" mat-button
(click)="rollback()">Discard change</button>
<div class="flex-filler"></div>
<input class="text-right" id="android-keystore-input" type="file" accept=".jks" (change)="onAndroidKeystoreFileChanged($event.target.files)">
</div>
</div>
</div>

<div *ngSwitchCase="'sourceCode'" class="source-code-input">
<div class="flex-container">
<mat-form-field class="mat-form-no-vertical-padding">
<mat-select [value]="config[variableName]? config[variableName].sourceType : null" required="true" (selectionChange)="sourceCodeTypeChanged($event.value)">
<mat-option value="file">Upload source code as zip</mat-option>
<mat-option value="github">Github</mat-option>
</mat-select>
</mat-form-field>
<ng-container [ngSwitch]="config[variableName].sourceType">
<input *ngSwitchCase="'file'" class="text-right" id="sourcecode-zip-input" type="file" accept=".zip" (change)="onSourceCodeZipFileChanged($event.target.files)">
<div class="github-container" *ngSwitchCase="'github'">
<mat-checkbox class="small" [checked]="config[variableName].data !=null ? config[variableName].data.useOfficial : false" (change)="onSourceCodeUseOfficialChecked($event.checked)">Use the official repo</mat-checkbox>
<input *ngIf="config[variableName].data && config[variableName].data.useOfficial !== true" class="form-control" type="text"
placeholder="muclipse/omnitrack_android"
[value] = "(config[variableName].data || {'repository': ''}).repository || ''"
(input) = "onSourceCodeRepositoryChanged($event.target.value)"
>
<div *ngSwitchCase="'sourceCode'" class="source-code-input">
<div class="flex-container">
<mat-form-field class="mat-form-no-vertical-padding">
<mat-select [value]="config[variableName]? config[variableName].sourceType : null" required="true" (selectionChange)="sourceCodeTypeChanged($event.value)">
<mat-option value="file">Upload source code as zip</mat-option>
<mat-option value="github">Github</mat-option>
</mat-select>
</mat-form-field>
<ng-container [ngSwitch]="config[variableName].sourceType">
<input *ngSwitchCase="'file'" class="text-right" id="sourcecode-zip-input" type="file" accept=".zip" (change)="onSourceCodeZipFileChanged($event.target.files)">
<div class="github-container" *ngSwitchCase="'github'">
<mat-checkbox class="small" [checked]="config[variableName].data !=null ? config[variableName].data.useOfficial : false"
(change)="onSourceCodeUseOfficialChecked($event.checked)">Use the official repo</mat-checkbox>
<input *ngIf="config[variableName].data && config[variableName].data.useOfficial !== true" class="form-control" type="text"
placeholder="muclipse/omnitrack_android" [value]="(config[variableName].data || {'repository': ''}).repository || ''"
(input)="onSourceCodeRepositoryChanged($event.target.value)">

</div>
</ng-container>
</div>
</ng-container>
</div>
</div>
</div>
</ng-container>
</div>
</ng-container>
</div>
<div class="invalid-message small text-color-danger">
{{validationFailedMessage}}
</div>

</div>
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
#sourcecode-zip-input{
#sourcecode-zip-input {
margin-left: 0.5rem;
}

.github-container{
.github-container {
margin-left: 1rem;
}
}

.validation-container {
&.invalid {
margin-top: 4px;
margin-bottom: 4px;
background: #ffecec;
border-radius: 6px;
}

.invalid-message{
padding: 0.5rem;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export class ConfigVariableRowComponent implements OnInit {
@Input() variableName: string = null
@Input() hintText: string = null

@Input() validationFailedMessage: string = null

@Output() binaryFileChanged = new EventEmitter<File>()

constructor() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@
<app-config-variable-row label="App name" type="text" variableName="appName" hintText="Set app name to override 'OmniTrack'"
[config]="changedConfig" [originalValue]="originalConfig.appName"></app-config-variable-row>

<app-config-variable-row label="Package name" type="text" variableName="packageName" hintText="kr.ac.snu.hcil.omnitrack"
[config]="changedConfig" [originalValue]="originalConfig.packageName"></app-config-variable-row>
<app-config-variable-row label="Package name" type="text" variableName="packageName" hintText="An experiment-wise unique package address"
[config]="changedConfig" [originalValue]="originalConfig.packageName" [validationFailedMessage]="getValidationError('packageName')"></app-config-variable-row>


<app-config-variable-row label="Source code" type="sourceCode" variableName="sourceCode" hintText="Source code for app compilation"
[config]="changedConfig" [originalValue]="originalConfig.sourceCode" (binaryFileChanged)="localFiles['sourceCodeZip_' + platform] = $event"></app-config-variable-row>
[config]="changedConfig" [originalValue]="originalConfig.sourceCode" (binaryFileChanged)="localFiles['sourceCodeZip_' + platform] = $event" [validationFailedMessage]="getValidationError('sourceCode')"></app-config-variable-row>
</td>
</tr>
<tr>
Expand Down Expand Up @@ -83,20 +83,20 @@
</th>
<td class="main-settings-container">
<app-config-variable-row label="google-services.json" [originalValue]="originalConfig.credentials.googleServices" [config]="changedConfig.credentials"
variableName="googleServices" type="jsonFile"></app-config-variable-row>
variableName="googleServices" type="jsonFile" [validationFailedMessage]="getValidationError('credentials.googleServices')"></app-config-variable-row>

<app-config-variable-row label="Keystore file (.jks)" [originalValue]="originalConfig.credentials.keystoreFileHash" [config]="changedConfig.credentials"
(binaryFileChanged)="localFiles.androidKeystore = $event" variableName="keystoreFileHash" type="jksFile"></app-config-variable-row>
(binaryFileChanged)="localFiles.androidKeystore = $event" variableName="keystoreFileHash" type="jksFile" [validationFailedMessage]="getValidationError('credentials.keystoreFileHash')"></app-config-variable-row>

<app-config-variable-row label="Keystore password" [originalValue]="originalConfig.credentials.keystorePassword" [config]="changedConfig.credentials"
hintText="Password for the keystore" variableName="keystorePassword" type="password"></app-config-variable-row>
hintText="Password for the keystore" variableName="keystorePassword" type="password" [validationFailedMessage]="getValidationError('credentials.keystorePassword')"></app-config-variable-row>

<app-config-variable-row label="Keypair alias" [originalValue]="originalConfig.credentials.keystoreAlias" [config]="changedConfig.credentials"
hintText="Alias of keypair to use" variableName="keystoreAlias" type="text"></app-config-variable-row>
hintText="Alias of keypair to use" variableName="keystoreAlias" type="text" [validationFailedMessage]="getValidationError('credentials.kestoreAlias')"></app-config-variable-row>


<app-config-variable-row label="Keypair password" [originalValue]="originalConfig.credentials.keystoreKeyPassword" hintText="Password for the designated keypair"
[config]="changedConfig.credentials" variableName="keystoreKeyPassword" type="password"></app-config-variable-row>
[config]="changedConfig.credentials" variableName="keystoreKeyPassword" type="password" [validationFailedMessage]="getValidationError('credentials.keystorePairPassword')"></app-config-variable-row>
</td>
</tr>
</ng-container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ mat-expansion-panel-header {
min-height: 46px;
justify-content: space-between;
&:hover {
background: #fafafa;
background: rgba(0,0,0,0.07);
}
.label {
color: #757575;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import deepEqual from 'deep-equal';
import { IClientBuildConfigBase, APP_THIRD_PARTY_KEYSTORE_KEYS } from '../../../../omnitrack/core/research/db-entity-types';
import { MatDialog } from '@angular/material/dialog';
import { YesNoDialogComponent } from '../../dialogs/yes-no-dialog/yes-no-dialog.component';
import { validateBuildConfig } from '../../../../omnitrack/core/research/build-config-utils';
import { NotificationService } from '../../services/notification.service';

@Component({
selector: 'app-platform-config-panel',
Expand Down Expand Up @@ -36,6 +38,8 @@ export class PlatformConfigPanelComponent implements OnInit, OnDestroy {
public isLoadingBinaries = true
public binaries: Array<any>

public validationErrors: Array<{ key: string, message: string }>

@Input("platform") set _platform(platform: string) {
if (this.platform !== platform) {
this.platform = platform
Expand Down Expand Up @@ -74,7 +78,7 @@ export class PlatformConfigPanelComponent implements OnInit, OnDestroy {
}
}

constructor(private api: ResearchApiService, public clientBuildService: ClientBuildService, private dialog: MatDialog) { }
constructor(private api: ResearchApiService, public clientBuildService: ClientBuildService, private dialog: MatDialog, private notificationService: NotificationService) { }

ngOnInit() {
}
Expand Down Expand Up @@ -124,7 +128,9 @@ export class PlatformConfigPanelComponent implements OnInit, OnDestroy {
this.isLoading = true
this._internalSubscriptions.add(
this.clientBuildService.initializePlatformDefault(this.platform).subscribe(

() => {
this.validateConfig()
}
)
)
}
Expand All @@ -133,14 +139,15 @@ export class PlatformConfigPanelComponent implements OnInit, OnDestroy {
this.localFiles = {}
this.changedConfig = deepclone(this.originalConfig)
$('input[type="file"]').val('')
this.validateConfig()
}

onSaveClicked() {
this.isLoading = true
this._internalSubscriptions.add(
this.applyChanges().subscribe(
() => {

this.validateConfig()
},
err => {

Expand Down Expand Up @@ -243,6 +250,23 @@ export class PlatformConfigPanelComponent implements OnInit, OnDestroy {
fileReader.readAsText(files[0])
}

validateConfig(): boolean {
const errors = validateBuildConfig(this.originalConfig)
this.validationErrors = errors
return !errors || errors.length === 0
}

getValidationError(key: string): string {
if (this.validationErrors) {
const match = this.validationErrors.find(v => v.key === key)
if (match) {
return match.message
} else return null
} else {
return null
}
}

onStartBuildClicked() {
if (this.isBuilding === false) {
this.isLoading = true
Expand All @@ -253,7 +277,13 @@ export class PlatformConfigPanelComponent implements OnInit, OnDestroy {
return this.applyChanges()
} else { return of(null) }
}).pipe(
flatMap(() => this.clientBuildService.startBuild(this.originalConfig))
flatMap(() => {
if (this.validateConfig() === true) {
return this.clientBuildService.startBuild(this.originalConfig)
} else {
throw { code: "ValidationFailed" }
}
})
).subscribe(
() => {
this.isBuilding = true
Expand All @@ -280,6 +310,12 @@ export class PlatformConfigPanelComponent implements OnInit, OnDestroy {
})
).subscribe(() => { }, () => { }, () => { this.isLoading = false })
)
} else if (err.code === "ValidationFailed") {
console.log(this.validationErrors)
this.notificationService.pushSnackBarMessage({
message: "Configuration is not valid or incomplete."
})
this.isLoading = false
}
},
() => {
Expand Down
Loading

0 comments on commit e7b43e9

Please sign in to comment.