[GRIFFIN-164][GRIFFIN-186][GRIFFIN-187] Profiling Re-factor + Regex/Empty String Support
We've been working away on Griffin here at Credit Karma, and we'd love to contribute back! This PR tackles three separate tasks: - [GRIFFIN-164](https://issues.apache.org/jira/browse/GRIFFIN-164): Regex Support - [GRIFFIN-186](https://issues.apache.org/jira/browse/GRIFFIN-186): Create Profiling Measure Re-Factor - [GRIFFIN-187](https://issues.apache.org/jira/browse/GRIFFIN-187): Empty String Support The details for each of these tasks can be found in the JIRA tickets linked above! Author: Spencer Hivert <spencer.hiv...@creditkarma.com> Closes #381 from spencer-hivert-ck/shivert/profiling-refactor-and-regex-support. Project: http://git-wip-us.apache.org/repos/asf/incubator-griffin/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-griffin/commit/d58bd194 Tree: http://git-wip-us.apache.org/repos/asf/incubator-griffin/tree/d58bd194 Diff: http://git-wip-us.apache.org/repos/asf/incubator-griffin/diff/d58bd194 Branch: refs/heads/master Commit: d58bd194f646e2f0ef3a5c4259b0195a759909f5 Parents: 1c39b83 Author: Spencer Hivert <spencer.hiv...@creditkarma.com> Authored: Fri Aug 3 15:35:41 2018 +0800 Committer: William Guo <gu...@apache.org> Committed: Fri Aug 3 15:35:41 2018 +0800 ---------------------------------------------------------------------- .../step/builder/dsl/expr/LogicalExpr.scala | 19 + .../step/builder/dsl/parser/BasicParser.scala | 8 +- ui/angular/src/app/app.module.ts | 10 + .../measure/create-measure/ac/ac.component.css | 12 +- .../configuration/configuration.component.css | 4 + .../configuration/configuration.component.html | 6 +- .../pr/confirmModal/confirmModal.component.css | 24 + .../pr/confirmModal/confirmModal.component.html | 128 +++ .../confirmModal/confirmModal.component.spec.ts | 43 + .../pr/confirmModal/confirmModal.component.ts | 46 ++ .../measure/create-measure/pr/pr.component.css | 137 +--- .../measure/create-measure/pr/pr.component.html | 366 +-------- .../measure/create-measure/pr/pr.component.ts | 792 +++++++------------ .../create-measure/pr/step1/step1.component.css | 141 ++++ .../pr/step1/step1.component.html | 78 ++ .../pr/step1/step1.component.spec.ts | 43 + .../create-measure/pr/step1/step1.component.ts | 236 ++++++ .../create-measure/pr/step2/step2.component.css | 55 ++ .../pr/step2/step2.component.html | 80 ++ .../pr/step2/step2.component.spec.ts | 43 + .../create-measure/pr/step2/step2.component.ts | 77 ++ .../create-measure/pr/step3/step3.component.css | 37 + .../pr/step3/step3.component.html | 48 ++ .../pr/step3/step3.component.spec.ts | 43 + .../create-measure/pr/step3/step3.component.ts | 57 ++ .../create-measure/pr/step4/step4.component.css | 42 + .../pr/step4/step4.component.html | 103 +++ .../pr/step4/step4.component.spec.ts | 43 + .../create-measure/pr/step4/step4.component.ts | 59 ++ .../create-measure/pub/pub.component.css | 1 - .../src/app/measure/measure.component.css | 7 +- ui/angular/src/styles.css | 17 +- 32 files changed, 1796 insertions(+), 1009 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/d58bd194/measure/src/main/scala/org/apache/griffin/measure/step/builder/dsl/expr/LogicalExpr.scala ---------------------------------------------------------------------- diff --git a/measure/src/main/scala/org/apache/griffin/measure/step/builder/dsl/expr/LogicalExpr.scala b/measure/src/main/scala/org/apache/griffin/measure/step/builder/dsl/expr/LogicalExpr.scala index af1223f..13317bb 100644 --- a/measure/src/main/scala/org/apache/griffin/measure/step/builder/dsl/expr/LogicalExpr.scala +++ b/measure/src/main/scala/org/apache/griffin/measure/step/builder/dsl/expr/LogicalExpr.scala @@ -86,6 +86,25 @@ case class LikeExpr(head: Expr, is: Boolean, value: Expr) extends LogicalExpr { } } +case class RLikeExpr(head: Expr, is: Boolean, value: Expr) extends LogicalExpr { + + addChildren(head :: value :: Nil) + + def desc: String = { + val notStr = if (is) "" else " NOT" + s"${head.desc}${notStr} RLIKE ${value.desc}" + } + def coalesceDesc: String = { + val notStr = if (is) "" else " NOT" + s"${head.coalesceDesc}${notStr} RLIKE ${value.coalesceDesc}" + } + + override def map(func: (Expr) => Expr): RLikeExpr = { + RLikeExpr(func(head), is, func(value)) + } +} + + case class IsNullExpr(head: Expr, is: Boolean) extends LogicalExpr { addChild(head) http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/d58bd194/measure/src/main/scala/org/apache/griffin/measure/step/builder/dsl/parser/BasicParser.scala ---------------------------------------------------------------------- diff --git a/measure/src/main/scala/org/apache/griffin/measure/step/builder/dsl/parser/BasicParser.scala b/measure/src/main/scala/org/apache/griffin/measure/step/builder/dsl/parser/BasicParser.scala index ba6ac2c..18f7754 100644 --- a/measure/src/main/scala/org/apache/griffin/measure/step/builder/dsl/parser/BasicParser.scala +++ b/measure/src/main/scala/org/apache/griffin/measure/step/builder/dsl/parser/BasicParser.scala @@ -72,6 +72,7 @@ trait BasicParser extends JavaTokenParsers with Serializable { * <between-expr> ::= <math-expr> [<not>]? <between> (<math-expr> <and> <math-expr> | <range-expr>) * <range-expr> ::= "(" [<math-expr>]? [, <math-expr>]+ ")" * <like-expr> ::= <math-expr> [<not>]? <like> <math-expr> + * <rlike-expr> ::= <math-expr> [<not>]? <rlike> <math-expr> * <is-null-expr> ::= <math-expr> <is> [<not>]? <null> * <is-nan-expr> ::= <math-expr> <is> [<not>]? <nan> * @@ -131,6 +132,7 @@ trait BasicParser extends JavaTokenParsers with Serializable { val AND_ONLY: Parser[String] = """(?i)and\s""".r val IS: Parser[String] = """(?i)is\s""".r val LIKE: Parser[String] = """(?i)like\s""".r + val RLIKE: Parser[String] = """(?i)rlike\s""".r val COMPARE: Parser[String] = "=" | "!=" | "<>" | "<=" | ">=" | "<" | ">" val LOGICAL_UNARY: Parser[String] = NOT val LOGICAL_BINARIES: Seq[Parser[String]] = Seq((COMPARE), (AND), (OR)) @@ -276,6 +278,7 @@ trait BasicParser extends JavaTokenParsers with Serializable { * <between-expr> ::= <math-expr> [<not>]? <between> (<math-expr> <and> <math-expr> | <range-expr>) * <range-expr> ::= "(" [<math-expr>]? [, <math-expr>]+ ")" * <like-expr> ::= <math-expr> [<not>]? <like> <math-expr> + * <rlike-expr> ::= <math-expr> [<not>]? <rlike> <math-expr> * <is-null-expr> ::= <math-expr> <is> [<not>]? <null> * <is-nan-expr> ::= <math-expr> <is> [<not>]? <nan> * @@ -296,6 +299,9 @@ trait BasicParser extends JavaTokenParsers with Serializable { def likeExpr: Parser[LogicalExpr] = mathExpression ~ opt(NOT) ~ LIKE ~ mathExpression ^^ { case head ~ notOpt ~ _ ~ value => LikeExpr(head, notOpt.isEmpty, value) } + def rlikeExpr: Parser[LogicalExpr] = mathExpression ~ opt(NOT) ~ RLIKE ~ mathExpression ^^ { + case head ~ notOpt ~ _ ~ value => RLikeExpr(head, notOpt.isEmpty, value) + } def isNullExpr: Parser[LogicalExpr] = mathExpression ~ IS ~ opt(NOT) ~ NULL ^^ { case head ~ _ ~ notOpt ~ _ => IsNullExpr(head, notOpt.isEmpty) } @@ -303,7 +309,7 @@ trait BasicParser extends JavaTokenParsers with Serializable { case head ~ _ ~ notOpt ~ _ => IsNanExpr(head, notOpt.isEmpty) } - def logicalFactor: Parser[LogicalExpr] = (inExpr | betweenExpr | likeExpr | isNullExpr | isNanExpr | mathExpression) ^^ { + def logicalFactor: Parser[LogicalExpr] = (inExpr | betweenExpr | likeExpr | rlikeExpr | isNullExpr | isNanExpr | mathExpression) ^^ { LogicalFactorExpr(_, false, None) } | LBR ~ logicalExpression ~ RBR ~ opt(asAlias) ^^ { case _ ~ expr ~ _ ~ aliasOpt => LogicalFactorExpr(expr, true, aliasOpt) http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/d58bd194/ui/angular/src/app/app.module.ts ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/app.module.ts b/ui/angular/src/app/app.module.ts index 8adb212..2c769f3 100644 --- a/ui/angular/src/app/app.module.ts +++ b/ui/angular/src/app/app.module.ts @@ -42,6 +42,11 @@ import { DataassetComponent } from './dataasset/dataasset.component'; import { BatchComponent } from './job/create-job/batch/batch.component'; import { AcComponent} from './measure/create-measure/ac/ac.component'; import { PrComponent } from './measure/create-measure/pr/pr.component'; +import { PrStep1Component } from './measure/create-measure/pr/step1/step1.component'; +import { PrStep2Component } from './measure/create-measure/pr/step2/step2.component'; +import { PrStep3Component } from './measure/create-measure/pr/step3/step3.component'; +import { PrStep4Component } from './measure/create-measure/pr/step4/step4.component'; +import { PrConfirmModal } from './measure/create-measure/pr/confirmModal/confirmModal.component'; import { PubComponent } from './measure/create-measure/pub/pub.component'; import { LoginComponent } from './login/login.component'; import { AngularMultiSelectModule } from 'angular2-multiselect-dropdown/angular2-multiselect-dropdown'; @@ -151,6 +156,11 @@ const appRoutes: Routes = [ BatchComponent, AcComponent, PrComponent, + PrStep1Component, + PrStep2Component, + PrStep3Component, + PrStep4Component, + PrConfirmModal, PubComponent, LoginComponent, RuleComponent, http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/d58bd194/ui/angular/src/app/measure/create-measure/ac/ac.component.css ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/ac/ac.component.css b/ui/angular/src/app/measure/create-measure/ac/ac.component.css index 80b9b91..83562af 100644 --- a/ui/angular/src/app/measure/create-measure/ac/ac.component.css +++ b/ui/angular/src/app/measure/create-measure/ac/ac.component.css @@ -20,6 +20,16 @@ under the License. @import url('../../../../../node_modules/angular2-toaster/toaster.css'); @import url('../../measure.component.css'); +div.formStep { + min-height: 65vh; + margin-left: 30px; + margin-right: 30px; +} + +h5.over-title { + margin-left: 30px; +} + div.tree div.tree-children::before, div.tree::before { content: ""; @@ -117,4 +127,4 @@ div.tree>treenode>div::before { div.tree>treenode>div>.node-wrapper>treenodeexpander>.toggle-children-wrapper { left: 22px -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/d58bd194/ui/angular/src/app/measure/create-measure/configuration/configuration.component.css ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/configuration/configuration.component.css b/ui/angular/src/app/measure/create-measure/configuration/configuration.component.css index e884cdd..32f3d25 100644 --- a/ui/angular/src/app/measure/create-measure/configuration/configuration.component.css +++ b/ui/angular/src/app/measure/create-measure/configuration/configuration.component.css @@ -16,3 +16,7 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + +label.control-label { + margin-top: 7px; +} http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/d58bd194/ui/angular/src/app/measure/create-measure/configuration/configuration.component.html ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/configuration/configuration.component.html b/ui/angular/src/app/measure/create-measure/configuration/configuration.component.html index ae2a1e3..9f4a926 100644 --- a/ui/angular/src/app/measure/create-measure/configuration/configuration.component.html +++ b/ui/angular/src/app/measure/create-measure/configuration/configuration.component.html @@ -26,7 +26,7 @@ under the License. </div> </div> </div> -<div class="col-md-12 col-lg-12 col-sm-12"> +<div class="col-md-12 col-lg-12 col-sm-12" style="margin-top:10px;"> <div class="form-group"> <label class="col-md-2 col-lg-2 col-sm-2 control-label" title="Your minimum partition size"> Partition Size: @@ -41,7 +41,7 @@ under the License. </div> </div> </div> -<div class="col-md-12 col-lg-12 col-sm-12"> +<div class="col-md-12 col-lg-12 col-sm-12" style="margin-top:10px;"> <div class="form-group"> <label class="col-md-2 col-lg-2 col-sm-2 control-label"> Time Zone: @@ -53,7 +53,7 @@ under the License. </div> </div> </div> -<div class="col-md-12 col-lg-12 col-sm-12" style="height: 30px;"> +<div class="col-md-12 col-lg-12 col-sm-12" style="margin-top: 30px; height: 30px;"> <div class="form-group"> <input style="margin-left:15px" type="checkbox" [checked]="needpath" (change)="needpath=!needpath;upward()"> <label> http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/d58bd194/ui/angular/src/app/measure/create-measure/pr/confirmModal/confirmModal.component.css ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/pr/confirmModal/confirmModal.component.css b/ui/angular/src/app/measure/create-measure/pr/confirmModal/confirmModal.component.css new file mode 100644 index 0000000..e52c8ac --- /dev/null +++ b/ui/angular/src/app/measure/create-measure/pr/confirmModal/confirmModal.component.css @@ -0,0 +1,24 @@ +/* +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. +*/ + +.viewrule-content { + border: 1px solid #fff; + border-radius: 4px; + padding: 10px; +} http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/d58bd194/ui/angular/src/app/measure/create-measure/pr/confirmModal/confirmModal.component.html ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/pr/confirmModal/confirmModal.component.html b/ui/angular/src/app/measure/create-measure/pr/confirmModal/confirmModal.component.html new file mode 100644 index 0000000..92a3afc --- /dev/null +++ b/ui/angular/src/app/measure/create-measure/pr/confirmModal/confirmModal.component.html @@ -0,0 +1,128 @@ +<!-- +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. +--> +<div class="modal-dialog modal-xg modal-lg"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" (click)="hideModal()">×</button> + <h4 class="modal-title">Create measure with the below information?</h4> + </div> + <div class="modal-body"> + <div class="container-fluid" id="viewruleContent" style="overflow:auto;"> + <div class="row"> + <h5 class="over-title margin-bottom-15">Basic information</h5> + </div> + <div class="row"> + <div class="col-lg-12 col-md-12 col-sm-12"> + <div id="viewrule-definition" class="viewrule-content"> + <div class="row"> + <label class="col-md-4 col-lg-4 col-sm-4"> + Measure Name: + </label> + <div class="col-md-8 col-lg-8 col-sm-8 "> + {{step4.prName}} + </div> + </div> + <div class="row"> + <label class="col-md-4 col-lg-4 col-sm-4"> + Measure Description: + </label> + <div class="col-md-8 col-lg-8 col-sm-8 "> + {{step4.desc}} + </div> + </div> + <div class="row"> + <label class="col-md-4 col-lg-4 col-sm-4"> + Measure Type: + </label> + <div class="col-md-8 col-lg-8 col-sm-8 "> + {{step4.type}} + </div> + </div> + <div class="row"> + <label class="col-md-4 col-lg-4 col-sm-4"> + DataSource: + </label> + <div class="col-md-8 col-lg-8 col-sm-8"> + {{step1.currentDB}}.{{step1.currentTable}} + </div> + </div> + <div class="row" *ngIf="step3.size"> + <label class="col-md-4 col-lg-4 col-sm-4"> + Source Partition Size: + </label> + <div class="col-md-8 col-lg-8 col-sm-8"> + {{step3.size}} + </div> + </div> + <div class="row"> + <label class="col-md-4 col-lg-4 col-sm-4"> + Source Time Zone: + </label> + <div class="col-md-8 col-lg-8 col-sm-8"> + {{step3.timezone}} + </div> + </div> + <div class="row" *ngIf="step3.config['where']"> + <label class="col-md-4 col-lg-4 col-sm-4"> + Source Where (Parititon): + </label> + <div class="col-md-8 col-lg-8 col-sm-8"> + {{step3.config['where']}} + </div> + </div> + <div class="row" *ngIf="step3.config['whereCriteria']"> + <label class="col-md-4 col-lg-4 col-sm-4"> + Source Where (Criteria): + </label> + <div class="col-md-8 col-lg-8 col-sm-8"> + {{step3.config['whereCriteria']}} + </div> + </div> + <div class="row" *ngIf="step3.needpath && step3.path"> + <label class="col-md-4 col-lg-4 col-sm-4"> + Source Path: + </label> + <div class="col-md-8 col-lg-8 col-sm-8"> + {{step3.path}} + </div> + </div> + <div class="row"> + <label class="col-md-4 col-lg-4 col-sm-4"> + Owner: + </label> + <div class="col-md-8 col-lg-8 col-sm-8"> + {{step4.owner}} + </div> + </div> + </div> + </div> + </div> + <h5 class="row">Rules</h5> + <div class="row" *ngFor="let index of step4.noderule;"> + {{index.name}} : {{index.infos}} + </div> + <br/> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" (click)="hideModal()">Cancel</button> + <button type="button" id="save" class="btn btn-primary" (click)="saveModal()">Save</button> + </div> + </div> +</div> http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/d58bd194/ui/angular/src/app/measure/create-measure/pr/confirmModal/confirmModal.component.spec.ts ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/pr/confirmModal/confirmModal.component.spec.ts b/ui/angular/src/app/measure/create-measure/pr/confirmModal/confirmModal.component.spec.ts new file mode 100644 index 0000000..dddc650 --- /dev/null +++ b/ui/angular/src/app/measure/create-measure/pr/confirmModal/confirmModal.component.spec.ts @@ -0,0 +1,43 @@ +/* +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 { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PrConfirmModal } from './confirmModal.component'; + +describe('PrConfirmModalComponent', () => { + let component: PrConfirmModal; + let fixture: ComponentFixture<PrConfirmModal>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PrConfirmModal ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PrConfirmModal); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/d58bd194/ui/angular/src/app/measure/create-measure/pr/confirmModal/confirmModal.component.ts ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/pr/confirmModal/confirmModal.component.ts b/ui/angular/src/app/measure/create-measure/pr/confirmModal/confirmModal.component.ts new file mode 100644 index 0000000..d19022c --- /dev/null +++ b/ui/angular/src/app/measure/create-measure/pr/confirmModal/confirmModal.component.ts @@ -0,0 +1,46 @@ +/* +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 {Component, EventEmitter, Input, Output} from "@angular/core"; +import {ProfilingStep1, ProfilingStep2, ProfilingStep3, ProfilingStep4} from "../pr.component"; + +@Component({ + selector: "app-pr-confirm-modal", + templateUrl: "./confirmModal.component.html", + styleUrls: ["./confirmModal.component.css"] +}) +export class PrConfirmModal { + + @Input() step1: ProfilingStep1; + @Input() step2: ProfilingStep2; + @Input() step3: ProfilingStep3; + @Input() step4: ProfilingStep4; + + @Output() hide: EventEmitter<Object> = new EventEmitter<Object>(); + @Output() saveMeasure: EventEmitter<Object> = new EventEmitter<Object>(); + + constructor() {} + + hideModal() { + this.hide.emit(); + } + + saveModal() { + this.saveMeasure.emit() + } +} http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/d58bd194/ui/angular/src/app/measure/create-measure/pr/pr.component.css ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/pr/pr.component.css b/ui/angular/src/app/measure/create-measure/pr/pr.component.css index 512868c..8217c9c 100644 --- a/ui/angular/src/app/measure/create-measure/pr/pr.component.css +++ b/ui/angular/src/app/measure/create-measure/pr/pr.component.css @@ -20,107 +20,14 @@ under the License. @import url('../../../../../node_modules/angular2-toaster/toaster.css'); @import url('../../measure.component.css'); -.selected-list .c-list .c-token { - background: #6faece; +div.formStep { + min-height: 65vh; + margin-left: 30px; + margin-right: 30px; } -div.tree div.tree-children::before, -div.tree::before { - content: ""; - position: absolute; - border-left: 1px dotted #23527c; - height: 100%; - top: -14px; - left: 12px -} - -tree-root { - color: #999; -} - -div.tree { - padding-left: 0; - margin-left: -5px -} - -div.tree div.tree-children { - position: relative; - padding-left: 0; - margin-left: 16px -} - -div.tree div.tree-children::before { - left: 5px -} - -div.tree treenode>div>.node-wrapper { - margin-left: 24px -} - -div.tree treenode>div>.node-wrapper>.node-content-wrapper { - margin-left: 4px -} - -div.tree treenode>div.tree-node-leaf>.node-wrapper { - margin-left: 0 -} - -div.tree treenode>div::before { - content: ""; - position: absolute; - border-bottom: 1px dotted #23527c; - width: 7px; - margin-top: 12px; - left: 7px -} - -div.tree treenode>div .toggle-children-wrapper { - width: 13px; - height: 13px; - border: 1px solid #23527c; - position: absolute; - left: 15px; - margin-top: 5px; - margin-left: 0; - display: inline-block; - background-color: #fff; - z-index: 1 -} - -div.tree treenode>div .toggle-children-wrapper::before { - content: ""; - display: inline-block; - width: 7px; - border-top: 1px solid #23527c; - position: absolute; - top: 5px; - left: 2px -} - -div.tree treenode>div .toggle-children-wrapper.toggle-children-wrapper-collapsed::after { - content: ""; - display: inline-block; - height: 7px; - border-left: 1px solid #23527c; - position: absolute; - top: 2px; - left: 5px -} - -div.tree treenode>div .toggle-children-wrapper .toggle-children { - display: none -} - -div.tree treenode>div .node-content-wrapper { - margin-left: 4px -} - -div.tree>treenode>div::before { - left: 14px -} - -div.tree>treenode>div>.node-wrapper>treenodeexpander>.toggle-children-wrapper { - left: 22px +h5.over-title { + margin-left: 30px; } label { @@ -131,35 +38,3 @@ label { max-height: 40vh; overflow-y: scroll; } - -.badgebox { - opacity: 0; -} - -.badgebox+.badge { - /* Move the check mark away when unchecked */ - text-indent: -999999px; - /* Makes the badge's width stay the same checked and unchecked */ - width: 27px; -} - -.badgebox:focus+.badge { - /* Set something to make the badge looks focused */ - /* This really depends on the application, in my case it was: */ - /* Adding a light border */ - box-shadow: inset 0px 0px 5px; - /* Taking the difference out of the padding */ -} - -.badgebox:checked+.badge { - /* Move the check mark back when checked */ - text-indent: 0; -} - -.middle { - vertical-align: middle; -} - -.no-border { - border: 1px solid transparent !important; -} http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/d58bd194/ui/angular/src/app/measure/create-measure/pr/pr.component.html ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/pr/pr.component.html b/ui/angular/src/app/measure/create-measure/pr/pr.component.html index 1767845..22b1f4d 100644 --- a/ui/angular/src/app/measure/create-measure/pr/pr.component.html +++ b/ui/angular/src/app/measure/create-measure/pr/pr.component.html @@ -16,378 +16,60 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> -<div class="container-fluid" (window:resize)="onResize($event)"> +<div class="container-fluid"> <div class="row"> <h5 class="over-title margin-bottom-15">Create Measure</h5> + <toaster-container></toaster-container> </div> <div class="row"> <form name="Form" id="form" #prForm="ngForm" novalidate> <div id="wizard" class="swMain"> <ul> - <li (click)="goTo(1)"> + <li> <a [ngClass]="{'selected' : currentStep >= 1, 'done' : currentStep > 1}" class="selected"> - <div class="stepNumber"> - 1 - </div> - <span class="stepDesc text-small"> Choose Source </span> + <div class="stepNumber">1</div> + <span class="stepDesc text-small">Choose Source</span> </a> </li> - <li (click)="goTo(2)"> + <li> <a [ngClass]="{'selected' : currentStep >= 2, 'done' : currentStep > 2}" class="" style=""> - <div class="stepNumber"> - 2 - </div> - <span class="stepDesc text-small"> Select Models </span> + <div class="stepNumber">2</div> + <span class="stepDesc text-small">Select Models</span> </a> </li> - <li (click)="goTo(3)"> + <li> <a [ngClass]="{'selected' : currentStep >= 3, 'done' : currentStep > 3}"> - <div class="stepNumber"> - 3 - </div> - <span class="stepDesc text-small">Partition Configuration </span> + <div class="stepNumber">3</div> + <span class="stepDesc text-small">Partition Configuration</span> </a> </li> - <li (click)="goTo(4)"> + <li> <a [ngClass]="{'selected' : currentStep >= 4, 'done' : currentStep > 4}"> - <div class="stepNumber"> - 4 - </div> - <span class="stepDesc text-small"> Configuration </span> + <div class="stepNumber">4</div> + <span class="stepDesc text-small">Configuration</span> </a> </li> </ul> </div> + <div id="step-1" *ngIf="currentStep == 1" class="formStep"> - <label class="stepDesc">This step let you choose the single source of truth for data quality comparision with target. Currently you can only select the attributes from one schema</label> - <div class="container-fluid"> - <div class="col-md-4 col-lg-4 col-sm-4"> - <fieldset> - <legend>Please select schema</legend> - <tree-root [nodes]="nodeList" [options]="options"></tree-root> - </fieldset> - </div> - <div class="col-md-8 col-lg-8 col-sm-8"> - <fieldset> - <legend> - Select attributes - </legend> - <div class="y-scrollable"> - <div style="margin-top:10px;"> - <label>View schema:</label> - <i style="color:#fff;font-weight: bold;">{{currentDBstr}}{{currentTable}} - </i> - </div> - <div style="margin-top:5px;"> - <table class="table table-striped no-border"> - <thead> - <tr style="background-color:#7D95CC"> - <th> - <input type="checkbox" (click)="toggleAll()" [checked]="selectedAll" /> - </th> - <th>Column Name</th> - <th>Type</th> - <th>Comment</th> - </tr> - </thead> - <tbody> - <tr *ngIf="!schemaCollection || schemaCollection.length == 0"> - <td colspan="5" style="text-align:center;"><span class="highlight">Please select a schema from the left tree first</span></td> - </tr> - <tr *ngFor="let row of schemaCollection"> - <td> - <input type="checkbox" (click)='toggleSelection(row)' [checked]="row.selected" value={{row.name}} /> - </td> - <td>{{row.name}}</td> - <td>{{row.type}}</td> - <td>{{row.comment}}</td> - </tr> - </tbody> - </table> - </div> - </div> - </fieldset> - </div> - <div class="form-group btn-container"> - <toaster-container></toaster-container> - <button class="btn btn-primary btn-o next-step btn-wide pull-right" (click)="next(Form)"> - Next <i class="fa fa-arrow-circle-right"></i> - </button> - </div> - </div> + <app-pr-step-1 [step1]='step1' (nextStep)='updateStep1($event)'></app-pr-step-1> </div> + <div id="step-2" *ngIf="currentStep == 2" class="formStep"> - <label class="stepDesc">Please choose one of the profiling models provided below:</label> - <div class="container-fluid" id="notshowrule"> - <div class="col-md-12 col-lg-12 col-sm-12"> - <!-- <fieldset style="max-height:400px;overflow-y:scroll;"> --> - <fieldset> - <div class="y-scrollable"> - <div style="display:block"> - <label style="margin-left:15px">View schema:</label> <i style="color:#fff;font-weight: bold;">{{currentDB}}.{{currentTable}}</i> - </div> - <div class="col-md-12 col-lg-12 col-sm-12" style="z-index:100;margin-top:5px;"> - <table class="table table-striped" [mfData]="results" #mf="mfDataTable"> - <thead> - <tr style="background-color:#7D95CC"> - <th>Column Name</th> - <th>Data Type</th> - <th>Rule <i style="color:#b2c831;" class="fa fa-question-circle fa-lg"></i><i style="font-family: 'Open Sans', sans-serif;">Click <a (click)="showRule()" class="bark-link po">here</a> to view the rule definition</i></th> - </tr> - </thead> - <tbody> - <tr *ngFor="let item of selection"> - <td class="middle">{{item.name}}</td> - <td class="middle">{{item.type}}</td> - <td class="col-md-5 middle"> - <angular2-multiselect [data]="dropdownList[item.name]" name="rules-{{item.name}}" [(ngModel)]="selectedItems[item.name]" [settings]="dropdownSettings"></angular2-multiselect> - </td> - </tr> - </tbody> - </table> - </div> - <!-- <div *ngFor="let index of transrule"> {{index}}</div> --> - </div> - </fieldset> - </div> - <div class="form-group btn-container"> - <button class="btn btn-primary btn-o back-step btn-wide pull-left" (click)="prev(Form)"> - <i class="fa fa-arrow-circle-left"></i> Back - </button> - <toaster-container></toaster-container> - <button class="btn btn-primary btn-o next-step btn-wide pull-right" (click)="next(Form)"> - Next <i class="fa fa-arrow-circle-right"></i> - </button> - </div> - </div> - <div class="container-fluid formStep" id="showrule" style="display:none;"> - <div class="col-md-12 col-lg-12 col-sm-12"> - <fieldset> - <app-rule></app-rule> - </fieldset> - </div> - <div class="form-group btn-container"> - <button class="btn btn-primary btn-o back-step btn-wide pull-left" (click)="back()"> - <i class="fa fa-arrow-circle-left"></i> Back - </button> - </div> - </div> + <app-pr-step-2 [step1]='step1' [step2]='step2' (prevStep)='prev($event)' (nextStep)='updateStep2($event)'></app-pr-step-2> </div> + <div id="step-3" *ngIf="currentStep == 3" class="formStep"> - <label class="stepDesc">Please complete the partition configuration for {{currentTable}}</label> - <div class="container-fluid"> - <div class="col-md-12 col-lg-12 col-sm-12"> - <fieldset> - <legend> - Required Information - </legend> - <div class="y-scrollable"> - <div class="col-md-12 col-lg-12 col-sm-12"> - <div class="form-group" style="text-align:center"> - Data Source:{{currentDB}}.{{currentTable}} - </div> - <app-configuration [data]="config" [location]="srclocation" (event)="getData($event)"></app-configuration> - </div> - </div> - </fieldset> - </div> - <div class="form-group btn-container"> - <button class="btn btn-primary btn-o back-step btn-wide pull-left" (click)="prev(Form)"> - <i class="fa fa-arrow-circle-left"></i> Back - </button> - <toaster-container></toaster-container> - <button class="btn btn-primary btn-o next-step btn-wide pull-right" (click)="next(Form)"> - Next <i class="fa fa-arrow-circle-right"></i> - </button> - </div> - </div> + <app-pr-step-3 [step1]='step1' [step2]='step2' [step3]='step3' (prevStep)='prev($event)' (nextStep)='updateStep3($event)'></app-pr-step-3> </div> + <div id="step-4" *ngIf="currentStep == 4" class="formStep"> - <label class="stepDesc">Please setup the measure required information</label> - <div class="container-fluid"> - <div class="col-md-12 col-lg-12 col-sm-12"> - <fieldset> - <legend> - Required Information - </legend> - <div class="y-scrollable"> - <div class="col-md-12 col-lg-12 col-sm-12" style="margin-top:30px;"> - <div class="form-group" [ngClass]="{'has-error':prName.dirty&&prName.invalid, 'has-success':prName.valid}"> - <label class="col-md-2 col-lg-2 col-sm-2 control-label"> - Measure Name<span class="symbol required"></span>: - </label> - <div class="col-md-10 col-lg-10 col-sm-10 "> - <input type="text" class="form-control" [(ngModel)]="name" #prName="ngModel" name="prName" placeholder="Please input the measure name" required pattern="^[a-zA-Z0-9_-]*$"> - <span class="error text-small block " *ngIf="prName.dirty && (prName.errors?.required)">Measure Name is required</span> - <span class="error text-small block " *ngIf="prName.dirty && (prName.errors?.pattern)">Only letter, number, "-" and "_" are allowed</span> - </div> - </div> - </div> - <div class="col-md-12 col-lg-12 col-sm-12"> - <div class="form-group"> - <label class="col-md-2 col-lg-2 col-sm-2 control-label"> - Measure Description: - </label> - <div class="col-md-10 col-lg-10 col-sm-10 "> - <input type="text" class="form-control" [(ngModel)]="desc" placeholder="Please input detailed description of your measure" name="desc"> - </div> - </div> - </div> - <div class="col-md-12 col-lg-12 col-sm-12"> - <div class="form-group"> - <label for="typeSelector" class="col-md-2 col-lg-2 col-sm-2 control-label"> - Measure Type:<span class="symbol required"></span>: - </label> - <div class="col-md-10 col-lg-10 col-sm-10 "> - <select id="typeSelector" class="form-control" [(ngModel)]="type" disabled required name="type"> - <option>{{type}}</option> - </select> - </div> - </div> - </div> - <div class="col-md-12 col-lg-12 col-sm-12"> - <div class="form-group"> - <label class="col-md-2 col-lg-2 col-sm-2 control-label"> - DataSource:<span class="symbol required"></span>: - </label> - <div class="col-md-10 col-lg-10 col-sm-10"> - <input type="text" class="form-control" [(ngModel)]="currentTable" #prDataSource="ngModel" name="prDataSource" [readonly]="true" required > - <span class="error text-small block " *ngIf="prDataSource.dirty && (prDataSource.errors?.required)">Data source is required</span> - </div> - </div> - </div> - <div class="col-md-12 col-lg-12 col-sm-12"> - <div class="form-group"> - <label class="col-md-2 col-lg-2 col-sm-2 control-label"> - Owner: - </label> - <div class="col-md-10 col-lg-10 col-sm-10"> - <input type="text" class="form-control" name="owner" disabled [(ngModel)]="owner"> - </div> - </div> - </div> - </div> - <div style="color:#b2c831"> - <p> - <i class="fa fa-info-circle"></i> After submitted, please go to "<a class="bark-link" routerLink="/measures">Measures</a>" to check the measure status - </p> - </div> - </fieldset> - </div> - <div class="form-group btn-container"> - <button class="btn btn-primary btn-o back-step btn-wide pull-left" (click)="prev(Form)"> - <i class="fa fa-arrow-circle-left"></i> Back - </button> - <toaster-container></toaster-container> - <button type="submit" (click)="submit(prForm)" class="btn btn-primary btn-o next-step btn-wide pull-right"> - Submit - </button> - </div> - </div> + <app-pr-step-4 [step1]='step1' [step2]='step2' [step3]='step3' [step4]='step4' (prevStep)='prev($event)' (submitMeasure)='submit($event)'></app-pr-step-4> </div> + <div class="modal fade" id="confirm" role="dialog" #modal tabindex="-1" [ngClass]="{'in': visibleAnimate}" [ngStyle]="{'display': visible ? 'block' : 'none', 'opacity': visibleAnimate ? 1 : 0}" (click)="onContainerClicked($event)"> - <div class="modal-dialog modal-xg modal-lg"> - <div class="modal-content"> - <div class="modal-header"> - <button type="button" class="close" (click)="hide()">×</button> - <h4 class="modal-title">Save the measure with the below information?</h4> - </div> - <div class="modal-body"> - <div class="container-fluid" id="viewruleContent" style="overflow:auto;"> - <div class="row"> - <h5 class="over-title margin-bottom-15">Basic information</h5> - </div> - <div class="row"> - <div class="col-lg-12 col-md-12 col-sm-12"> - <div id="viewrule-definition" class="viewrule-content"> - <div class="row"> - <label class="col-md-4 col-lg-4 col-sm-4"> - Measure Name: - </label> - <div class="col-md-8 col-lg-8 col-sm-8 "> - {{name}} - </div> - </div> - <div class="row"> - <label class="col-md-4 col-lg-4 col-sm-4"> - Measure Description: - </label> - <div class="col-md-8 col-lg-8 col-sm-8 "> - {{desc}} - </div> - </div> - <div class="row"> - <label class="col-md-4 col-lg-4 col-sm-4"> - Measure Type: - </label> - <div class="col-md-8 col-lg-8 col-sm-8 "> - {{type}} - </div> - </div> - <div class="row"> - <label class="col-md-4 col-lg-4 col-sm-4"> - DataSource: - </label> - <div class="col-md-8 col-lg-8 col-sm-8" *ngIf="this.currentTable"> - {{currentDB}}.{{currentTable}} - </div> - </div> - <div class="row" *ngIf="this.size"> - <label class="col-md-4 col-lg-4 col-sm-4"> - Source Partition Size: - </label> - <div class="col-md-8 col-lg-8 col-sm-8"> - {{this.size}} - </div> - </div> - <div class="row" *ngIf="this.size"> - <label class="col-md-4 col-lg-4 col-sm-4"> - Source Time Zone: - </label> - <div class="col-md-8 col-lg-8 col-sm-8"> - {{this.timezone}} - </div> - </div> - <div class="row" *ngIf="this.where"> - <label class="col-md-4 col-lg-4 col-sm-4"> - Source Where: - </label> - <div class="col-md-8 col-lg-8 col-sm-8"> - {{this.where}} - </div> - </div> - <div class="row" *ngIf="this.needpath && this.path"> - <label class="col-md-4 col-lg-4 col-sm-4"> - Source Path: - </label> - <div class="col-md-8 col-lg-8 col-sm-8"> - {{this.path}} - </div> - </div> - <div class="row"> - <label class="col-md-4 col-lg-4 col-sm-4"> - Owner: - </label> - <div class="col-md-8 col-lg-8 col-sm-8"> - {{owner}} - </div> - </div> - </div> - </div> - </div> - <h5 class="row">Rules</h5> - <div class="row" *ngFor="let index of noderule;"> - {{index.name}} : {{index.infos}} - </div> - <br/> - </div> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-default" (click)="hide()">Cancel</button> - <button type="button" id="save" class="btn btn-primary" (click)="save()">Save</button> - </div> - </div> - </div> + <app-pr-confirm-modal [step1]='step1' [step2]='step2' [step3]='step3' [step4]='step4' (hide)='hide($event)' (saveMeasure)='save($event)'></app-pr-confirm-modal> </div> </form> </div> http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/d58bd194/ui/angular/src/app/measure/create-measure/pr/pr.component.ts ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/pr/pr.component.ts b/ui/angular/src/app/measure/create-measure/pr/pr.component.ts index b36af39..4b4b747 100644 --- a/ui/angular/src/app/measure/create-measure/pr/pr.component.ts +++ b/ui/angular/src/app/measure/create-measure/pr/pr.component.ts @@ -17,64 +17,54 @@ specific language governing permissions and limitations under the License. */ import { Component, OnInit } from "@angular/core"; -import { FormControl } from "@angular/forms"; -import { FormsModule } from "@angular/forms"; import { ServiceService } from "../../../service/service.service"; -import { TREE_ACTIONS, KEYS, IActionMapping, ITreeOptions } from "angular-tree-component"; -import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { ToasterModule, ToasterService, ToasterContainerComponent } from "angular2-toaster"; -import * as $ from "jquery"; import { HttpClient } from "@angular/common/http"; import { Router } from "@angular/router"; -import { DataTableModule } from "angular2-datatable"; import { AfterViewChecked, ElementRef } from "@angular/core"; -import { AngularMultiSelectModule } from "angular2-multiselect-dropdown/angular2-multiselect-dropdown"; -import { ConfigurationComponent } from "../configuration/configuration.component"; - -class node { - name: string; - id: number; - children: object[]; - isExpanded: boolean; - cols: Col[]; - parent: string; - location: string; +import { Col } from './step1/step1.component' +import * as $ from "jquery"; + +export class ProfilingStep1 { + data: any; + schemaCollection: Col[] = []; + nodeList: object[] = []; + selection: Col[]; + dropdownList: object = {}; + currentDB: string; + currentDBStr: string; + currentTable: string; + srcname: string; + srclocation: string; + selectedItems: object = {}; } -class Rule { - type: string; +export class ProfilingStep2 { + selectedItems: object = {}; } -class Col { - name: string; - type: string; - comment: string; - selected: boolean; - isNum: boolean; - isExpanded: boolean; - // rules:string[]; - groupby: string; - RE: string; - rules: any; - newRules: Rule[]; - ruleLength = 0; - constructor(name: string, type: string, comment: string, selected: boolean) { - this.name = name; - this.type = type; - this.comment = comment; - this.selected = false; - this.isExpanded = false; - this.groupby = ""; - this.rules = []; - this.RE = ""; - this.newRules = []; - - var patt = new RegExp("int|double|float/i"); - if (patt.test(this.type)) { - this.isNum = true; - } - // this.rules = []; - } +export class ProfilingStep3 { + config: object = { + where: "", + timezone: "UTC(WET,GMT)", + num: 1, + timetype: "day", + needpath: false, + path: "" + }; + timezone: string = "UTC(WET,GMT)"; + where: string = ""; + size: string = "1day"; + needpath: boolean = false; + path: string; +} + +export class ProfilingStep4 { + prName: string = ""; + desc: string; + type: string = "profiling"; + owner: string = "test"; + noderule: object[] = []; } @Component({ @@ -84,338 +74,306 @@ class Col { styleUrls: ["./pr.component.css"] }) export class PrComponent implements AfterViewChecked, OnInit { - noderule = []; + currentStep = 1; + + step1: ProfilingStep1; + step2: ProfilingStep2; + step3: ProfilingStep3; + step4: ProfilingStep4; + transrule = []; transenumrule = []; transnullrule = []; - showrule = false; - dropdownList = {}; - selectedItems = {}; - allRules = {}; - dropdownSettings = {}; - currentStep = 1; - firstCond = false; - mouseover = false; - selection: Col[]; - selectedAll = false; - currentDB = ""; - currentTable = ""; - schemaCollection: Col[]; - totallen = 0; - type = "profiling"; - data: any; - desc: string; - owner = "test"; - currentDBstr: string; - timezone = ""; - ruledesc = ""; - newMeasure = { - name: "", - "measure.type": "griffin", - "dq.type": "PROFILING", - "process.type": "BATCH", - owner: "", - description: "", - "rule.description": { - details:[] - }, - // "group":[], - "data.sources": [ - { - name: "source", - connectors: [ - { - name: "", - type: "HIVE", - version: "1.2", - "data.unit": "", - "data.time.zone": "", - config: { - database: "", - "table.name": "", - where: "" - }, - predicates: [ - { - type: "file.exist", - config: { - "root.path": '', - path: "" - } - } - ] - } - ] - } - ], - "evaluate.rule": { - rules: [ - // { - // "dsl.type": "griffin-dsl", - // "dq.type": "profiling", - // rule: "", - // name: "", - // } - ] - } - }; - name: ""; + transregexrule = []; + newMeasure = {}; createResult: any; - newCond: any; - srclocation: string; - srcname: string; - config = { - where: "", - timezone: "", - num: 1, - timetype: "day", - needpath: false, - path: "" - }; - where: string; - size: string; - path: string; - location: string; - needpath: boolean; - private toasterService: ToasterService; public visible = false; public visibleAnimate = false; + private toasterService: ToasterService; - public hide(): void { - this.visibleAnimate = false; - setTimeout(() => (this.visible = false), 300); - this.transrule = []; - this.transenumrule = []; - this.transnullrule = []; - this.noderule = []; - $("#save").removeAttr("disabled"); + next() { + if (this.formValidation(this.currentStep)) { + this.currentStep++; + } else { + this.toasterService.pop( + "error", + "Error!", + "Please select at least one attribute!" + ); + return false; + } } - public onContainerClicked(event: MouseEvent): void { - if ((<HTMLElement>event.target).classList.contains("modal")) { - this.hide(); - } + prev() { + this.currentStep--; } - onResize(event) { - this.resizeWindow(); + updateStep1(body:ProfilingStep1) { + this.step1 = body; + this.next(); } - resizeWindow() { - var stepSelection = ".formStep"; - $(stepSelection).css({ - // height: window.innerHeight - $(stepSelection).offset().top - $('#footerwrap').outerHeight() - height: window.innerHeight - $(stepSelection).offset().top - }); - $("fieldset").height( - $(stepSelection).height() - - $(stepSelection + ">.stepDesc").height() - - $(".btn-container").height() - - 130 - ); - $(".y-scrollable").css({ - // 'max-height': $('fieldset').height()- $('.add-dataset').outerHeight() - height: $("fieldset").height() - }); + updateStep2(body:ProfilingStep2) { + this.step2 = body; + this.next(); } - setDropdownList() { - if (this.selection) { - for (let item of this.selection) { - if (item.isNum == true) { - this.dropdownList[item.name] = [ - { id: 1, itemName: "Null Count", category: "Simple Statistics" }, - { id: 2, itemName: "Distinct Count", category: "Simple Statistics" }, - { id: 3, itemName: "Total Count", category: "Summary Statistics" }, - { id: 4, itemName: "Maximum", category: "Summary Statistics" }, - { id: 5, itemName: "Minimum", category: "Summary Statistics" }, - { id: 6, itemName: "Average", category: "Summary Statistics" }, - // {"id":7,"itemName":"Median","category": "Summary Statistics"}, - // {"id":8,"itemName":"Rule Detection Count","category": "Advanced Statistics"}, - { id: 9, itemName: "Enum Detection Top5 Count", category: "Advanced Statistics" } - ]; - } else { - this.dropdownList[item.name] = [ - { id: 1, itemName: "Null Count", category: "Simple Statistics" }, - { id: 2, itemName: "Distinct Count", category: "Simple Statistics" }, - { id: 3, itemName: "Total Count", category: "Summary Statistics" }, - // {"id":8,"itemName":"Rule Detection Count","category": "Advanced Statistics"}, - { id: 9, itemName: "Enum Detection Top5 Count", category: "Advanced Statistics" } - // {"id":10,"itemName":"Regular Expression Detection Count","category": "Advanced Statistics"} - ]; + updateStep3(body:ProfilingStep3) { + this.step3 = body; + this.next(); + } + + formValidation = (step) => { + if (step == undefined) step = this.currentStep; + if (step == 1) { + return this.step1.selection && this.step1.selection.length > 0; + } else if (step == 2) { + let len = 0; + let selectedlen = 0; + for (let key in this.step2.selectedItems) { + selectedlen++; + len = this.step2.selectedItems[key].length; + if (len == 0) { + return false; } } + return this.step1.selection.length == selectedlen; + } else if (step == 3) { + return true; + } else if (step == 4) { + return /^[a-zA-Z0-9_-]+$/.test(this.step4.prName); } + return false; + }; + + constructor( + private elementRef: ElementRef, + toasterService: ToasterService, + private http: HttpClient, + private router: Router, + public serviceService: ServiceService + ) { + this.toasterService = toasterService; } - toggleSelection(row) { - row.selected = !row.selected; - var idx = this.selection.indexOf(row); - // is currently selected - if (idx > -1) { - this.selection.splice(idx, 1); - this.selectedAll = false; - for (let key in this.selectedItems) { - if (key === row.name) { - delete this.selectedItems[key]; + getGrouprule() { + var selected = { name: "" }; + var value = ""; + var nullvalue = ""; + var nullname = ""; + var enmvalue = "" + var regexvalue = ""; + var regexname = ""; + var grpname = ""; + + for (let key in this.step2.selectedItems) { + selected.name = key; + let info = ""; + let otherinfo = ""; + for (let i = 0; i < this.step2.selectedItems[key].length; i++) { + var originrule = this.step2.selectedItems[key][i].itemName; + info = info + originrule + ","; + + if (originrule == "Enum Detection Top5 Count") { + + enmvalue = this.transferRule(originrule, selected); + grpname = `${selected.name}_grp`; + this.transenumrule.push(enmvalue); + this.pushEnmRule(enmvalue, grpname); + + } else if (originrule == "Null Count") { + + nullvalue = this.transferRule(originrule, selected); + nullname = `${selected.name}_nullcount`; + this.transnullrule.push(nullvalue); + this.pushNullRule(nullvalue, nullname); + + } else if (originrule == "Empty Count") { + + nullvalue = this.transferRule(originrule, selected); + nullname = `${selected.name}_emptycount`; + this.transnullrule.push(nullvalue); + this.pushNullRule(nullvalue, nullname); + + } else if (originrule == "Regular Expression Detection Count") { + + selected['regex'] = this.step2.selectedItems[key].regex; + regexvalue = this.transferRule(originrule, selected); + regexname = `${selected.name}_regexcount`; + this.transregexrule.push(regexvalue); + this.pushRegexRule(regexvalue, regexname); + + } else { + + otherinfo = otherinfo + originrule + ","; + value = this.transferRule(originrule, selected); + this.transrule.push(value); + } } - } else { - // is newly selected - this.selection.push(row); + + info = info.substring(0, info.lastIndexOf(",")); + otherinfo = otherinfo.substring(0, otherinfo.lastIndexOf(",")); + this.step4.noderule.push({ + name: key, + infos: info + }); } - if (this.selection.length == 3) { - this.selectedAll = true; - } else { - this.selectedAll = false; + if (this.transrule.length != 0) { + this.getRule(this.transrule); } - this.setDropdownList(); } - toggleAll() { - this.selectedAll = !this.selectedAll; - this.selection = []; - for (var i = 0; i < this.schemaCollection.length; i++) { - this.schemaCollection[i].selected = this.selectedAll; - if (this.selectedAll) { - this.selection.push(this.schemaCollection[i]); - } + getRule(trans) { + var rule = ""; + for (let i of trans) { + rule = rule + i + ","; } - this.setDropdownList(); + rule = rule.substring(0, rule.lastIndexOf(",")); + this.pushRule(rule); + } + + pushEnmRule(rule, grpname) { + this.newMeasure["evaluate.rule"].rules.push({ + "dsl.type": "griffin-dsl", + "dq.type": "PROFILING", + rule: rule, + name: grpname, + metric: { + "collect.type": "array" + } + }); + } + + pushNullRule(rule, nullname) { + this.newMeasure["evaluate.rule"].rules.push({ + "dsl.type": "griffin-dsl", + "dq.type": "PROFILING", + rule: rule, + name: nullname + }); + } + + pushRegexRule(rule, nullname) { + this.newMeasure["evaluate.rule"].rules.push({ + "dsl.type": "griffin-dsl", + "dq.type": "PROFILING", + rule: rule, + name: nullname + }); + } + + pushRule(rule) { + this.newMeasure["evaluate.rule"].rules.push({ + "dsl.type": "griffin-dsl", + "dq.type": "PROFILING", + rule: rule, + name: "profiling" + }); } transferRule(rule, col) { switch (rule) { case "Total Count": - return "count(source.`" + col.name + "`) AS `" + col.name + "-count`"; + return ( + `count(source.${col.name}) AS \`${col.name}_count\`` + ); case "Distinct Count": return ( - "approx_count_distinct(source.`" + - col.name + - "`) AS `" + - col.name + - "-distcount`" + `approx_count_distinct(source.${col.name}) AS \`${col.name}_distcount\`` ); case "Null Count": return ( - "count(source.`" + - col.name + - "`) AS `" + - col.name + - "-nullcount" + - "` WHERE source.`" + - col.name + - "` IS NULL" + `count(source.${col.name}) AS \`${col.name}_nullcount\` WHERE source.${col.name} IS NULL` ); - // case 'Regular Expression Detection Count': - // return 'count(source.`'+col.name+'`) where source.`'+col.name+'` LIKE '; - // case 'Rule Detection Count': - // return 'count(source.`'+col.name+'`) where source.`'+col.name+'` LIKE '; case "Maximum": - return "max(source.`" + col.name + "`) AS `" + col.name + "-max`"; + return ( + `max(source.${col.name}) AS \`${col.name}_max\`` + ); case "Minimum": - return "min(source.`" + col.name + "`) AS `" + col.name + "-min`"; - // case 'Median': - // return 'median(source.`'+col.name+'`) '; + return ( + `min(source.${col.name}) AS \`${col.name}_min\`` + ); case "Average": - return "avg(source.`" + col.name + "`) AS `" + col.name + "-average`"; + return ( + `avg(source.${col.name}) AS \`${col.name}_average\`` + ); + case "Empty Count": + return ( + `count(source.${col.name}) AS \`${col.name}_emptycount\` WHERE source.${col.name} = ''` + ); + case "Regular Expression Detection Count": + return ( + `count(source.${col.name} RLIKE '${col.regex}') AS \`${col.name}_regexcount\`` + ); case "Enum Detection Top5 Count": return ( - "source.`" + - col.name + - "`,count(*) AS `" + - "count` GROUP BY source.`" + - col.name + - "` ORDER BY `count` DESC LIMIT 5" + `source.${col.name}, ${col.name}, count(*) AS count GROUP BY source.${col.name} ORDER BY count DESC LIMIT 5` ); } } - next(form) { - if (this.formValidation(this.currentStep)) { - this.currentStep++; - } else { - this.toasterService.pop( - "error", - "Error!", - "Please select at least one attribute!" - ); - return false; - } + public hide(): void { + this.visibleAnimate = false; + setTimeout(() => (this.visible = false), 300); + this.transrule = []; + this.transenumrule = []; + this.transnullrule = []; + this.transregexrule = []; + this.step4.noderule = []; + $("#save").removeAttr("disabled"); } - formValidation = function(step) { - if (step == undefined) { - step = this.currentStep; - } - if (step == 1) { - return this.selection && this.selection.length > 0; - } else if (step == 2) { - var len = 0; - var selectedlen = 0; - for (let key in this.selectedItems) { - selectedlen++; - len = this.selectedItems[key].length; - if (len == 0) { - return false; - } - } - return this.selection.length == selectedlen ? true : false; - } else if (step == 3) { - return true; - } else if (step == 4) { + public onContainerClicked(event: MouseEvent): void { + if ((<HTMLElement>event.target).classList.contains("modal")) { + this.hide(); } - return false; - }; - - prev(form) { - this.currentStep--; - } - goTo(i) { - this.currentStep = i; } - submit(form) { - if (!form.valid) { + + submit(body:ProfilingStep4) { + this.step4 = body; + + if (!this.formValidation(this.currentStep)) { this.toasterService.pop( "error", "Error!", - "please complete the form in this step before proceeding" + "Please complete the form in this step before proceeding!" ); return false; } + this.newMeasure = { - name: this.name, + name: this.step4.prName, "measure.type": "griffin", "dq.type": "PROFILING", "rule.description": { - details:this.noderule + details: this.step4.noderule }, "process.type": "BATCH", - owner: this.owner, - description: this.desc, - // "group":this.finalgrp, + owner: this.step4.owner, + description: this.step4.desc, "data.sources": [ { name: "source", connectors: [ { - name: this.srcname, + name: this.step1.srcname, type: "HIVE", version: "1.2", - "data.unit": this.size, - "data.time.zone": this.timezone, + "data.unit": this.step3.size, + "data.time.zone": this.step3.timezone, config: { - database: this.currentDB, - "table.name": this.currentTable, - where: this.where + database: this.step1.currentDB, + "table.name": this.step1.currentTable, + where: this.step3.config['where'] }, predicates: [ { type: "file.exist", config: { - "root.path": this.srclocation, - path: this.path + "root.path": this.step1.srclocation, + path: this.step3.path } } ] @@ -424,72 +382,26 @@ export class PrComponent implements AfterViewChecked, OnInit { } ], "evaluate.rule": { - rules: [ - // { - // "dsl.type": "griffin-dsl", - // "dq.type": "profiling", - // "rule": "", - // "details": {} - // } - ] + rules: [] } }; + this.getGrouprule(); - if (this.size.indexOf("0") == 0) { + if (this.step3.size.indexOf("0") == 0) { delete this.newMeasure["data.sources"][0]["connectors"][0]["data.unit"]; } - if (!this.needpath || this.path == "") { + if (!this.step3.needpath || this.step3.path == "") { delete this.newMeasure["data.sources"][0]["connectors"][0]["predicates"]; } this.visible = true; setTimeout(() => (this.visibleAnimate = true), 100); } - getRule(trans) { - var rule = ""; - for (let i of trans) { - rule = rule + i + ","; - } - rule = rule.substring(0, rule.lastIndexOf(",")); - this.pushRule(rule); - } - - pushEnmRule(rule, grpname) { - var self = this; - self.newMeasure["evaluate.rule"].rules.push({ - "dsl.type": "griffin-dsl", - "dq.type": "PROFILING", - rule: rule, - name: grpname, - metric: { - "collect.type": "array" - } - }); - } - - pushNullRule(rule, nullname) { - var self = this; - self.newMeasure["evaluate.rule"].rules.push({ - "dsl.type": "griffin-dsl", - "dq.type": "PROFILING", - rule: rule, - name: nullname - }); - } - - pushRule(rule) { - var self = this; - self.newMeasure["evaluate.rule"].rules.push({ - "dsl.type": "griffin-dsl", - "dq.type": "PROFILING", - rule: rule, - name: "profiling" - }); - } - save() { - var addModels = this.serviceService.config.uri.addModels; + let addModels = this.serviceService.config.uri.addModels; + $("#save").attr("disabled", "true"); + this.http.post(addModels, this.newMeasure).subscribe( data => { this.createResult = data; @@ -508,166 +420,12 @@ export class PrComponent implements AfterViewChecked, OnInit { ); } - options: ITreeOptions = { - displayField: "name", - isExpandedField: "expanded", - idField: "id", - actionMapping: { - mouse: { - click: (tree, node, $event) => { - if (node.hasChildren) { - this.currentDB = node.data.name; - this.currentDBstr = this.currentDB + "."; - this.currentTable = ""; - this.schemaCollection = []; - this.selectedAll = false; - TREE_ACTIONS.TOGGLE_EXPANDED(tree, node, $event); - } else if (node.data.cols) { - this.currentTable = node.data.name; - this.currentDB = node.data.parent; - this.schemaCollection = node.data.cols; - this.srcname = "source" + new Date().getTime(); - this.srclocation = node.data.location; - this.selectedAll = false; - this.selection = []; - for (let row of this.schemaCollection) { - row.selected = false; - } - } - } - } - }, - animateExpand: true, - animateSpeed: 30, - animateAcceleration: 1.2 - }; - - nodeList: object[]; - nodeListTarget: object[]; - - constructor( - private elementRef: ElementRef, - toasterService: ToasterService, - private http: HttpClient, - private router: Router, - public serviceService: ServiceService - ) { - this.toasterService = toasterService; - this.selection = []; - } - - getGrouprule() { - var selected = { name: "" }; - var value = ""; - var nullvalue = ""; - var nullname = ""; - var enmvalue = ""; - var grpname = ""; - for (let key in this.selectedItems) { - selected.name = key; - var info = ""; - var otherinfo = ""; - for (let i = 0; i < this.selectedItems[key].length; i++) { - var originrule = this.selectedItems[key][i].itemName; - info = info + originrule + ","; - if (originrule == "Enum Detection Top5 Count") { - enmvalue = this.transferRule(originrule, selected); - grpname = selected.name + "-grp"; - this.transenumrule.push(enmvalue); - this.pushEnmRule(enmvalue, grpname); - } else if (originrule == "Null Count") { - nullvalue = this.transferRule(originrule, selected); - nullname = selected.name + "-nullct"; - this.transnullrule.push(nullvalue); - this.pushNullRule(nullvalue, nullname); - } else { - otherinfo = otherinfo + originrule + ","; - value = this.transferRule(originrule, selected); - this.transrule.push(value); - } - } - info = info.substring(0, info.lastIndexOf(",")); - otherinfo = otherinfo.substring(0, otherinfo.lastIndexOf(",")); - this.noderule.push({ - name: key, - infos: info - }); - } - if (this.transrule.length != 0) { - this.getRule(this.transrule); - } - this.ruledesc = JSON.stringify(this.noderule); - } - - confirmAdd() { - document.getElementById("rule").style.display = "none"; - } - - showRule() { - document.getElementById("showrule").style.display = ""; - document.getElementById("notshowrule").style.display = "none"; - } - - back() { - document.getElementById("showrule").style.display = "none"; - document.getElementById("notshowrule").style.display = ""; - } - - getData(evt) { - this.config = evt; - this.timezone = evt.timezone; - this.where = evt.where; - this.size = evt.num + evt.timetype; - this.needpath = evt.needpath; - this.path = evt.path; - } - ngOnInit() { - var allDataassets = this.serviceService.config.uri.dataassetlist; - this.http.get(allDataassets).subscribe(data => { - this.nodeList = new Array(); - let i = 1; - this.data = data; - for (let db in this.data) { - let new_node = new node(); - new_node.name = db; - new_node.id = i; - new_node.isExpanded = true; - i++; - new_node.children = new Array(); - for (let i = 0; i < this.data[db].length; i++) { - let new_child = new node(); - new_child.name = this.data[db][i]["tableName"]; - new_node.children.push(new_child); - new_child.isExpanded = false; - new_child.location = this.data[db][i]["sd"]["location"]; - new_child.parent = db; - new_child.cols = Array<Col>(); - for (let j = 0; j < this.data[db][i]["sd"]["cols"].length; j++) { - let new_col = new Col( - this.data[db][i]["sd"]["cols"][j].name, - this.data[db][i]["sd"]["cols"][j].type, - this.data[db][i]["sd"]["cols"][j].comment, - false - ); - new_child.cols.push(new_col); - } - } - this.nodeList.push(new_node); - } - this.nodeListTarget = JSON.parse(JSON.stringify(this.nodeList)); - }); - this.dropdownSettings = { - singleSelection: false, - text: "Select Rule", - enableCheckAll: false, - enableSearchFilter: true, - classes: "myclass", - groupBy: "category" - }; - this.size = "1day"; - } - ngAfterViewChecked() { - this.resizeWindow(); + this.step1 = new ProfilingStep1(); + this.step2 = new ProfilingStep2(); + this.step3 = new ProfilingStep3(); + this.step4 = new ProfilingStep4(); } + + ngAfterViewChecked() {} } http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/d58bd194/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.css ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.css b/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.css new file mode 100644 index 0000000..fd725f8 --- /dev/null +++ b/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.css @@ -0,0 +1,141 @@ +/* +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 url('../../../../../../node_modules/angular2-toaster/toaster.css'); +@import url('../../../measure.component.css'); + +fieldset { + height: 60vh; +} + +label.stepDesc { + padding-left: 30px; + padding-right: 30px; +} + +div.btn-container { + width: 100%; + padding-left: 30px; + padding-right: 30px; + clear: both; +} + +div.tree div.tree-children::before, +div.tree::before { + content: ""; + position: absolute; + border-left: 1px dotted #23527c; + height: 100%; + top: -14px; + left: 12px +} + +tree-root { + color: #999; +} + +div.tree { + padding-left: 0; + margin-left: -5px +} + +div.tree div.tree-children { + position: relative; + padding-left: 0; + margin-left: 16px +} + +div.tree div.tree-children::before { + left: 5px +} + +div.tree treenode>div>.node-wrapper { + margin-left: 24px +} + +div.tree treenode>div>.node-wrapper>.node-content-wrapper { + margin-left: 4px +} + +div.tree treenode>div.tree-node-leaf>.node-wrapper { + margin-left: 0 +} + +div.tree treenode>div::before { + content: ""; + position: absolute; + border-bottom: 1px dotted #23527c; + width: 7px; + margin-top: 12px; + left: 7px +} + +div.tree treenode>div .toggle-children-wrapper { + width: 13px; + height: 13px; + border: 1px solid #23527c; + position: absolute; + left: 15px; + margin-top: 5px; + margin-left: 0; + display: inline-block; + background-color: #fff; + z-index: 1 +} + +div.tree treenode>div .toggle-children-wrapper::before { + content: ""; + display: inline-block; + width: 7px; + border-top: 1px solid #23527c; + position: absolute; + top: 5px; + left: 2px +} + +div.tree treenode>div .toggle-children-wrapper.toggle-children-wrapper-collapsed::after { + content: ""; + display: inline-block; + height: 7px; + border-left: 1px solid #23527c; + position: absolute; + top: 2px; + left: 5px +} + +div.tree treenode>div .toggle-children-wrapper .toggle-children { + display: none +} + +div.tree treenode>div .node-content-wrapper { + margin-left: 4px +} + +div.tree>treenode>div::before { + left: 14px +} + +div.tree>treenode>div>.node-wrapper>treenodeexpander>.toggle-children-wrapper { + left: 22px +} + +table > tbody > tr > td, .table > thead > tr > th { + word-wrap: break-word; + min-width: 80px; +} http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/d58bd194/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.html ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.html b/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.html new file mode 100644 index 0000000..4c9eb99 --- /dev/null +++ b/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.html @@ -0,0 +1,78 @@ +<!-- +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. +--> +<div class="container-fluid"> + <form name="Form" id="form" #prForm="ngForm" novalidate> + <label class="stepDesc">This step lets you select the single source of truth for data quality comparision with a target source.</label> + <div class="container-fluid"> + <div class="col-md-4 col-lg-4 col-sm-4"> + <fieldset> + <legend>Please select schema</legend> + <tree-root [nodes]="step1.nodeList" [options]="options"></tree-root> + </fieldset> + </div> + <div class="col-md-8 col-lg-8 col-sm-8"> + <fieldset> + <legend> + Select attributes + </legend> + <div class="y-scrollable"> + <div style="margin-top:10px;"> + <label>View schema:</label> + <i style="color:#fff;font-weight: bold;">{{step1.currentDBstr}}{{step1.currentTable}} + </i> + </div> + <div style="margin-top:5px;"> + <table class="table table-striped no-border"> + <thead> + <tr style="background-color:#7D95CC"> + <th> + <input type="checkbox" (click)="toggleAll()" [checked]="selectedAll" /> + </th> + <th>Column Name</th> + <th>Type</th> + <th>Comment</th> + </tr> + </thead> + <tbody> + <tr *ngIf="!step1.schemaCollection || step1.schemaCollection.length == 0"> + <td colspan="5" style="text-align:center;"><span class="highlight">Please select a schema from the left tree first</span></td> + </tr> + <tr *ngFor="let row of step1.schemaCollection"> + <td> + <input type="checkbox" (click)='toggleSelection(row)' [checked]="row.selected" value={{row.name}} /> + </td> + <td>{{row.name}}</td> + <td>{{row.type}}</td> + <td>{{row.comment}}</td> + </tr> + </tbody> + </table> + </div> + </div> + </fieldset> + </div> + </div> + </form> + + <div class="form-group btn-container"> + <button class="btn btn-primary btn-o next-step btn-wide pull-right" (click)="nextChildStep()"> + Next <i class="fa fa-arrow-circle-right"></i> + </button> + </div> +</div> http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/d58bd194/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.spec.ts ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.spec.ts b/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.spec.ts new file mode 100644 index 0000000..13d47d4 --- /dev/null +++ b/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.spec.ts @@ -0,0 +1,43 @@ +/* +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 { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PrStep1Component } from './step1.component'; + +describe('PrStep1Component', () => { + let component: PrStep1Component; + let fixture: ComponentFixture<PrStep1Component>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PrStep1Component ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PrStep1Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +});