http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/42ee8863/ui/angular/src/app/measure/create-measure/ac/ac.component.ts ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/ac/ac.component.ts b/ui/angular/src/app/measure/create-measure/ac/ac.component.ts new file mode 100644 index 0000000..122d7b7 --- /dev/null +++ b/ui/angular/src/app/measure/create-measure/ac/ac.component.ts @@ -0,0 +1,594 @@ +/* +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, OnInit } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { FormsModule, Validator} from '@angular/forms'; +import {ServiceService} from '../../../service/service.service'; +// import { PatternValidator } from '@angular/forms'; + + +import { TREE_ACTIONS, KEYS, IActionMapping, ITreeOptions } from 'angular-tree-component'; +import { BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import { ToasterModule, ToasterService} from 'angular2-toaster'; +import * as $ from 'jquery'; +import { HttpClient} from '@angular/common/http'; +import { Router} from "@angular/router"; + + +class node { + name: string; + id: number; + children:object[]; + isExpanded:boolean; + cols:Col[]; + parent:string; +}; +class Col{ + name:string; + type:string; + comment:string; + selected :boolean; + constructor(name:string,type:string,comment:string,selected:boolean){ + this.name = name; + this.type = type; + this.comment = comment; + this.selected = false; + } + getSelected(){ + return this.selected; + } + setSelected(selected){ + this.selected = selected; + } +} + +@Component({ + selector: 'app-ac', + templateUrl: './ac.component.html', + providers:[ServiceService], + styleUrls: ['./ac.component.css'] +}) + +export class AcComponent implements OnInit { + + currentStep = 1; + org:string; + desc:string; + selection = []; + selectedAll = false; + selectedAllTarget = false; + selectionTarget = []; + map = []; + mappings = []; + matches = []; + dataAsset = ''; + rules = ''; + currentDB = ''; + currentTable = ''; + currentDBTarget = ''; + currentTableTarget = ''; + schemaCollection:Col[]; + schemaCollectionTarget:Col[]; + matchFunctions = ['=', '!=', '>', '>=','<',"<="]; + data:any; + + measureTypes = ['accuracy','validity','anomaly detection','publish metrics']; + type = 'accuracy'; + newMeasure = { + "name":'', + "process.type": "batch", + "owner":"", + "description":"", + "organization":"", + "data.sources": [ + { + "name": "source", + "connectors": [ + { + "type": "HIVE", + "version": "1.2", + "config":{ + "database":'', + "table.name":'', + } + } + ] + }, { + "name": "target", + "connectors": [ + { + "type": "HIVE", + "version": "1.2", + "config":{ + "database":'', + "table.name":'', + } + } + ] + } + ], + + "evaluateRule":{ + "rules": [ + { + "dsl.type": "griffin-dsl", + "dq.type": "accuracy", + "rule": "" + // "details": { + // "source": "source", + // "target": "target", + // "miss.records": { + // "name": "miss.records", + // "persist.type": "record" + // }, + // "accuracy": { + // "name": "accu", + // "persist.type": "metric" + // }, + // "miss": "miss", + // "total": "total", + // "matched": "matched" + // } + } + ] + } + }; + name:''; + evaluateRule:any; + // desc:''; + // org:''; + owner = 'test'; + createResult :any; + + private toasterService: ToasterService; + public visible = false; + public visibleAnimate = false; + + public hide(): void { + this.visibleAnimate = false; + setTimeout(() => this.visible = false, 300); + } + + public onContainerClicked(event: MouseEvent): void { + if ((<HTMLElement>event.target).classList.contains('modal')) { + this.hide(); + } + } + + addMapping(x,i){ + this.mappings[i] = x; + } + + toggleSelection (row) { + row.selected = !row.selected; + var idx = this.selection.indexOf(row.name); + // is currently selected + if (idx > -1) { + this.selection.splice(idx, 1); + this.selectedAll = false; + } + // is newly selected + else { + this.selection.push(row.name); + } + }; + + toggleSelectionTarget (row) { + row.selected = !row.selected; + var idx = this.selectionTarget.indexOf(row.name); + // is currently selected + if (idx > -1) { + this.selectionTarget.splice(idx, 1); + this.selectedAllTarget = false; + } + // is newly selected + else { + this.selectionTarget.push(row.name); + } + let l = this.selectionTarget.length; + for(let i =0;i<l;i++) + this.matches[i] = "="; + }; + + 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].name); + this.matches[i] = "="; + } + } + }; + + toggleAllTarget () { + this.selectedAllTarget = !this.selectedAllTarget; + this.selectionTarget = []; + for(var i =0; i < this.schemaCollectionTarget.length; i ++){ + this.schemaCollectionTarget[i].selected = this.selectedAllTarget; + if (this.selectedAllTarget) { + this.selectionTarget.push(this.schemaCollectionTarget[i].name); + } + } + }; + + next (form) { + if(this.formValidation(this.currentStep)){ + this.currentStep++; + }else{ + this.toasterService.pop('error','Error!','Please select at least one attribute!'); + return false; + } + } + + formValidation = function(step) { + if (step == undefined) { + step = this.currentStep; + } + if (step == 1) { + return this.selection && this.selection.length > 0; + } else if (step == 2) { + return (this.selectionTarget && this.selectionTarget.length > 0)//at least one target is selected + // && !((this.currentTable.name == this.currentTableTarget.name)&&(this.currentDB.name == this.currentDBTarget.name));//target and source should be different + } else if (step == 3) { + return this.selectionTarget && this.selectionTarget.length == this.mappings.length + && this.mappings.indexOf('') == -1 + } else if (step == 4) { + } + return false; + } + + prev (form) { + this.currentStep--; + } + goTo (i) { + this.currentStep = i; + } + submit (form) { + // form.$setPristine(); + if (!form.valid) { + this.toasterService.pop('error', 'Error!', 'please complete the form in this step before proceeding'); + return false; + } + var rule = ''; + this.newMeasure = { + "name":this.name, + "process.type": "batch", + "owner":this.owner, + "description":this.desc, + "organization":this.org, + "data.sources": [ + { + "name": "source", + "connectors": [ + { + "type": "HIVE", + "version": "1.2", + "config":{ + "database":this.currentDB, + "table.name":this.currentTable, + } + } + ] + }, { + "name": "target", + "connectors": [ + { + "type": "HIVE", + "version": "1.2", + "config":{ + "database":this.currentDBTarget, + "table.name":this.currentTableTarget, + } + } + ] + } + ], + + "evaluateRule":{ + "rules": [ + { + "dsl.type": "griffin-dsl", + "dq.type": "accuracy", + "rule": "" + // "details": { + // "source": "source", + // "target": "target", + // "miss.records": { + // "name": "miss.records", + // "persist.type": "record" + // }, + // "accuracy": { + // "name": "accu", + // "persist.type": "metric" + // }, + // "miss": "miss", + // "total": "total", + // "matched": "matched" + // } + } + ] + } + }; + var mappingRule = function(src, tgt, matches) { + var rules; + rules = 'source.' + src + matches + 'target.' + tgt + return rules; + } + var self = this; + var rules = this.selectionTarget.map(function(item, i) { + return mappingRule(self.selection[i], item, self.matches[i]); + }); + rule = rules.join(" AND "); + this.rules = rule; + this.newMeasure.evaluateRule.rules[0].rule = rule; + // for(var i =0; i < this.selectionTarget.length; i ++){ + // this.newMeasure.mappings.push({target:this.selectionTarget[i], + // // src:this.mappings[i], + // matchMethod: this.matches[i]}); + // } + this.visible = true; + setTimeout(() => this.visibleAnimate = true, 100); + } + + save() { + + var addModels = this.servicecService.config.uri.addModels; + + this.http + .post(addModels, this.newMeasure) + .subscribe(data => { + this.createResult = data; + this.hide(); + this.router.navigate(['/measures']); + // var self = this; + // setTimeout(function () { + // self.hide(); + // self.router.navigate(['/measures']); + // },0) + }, + err => { + console.log('Something went wrong!'); + }); + + } + + // data: { [key: string]: Array<object>; } = { + // "default": [ + // { + // "tableName": "ext", + // "dbName": "default", + // "owner": "hadoop", + // "createTime": 1488353464, + // "lastAccessTime": 0, + // "retention": 0, + // "sd": { + // "cols": [ + // { + // "name": "id", + // "type": "int", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // }, + // { + // "name": "name", + // "type": "string", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // }, + // { + // "name": "age", + // "type": "int", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // } + // ], + // "location": "hdfs://10.9.246.187/user/hive/ext", + // }, + // }, + // { + // "tableName": "ext1", + // "dbName": "default", + // "owner": "hadoop", + // "createTime": 1489382943, + // "lastAccessTime": 0, + // "retention": 0, + // "sd": { + // "cols": [ + // { + // "name": "id", + // "type": "int", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // }, + // { + // "name": "name", + // "type": "string", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // }, + // { + // "name": "age", + // "type": "int", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // } + // ], + // "location": "hdfs://10.9.246.187/user/hive/ext1", + // }, + // } + // ], + // "griffin": [ + // { + // "tableName": "avr_out", + // "dbName": "griffin", + // "owner": "hadoop", + // "createTime": 1493892603, + // "lastAccessTime": 0, + // "retention": 0, + // "sd": { + // "cols": [ + // { + // "name": "id", + // "type": "bigint", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // }, + // { + // "name": "age", + // "type": "int", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // }, + // { + // "name": "desc", + // "type": "string", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // } + // ], + // "location": "hdfs://10.9.246.187/griffin/data/batch/avr_out", + // }, + // } + // ], + // }; + + options: ITreeOptions = { + displayField: 'name', + isExpandedField: 'expanded', + idField: 'id', + actionMapping: { + mouse: { + click: (tree, node, $event) => { + if (node.hasChildren) { + this.currentDB = node.data.name; + this.currentTable = ''; + 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; + } + } + } + }, + animateExpand: true, + animateSpeed: 30, + animateAcceleration: 1.2 + }; + + targetOptions: ITreeOptions = { + displayField: 'name', + isExpandedField: 'expanded', + idField: 'id', + actionMapping: { + mouse: { + click: (tree, node, $event) => { + if (node.hasChildren) { + this.currentDBTarget = node.data.name; + this.currentTableTarget = ''; + TREE_ACTIONS.TOGGLE_EXPANDED(tree, node, $event); + } + else if(node.data.cols) + { + this.currentTableTarget = node.data.name; + this.currentDBTarget = node.data.parent; + this.schemaCollectionTarget = node.data.cols; + } + } + } + }, + animateExpand: true, + animateSpeed: 30, + animateAcceleration: 1.2 + }; + + nodeList:object[]; + nodeListTarget:object[]; + constructor(toasterService: ToasterService,private http: HttpClient,private router:Router,public servicecService:ServiceService) { + this.toasterService = toasterService; + }; + + onResize(event){ + console.log("Width: " + event.target.innerWidth); + this.resizeWindow(); + } + + resizeWindow(){ + var stepSelection = '.formStep[id=step-' + this.currentStep + ']'; + $(stepSelection).css({ + height: window.innerHeight - $(stepSelection).offset().top + }); + } + + ngOnInit() { + var allDataassets = this.servicecService.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.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)); + }); + + }; +}
http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/42ee8863/ui/angular/src/app/measure/create-measure/create-measure.component.css ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/create-measure.component.css b/ui/angular/src/app/measure/create-measure/create-measure.component.css new file mode 100644 index 0000000..4e3c6f7 --- /dev/null +++ b/ui/angular/src/app/measure/create-measure/create-measure.component.css @@ -0,0 +1,123 @@ +/* +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'); + +div.tree div.tree-children::before, +div.tree::before { + content: ""; + position: absolute; + border-left: 1px dotted #23527c; + height: 100%; + top: -14px; + left: 12px +} + +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 +} + +.panel{ + background-color: #222222; +} + +.panel-footer{ + background-color: #3c3c3c; +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/42ee8863/ui/angular/src/app/measure/create-measure/create-measure.component.html ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/create-measure.component.html b/ui/angular/src/app/measure/create-measure/create-measure.component.html new file mode 100644 index 0000000..fd8bdcb --- /dev/null +++ b/ui/angular/src/app/measure/create-measure/create-measure.component.html @@ -0,0 +1,134 @@ +<!-- +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" id="main" > + <div class="row"> + <h5 class="over-title margin-bottom-15">Create DQ Model</h5> + </div> + <div class="row"> + <div class="col-lg-6 col-md-6 col-sm-6 ruletypes"> + <section id="panel-1" class="panel panel-red" style="cursor:pointer" (click)="click('ac')"> + <div class="panel-heading"> + <span style="font-size:20px">Accuracy</span> + <span class="pull-right" style="font-size:20px"> + <span class="fa fa-arrow-circle-right" (click)="click('ac')"></span> + </span> + </div> + <div class="swMain panel-body" > + <label class="label-definition">Definition: Measured by how the values agree with an identified source of truth</label> + <ul style="border-radius:0; background: none"> + <li > + <a class="selected" > + <div class="stepNumber"> + 1 + </div> + <span class="stepDesc text-small"> Choose Source </span> + </a> + </li> + <li> + <a> + <div class="stepNumber"> + 2 + </div> + <span class="stepDesc text-small"> Choose Target </span> + </a> + </li> + <li > + <a> + <div class="stepNumber"> + 3 + </div> + <span class="stepDesc text-small"> Mapping Source and Target </span> + </a> + </li> + <li> + <a> + <div class="stepNumber"> + 4 + </div> + <span class="stepDesc text-small"> Configuration </span> + </a> + </li> + </ul> + <div> + <ol> + <li>Select the source dataset and fields which will be used for comparision</li> + <li>Select the target dataset and fields which will be used for comparision</li> + <li>Mapping the target fields with source</li> + <li>Set basic configuration for your model (name, system, threshold, etc.)</li> + </ol> + </div> + </div> + <div class="panel-footer stepDesc"> + <label>Example:</label> suppose source table A has 1000 records and target table B only has 999 records pefectly matched with A for selected fields, then + Accuracy Rate(%) = 999/1000 * 100% = 99.9% + </div> + </section> + </div> + <div class="col-lg-6 col-md-6 col-sm-6 ruletypes"> + <section id="panel-2" class="panel panel-primary" (click)="click('pr')"> + <div class="panel-heading"> + <span style="font-size:20px">Data Profiling</span> + <span class="pull-right" style="font-size:20px"> + <span class="fa fa-arrow-circle-right"></span> + </span> + </div> + <div class="swMain panel-body" > + <label class="label-definition">Definition: Data profiling is the process of examining the data available in an existing data set and collecting statistics and information about that data</label> + <ul style="border-radius:0; background: none"> + <li > + <a class="selected" style="cursor:default"> + <div class="stepNumber"> + 1 + </div> + <span class="stepDesc text-small"> Choose Target </span> + </a> + </li> + <li> + <a style="cursor:default" > + <div class="stepNumber"> + 2 + </div> + <span class="stepDesc text-small">Define/Select Models </span> + </a> + </li> + <li > + <a style="cursor:default" > + <div class="stepNumber"> + 3 + </div> + <span class="stepDesc text-small"> Configuration </span> + </a> + </li> + </ul> + <div> + <ol> + <li>Select the target dataset and fields which want to be checked</li> + <li>Define your syntax check logic which will be applied on the selected fields</li> + <li>Set basic configuration for your model(name, system, threshold, etc.)</li> + </ol> + </div> + </div> + <div class="panel-footer stepDesc"> + <label>Example:</label> Check the data range(minimum, maximum) within a set of allowable values + </div> + </section> + </div> + </div><!--//row--> +</div> http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/42ee8863/ui/angular/src/app/measure/create-measure/create-measure.component.spec.ts ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/create-measure.component.spec.ts b/ui/angular/src/app/measure/create-measure/create-measure.component.spec.ts new file mode 100644 index 0000000..6e073a3 --- /dev/null +++ b/ui/angular/src/app/measure/create-measure/create-measure.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 { CreateMeasureComponent } from './create-measure.component'; + +describe('CreateMeasureComponent', () => { + let component: CreateMeasureComponent; + let fixture: ComponentFixture<CreateMeasureComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CreateMeasureComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CreateMeasureComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/42ee8863/ui/angular/src/app/measure/create-measure/create-measure.component.ts ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/create-measure.component.ts b/ui/angular/src/app/measure/create-measure/create-measure.component.ts new file mode 100644 index 0000000..ea89cf2 --- /dev/null +++ b/ui/angular/src/app/measure/create-measure/create-measure.component.ts @@ -0,0 +1,52 @@ +/* +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, OnInit } from '@angular/core'; +import { FormControl} from '@angular/forms'; +import { FormsModule } from '@angular/forms'; + +import { TREE_ACTIONS, KEYS, IActionMapping, ITreeOptions } from 'angular-tree-component'; +import { BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import { ToasterModule, ToasterService, ToasterConfig} from 'angular2-toaster'; +import * as $ from 'jquery'; +import { HttpClient} from '@angular/common/http'; +import { Router} from "@angular/router"; + +@Component({ + selector: 'app-create-measure', + templateUrl: './create-measure.component.html', + styleUrls: ['./create-measure.component.css'] +}) +export class CreateMeasureComponent implements OnInit { + + constructor(private router:Router) { } + + + ngOnInit() { + $('#panel-2 >.panel-body').css({height: $('#panel-1 >.panel-body').outerHeight() + $('#panel-1 >.panel-footer').outerHeight() - $('#panel-2 >.panel-footer').outerHeight()}); + $('#panel-4 >.panel-body').css({height: $('#panel-3 >.panel-body').outerHeight() + $('#panel-3 >.panel-footer').outerHeight() - $('#panel-4 >.panel-footer').outerHeight()}); + + } + click(type){ + this.router.navigate(['/createmeasure'+type]); + } + + +} + + http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/42ee8863/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 new file mode 100644 index 0000000..d7c4eb6 --- /dev/null +++ b/ui/angular/src/app/measure/create-measure/pr/pr.component.css @@ -0,0 +1,156 @@ +/* +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'); + +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 +} + +label{ + font-weight: normal; +} +.container{ + 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; +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/42ee8863/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 new file mode 100644 index 0000000..d667a80 --- /dev/null +++ b/ui/angular/src/app/measure/create-measure/pr/pr.component.html @@ -0,0 +1,392 @@ +<!-- +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"> + <div class="row"> + <h5 class="over-title margin-bottom-15">Create Measure</h5> + </div> + <div class="row"> + <form name="Form" id="form" (ngSubmit)="submit(prForm)" #prForm="ngForm" novalidate> + <div id="wizard" class="swMain" > + <ul> + <li (click)="goTo(1)" > + <a [ngClass]="{'selected' : currentStep >= 1, 'done' : currentStep > 1}" class="selected"> + <div class="stepNumber"> + 1 + </div> + <span class="stepDesc text-small"> Choose Source </span> + </a> + </li> + <li (click)="goTo(2)"> + <a [ngClass]="{'selected' : currentStep >= 2, 'done' : currentStep > 2}" class="" style=""> + <div class="stepNumber"> + 2 + </div> + <span class="stepDesc text-small"> Select Models </span> + </a> + </li> + <li (click)="goTo(3)"> + <a [ngClass]="{'selected' : currentStep >= 3, 'done' : currentStep > 3}"> + <div class="stepNumber"> + 3 + </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> + <label>View schema:</label> + <i + style="color:#fff;font-weight: bold;">{{currentDB}}.{{currentTable}} + </i> + </div> + <div> + <table class="table table-striped"> + <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> + </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"> + <div class="col-md-12 col-lg-12 col-sm-12"> + <fieldset> + <div class="y-scrollable"> + <div class="container col-md-12 col-lg-12 col-sm-12"> + <div class="row"> + <div class="col-md-12 col-lg-12 col-sm-12" *ngFor="let item of selection;let itemIndex = index" > + <p style="font-weight: bold;font-size: 120%;cursor:pointer" + (click)="item.isExpanded = !item.isExpanded"> + <i *ngIf="!item.isExpanded" class="fa fa-chevron-circle-right blue"></i> + <i *ngIf="item.isExpanded" class="fa fa-chevron-circle-down blue"></i> {{currentDB}}.{{currentTable}}.{{item.name}} + <i>:{{item.ruleLength}} rules</i> + </p> + <div *ngIf="item.isExpanded"> + <div id="simple{{itemIndex}}"> + <h5>Simple Statistics</h5> + <div *ngFor="let rule of item.newRules;let ruleIndex = index" > + <div *ngIf="rule!=null"> + <a class="btn btn-primary btn-o" style="background-color: #337ab7;" + (click)="removeRule(item,ruleIndex)"> + <i class="fa fa-times"></i> + </a> + <label style="width: 7%;text-align:center;">Rule:</label> + <select class="form-control" style="width: 25%;display: inline-block;" + id="simpleRule" name='simpleRule-{{itemIndex}}{{ruleIndex}}' [(ngModel)]='rule.type'> + <option>Total Count</option> + <option>Distinct Count</option> + <option>Null Detection Count</option> + <option *ngIf="item.type=='string'||item.type=='varchar'||item.type=='char'">Regular Expression Detection Count</option> + <option>Rule Detection Count</option> + <option *ngIf='item.isNum'>Maximum</option> + <option *ngIf='item.isNum'>Minimum</option> + <option *ngIf='item.isNum'>Median</option> + <option *ngIf='item.isNum'>Average</option> + <option>Enum Detection Count</option> + </select> + <!-- <a class="btn btn-primary btn-o" style="background-color: #337ab7;" + (click)="addCond(item,ruleIndex)"> + <i class="fa fa-plus"></i> + </a> --> + <div style="display: inline-block;font-size: 130%;vertical-align: middle;" *ngFor="let cond of rule.conditionGroup;let condIndex=index"> + <input type="checkbox" + name="condition-{{itemIndex}}{{ruleIndex}}{{condIndex}}" + value="cond.type" + (click)='toggleSelectionCond(cond,condIndex,ruleIndex,item)' + [disabled]="!cond.avaliable" [(ngModel)]="cond.chosen" style="width:18px;height: 18px;"> + <!-- <span class="badge">✓</span> --> + <label>{{cond.type}}</label> + <br> + </div> + <div *ngFor="let cond of rule.conditionGroup;let condIndex=index" style="margin-left:12%;"> + <label style="width: 7%;" *ngIf="cond.chosen">{{cond.type}}</label> + <input *ngIf="cond.chosen" + name="conditionDetail-{{itemIndex}}{{ruleIndex}}{{condIndex}}" type="text" class="form-control" style="width: 40%;display: inline-block;" [(ngModel)]="cond.content"/> + </div> + + </div> + + </div> + + <a class="btn btn-primary btn-o" style="background-color: #337ab7;" + (click)="addRule(item)"> + <i class="fa fa-plus"></i> + </a> + <br> + </div> + </div> + </div> + <!-- <div class="col-md-9 col-lg-9 col-sm-9" ng-include="'/pages/rules/va-explaination.html'"> + </div> --> + </div> + </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> + + <div id="step-3" *ngIf="currentStep == 3" 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"> + <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 for="typeSelector" class="col-md-2 col-lg-2 col-sm-2 control-label"> + Measure Type: + </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 for="systemSelector" class="col-md-2 col-lg-2 col-sm-2 control-label"> + Organization: + </label> + <div class="col-md-10 col-lg-10 col-sm-10 "> + <input type="text" id="systemSelector" class="form-control" required ng-pattern="'([0-9a-zA-Z\\_\\-])+'" name="org" value="{{currentDB}}" disabled> + </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"> + DataAsset: + </label> + <div class="col-md-10 col-lg-10 col-sm-10"> + <input type="text" class="form-control" name="DataAsset" + value="{{currentTable}}" disabled> + </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" class="btn btn-primary btn-o next-step btn-wide pull-right"> + Submit + </button> + </div> + </div> + </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" data-dismiss="modal" aria-hidden="true">×</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 Type: + </label> + <div class="col-md-8 col-lg-8 col-sm-8 "> + {{type}} + </div> + </div> + <div class="row"> + <label for="systemSelector" class="col-md-4 col-lg-4 col-sm-4"> + Organization: + </label> + <div class="col-md-8 col-lg-8 col-sm-8 "> + {{currentDB}} + </div> + </div> + <div class="row"> + <label class="col-md-4 col-lg-4 col-sm-4"> + DataAsset: + </label> + <div class="col-md-8 col-lg-8 col-sm-8"> + {{currentTable}} + </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 newRule of newMeasure.evaluateRule.rules"> + {{newRule.rule}} + </div> + <br/> + <!-- <h5 class="row">Mapping rules</h5> --> + <!-- <div class="row"> + <p>{{rules}}</p> + <p> + <label style="color:#B2C831">Accuracy Calculation Formula as Below:</label> + </p> + <div class="col-md-12 col-lg-12 col-sm-12" style="color:#fff;font-size:16px;display: flex; align-items: center"> + <div class="" style="text-align:right;display:block;float:left;width:20%;"> + Accuracy Rate(%) = + </div> + <div class="" style="text-align:center;display:block;float:left;margin:0 10px 0 10px"> + <div class="formula-text-up" style="border-bottom:1px solid;"> + Total Count of Matched records between <span class="badge">{{selectionTarget.length}}</span> <span style="color:green;">{{currentTableTarget}}</span> and <span class="badge">{{mappings.length}} + </span> <span style="color:green;">{{currentTable}}</span> fields + </div> + <div> + Total Count of records in <span style="color:green;font-weight:bold;">{{currentDB}}.{{currentTable}}</span> + </div> + </div> + <div class="" style="text-align:left;display:block;float:left;width:10%;"> + x 100% + </div> + </div> + </div> --> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" (click)="hide()">Cancel</button> + <button type="button" class="btn btn-primary" (click)="save()">Save</button> + </div> + </div> + </div> + </div> + </form> + </div> +</div> http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/42ee8863/ui/angular/src/app/measure/create-measure/pr/pr.component.spec.ts ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/create-measure/pr/pr.component.spec.ts b/ui/angular/src/app/measure/create-measure/pr/pr.component.spec.ts new file mode 100644 index 0000000..dcc2d00 --- /dev/null +++ b/ui/angular/src/app/measure/create-measure/pr/pr.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 { PrComponent } from './pr.component'; + +describe('PrComponent', () => { + let component: PrComponent; + let fixture: ComponentFixture<PrComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PrComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PrComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/42ee8863/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 new file mode 100644 index 0000000..4e80080 --- /dev/null +++ b/ui/angular/src/app/measure/create-measure/pr/pr.component.ts @@ -0,0 +1,580 @@ +/* +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, 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"; + + +class node { + name: string; + id: number; + children:object[]; + isExpanded:boolean; + cols:Col[]; + parent:string; +}; + +class Rule{ + type:string; + conditionGroup = [ + { + 'type':'where', + 'content':'', + 'chosen':false, + 'avaliable':true + }, + { + 'type':'groupby', + 'content':'', + 'chosen':false, + 'avaliable':true + }, + { + 'type':'having', + 'content':'', + 'chosen':false, + 'avaliable':false + }, + { + 'type':'orderby', + 'content':'', + 'chosen':false, + 'avaliable':true + }, + { + 'type':'limit', + 'content':'', + 'chosen':false, + 'avaliable':true + } + ]; +} + +class Col{ + name:string; + type:string; + comment:string; + selected :boolean; + isNum:boolean; + isExpanded:boolean; + // rules:string[]; + groupby:string; + RE:string; + 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.RE = ''; + this.newRules = [ + ]; + + var patt = new RegExp('int|double|float/i'); + if(patt.test(this.type)){ + this.isNum = true; + } + // this.rules = []; + } +} + +@Component({ + selector: 'app-pr', + templateUrl: './pr.component.html', + providers:[ServiceService], + styleUrls: ['./pr.component.css'] +}) +export class PrComponent implements OnInit { + + currentStep = 1; + firstCond = false; + selection : Col[]; + selectedAll = false; + rules = ''; + currentDB = ''; + currentTable = ''; + schemaCollection:Col[]; + totallen = 0; + type = 'profiling'; + data:any; + newMeasure = { + "name": "", + "process.type": "batch", + "data.sources": [ + { + "name": "source", + "connectors": [ + { + "type": "hive", + "version": "1.2", + "config": { + "database": "", + "table.name":"" + } + } + ] + } + ], + "evaluateRule": { + "rules": [ + { + "dsl.type": "griffin-dsl", + "dq.type": "profiling", + "rule": "" + // "details": {} + } + ] + } + }; + name:''; + createResult :any; + newCond:any; + + private toasterService: ToasterService; + public visible = false; + public visibleAnimate = false; + + public hide(): void { + this.visibleAnimate = false; + setTimeout(() => this.visible = false, 300); + } + + public onContainerClicked(event: MouseEvent): void { + if ((<HTMLElement>event.target).classList.contains('modal')) { + this.hide(); + } + } + + toggleSelectionCond(cond,condIndex,ruleIndex,item){ + cond.chosen = !cond.chosen; + if(condIndex==1&&cond.chosen) + item.newRules[ruleIndex].conditionGroup[2].avaliable = true; + if(condIndex==1&&!cond.chosen){ + item.newRules[ruleIndex].conditionGroup[2].avaliable = false; + item.newRules[ruleIndex].conditionGroup[2].chosen = false; + } + } + + toggleSelection (row) { + row.selected = !row.selected; + console.log(row); + var idx = this.selection.indexOf(row); + // is currently selected + if (idx > -1) { + this.selection.splice(idx, 1); + this.selectedAll = false; + } + // is newly selected + else { + this.selection.push(row); + } + }; + + 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]); + } + } + }; + + transferRule(rule,col){ + switch(rule){ + case 'Total Count': + return 'count(source.'+col.name+') '; + case 'Distinct Count': + return 'distinct count(source.'+col.name+') '; + case 'Null Detection Count': + return 'count(source.'+col.name+') 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 'Maxium': + return 'max(source.'+col.name+') '; + case 'Minimum': + return 'min(source.'+col.name+') '; + case 'Median': + return 'median(source.'+col.name+') '; + case 'Average': + return 'average(source.'+col.name+') '; + case 'Enum Detection Count': + return 'source.'+col.name+' group by source.'+col.name+''; + // case 'Groupby Count': + // return 'source.'+col.name+' group by source.'+col.name+''; + // case 'total count': + // return 'SELECT COUNT(*) FROM source'; + // case 'distinct count': + // return 'SELECT DISTINCT COUNT(source.'+col.name+') FROM source'; + // case 'null detection count': + // return 'SELECT COUNT(source.'+col.name+') FROM source WHERE source.'+col.name+' is null'; + // case 'regular expression detection count': + // return 'SELECT COUNT(source.'+col.name+') FROM source WHERE source.'+col.name+' like '+col.RE; + // case 'rule detection count': + // return 'SELECT COUNT(source.'+col.name+') FROM source WHERE source.'+col.name+' like '; + // case 'max': + // return 'SELECT max(source.'+col.name+') FROM source'; + // case 'min': + // return 'SELECT min(source.'+col.name+') FROM source'; + // case 'median': + // return 'SELECT median(source.'+col.name+') FROM source'; + // case 'avg': + // return 'SELECT average(source.'+col.name+') FROM source'; + // case 'enum detection group count': + // return 'source.'+col.name+' group by source.'+col.name+''; + // case 'groupby count': + // return 'source.'+col.name+' group by source.'+col.name+' '+col.groupby; + } + } + + addCond(item,ruleIndex){ } + + addRule(item){ + item.ruleLength++; + let newRule = new Rule(); + item.newRules.push(newRule); + } + + removeRule(item,ruleIndex){ + item.ruleLength--; + item.newRules[ruleIndex] = null; + } + + next (form) { + if(this.formValidation(this.currentStep)){ + this.currentStep++; + }else{ + this.toasterService.pop('error','Error!','Please select at least one attribute!'); + return false; + } + } + + formValidation = function(step) { + if (step == undefined) { + step = this.currentStep; + } + if (step == 1) { + return this.selection && this.selection.length > 0; + } else if (step == 2) { + for(let item of this.selection){ + this.totallen = this.totallen + item.newRules.length; + } + return (this.totallen > 0) + } else if (step == 3) { + } + return false; + } + + prev (form) { + this.currentStep--; + } + goTo (i) { + this.currentStep = i; + } + submit (form) { + // form.$setPristine(); + // if (!form.valid) { + // this.toasterService.pop('error', 'Error!', 'please complete the form in this step before proceeding'); + // return false; + // } + this.newMeasure = { + "name": this.name, + "process.type": "batch", + "data.sources": [ + { + "name": "source", + "connectors": [ + { + "type": "hive", + "version": "1.2", + "config": { + "database": this.currentDB, + "table.name":this.currentTable + } + } + ] + } + ], + "evaluateRule": { + "rules": [ + { + "dsl.type": "griffin-dsl", + "dq.type": "profiling", + "rule": "" + // "details": {} + } + ] + } + }; + + var self = this; + var rule = ''; + for(let item of this.selection){ + for(let itemRule of item.newRules){ + console.log(self.transferRule(itemRule.type,item)); + if(itemRule.conditionGroup[0].chosen==true){ + let whereRule = self.transferRule(itemRule.type,item); + for(let condition of itemRule.conditionGroup){ + if(condition.content!='') + whereRule = whereRule + condition.type + ' ' + condition.content + ','; + } + self.newMeasure.evaluateRule.rules.push({ + "dsl.type": "griffin-dsl", + "dq.type": "profiling", + "rule": whereRule, + // "details": {} + }); + } + else { + let normalRule = self.transferRule(itemRule.type,item); + for(let condition of itemRule.conditionGroup){ + if(condition.content!='') + normalRule = normalRule + ' '+ condition.type + ' ' + condition.content + ','; + } + rule = rule + normalRule; + } + } + } + // this.newMeasure.evaluateRule.rules[0].rule = rule; + self.newMeasure.evaluateRule.rules.push({ + "dsl.type": "griffin-dsl", + "dq.type": "profiling", + "rule": rule, + // "details": {} + }); + this.visible = true; + setTimeout(() => this.visibleAnimate = true, 100); + } + + save() { + console.log(this.newMeasure); + var addModels = this.servicecService.config.uri.addModels; + this.http + .post(addModels, this.newMeasure) + .subscribe(data => { + this.createResult = data; + this.hide(); + this.router.navigate(['/measures']); + }, + err => { + console.log('Something went wrong!'); + }); + } + + // data: { [key: string]: Array<object>; } = { + // "default": [ + // { + // "tableName": "ext", + // "dbName": "default", + // "owner": "hadoop", + // "createTime": 1488353464, + // "lastAccessTime": 0, + // "retention": 0, + // "sd": { + // "cols": [ + // { + // "name": "id", + // "type": "int", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // }, + // { + // "name": "name", + // "type": "string", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // }, + // { + // "name": "age", + // "type": "int", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // } + // ], + // "location": "hdfs://10.9.246.187/user/hive/ext", + // }, + // }, + // { + // "tableName": "ext1", + // "dbName": "default", + // "owner": "hadoop", + // "createTime": 1489382943, + // "lastAccessTime": 0, + // "retention": 0, + // "sd": { + // "cols": [ + // { + // "name": "id", + // "type": "int", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // }, + // { + // "name": "name", + // "type": "string", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // }, + // { + // "name": "age", + // "type": "int", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // } + // ], + // "location": "hdfs://10.9.246.187/user/hive/ext1", + // }, + // } + // ], + // "griffin": [ + // { + // "tableName": "avr_out", + // "dbName": "griffin", + // "owner": "hadoop", + // "createTime": 1493892603, + // "lastAccessTime": 0, + // "retention": 0, + // "sd": { + // "cols": [ + // { + // "name": "id", + // "type": "bigint", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // }, + // { + // "name": "age", + // "type": "int", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // }, + // { + // "name": "desc", + // "type": "string", + // "comment": null, + // "setName": true, + // "setComment": false, + // "setType": true + // } + // ], + // "location": "hdfs://10.9.246.187/griffin/data/batch/avr_out", + // }, + // } + // ], + // }; + + options: ITreeOptions = { + displayField: 'name', + isExpandedField: 'expanded', + idField: 'id', + actionMapping: { + mouse: { + click: (tree, node, $event) => { + if (node.hasChildren) { + this.currentDB = node.data.name; + this.currentTable = ''; + 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; + } + } + } + }, + animateExpand: true, + animateSpeed: 30, + animateAcceleration: 1.2 + }; + + nodeList:object[]; + nodeListTarget:object[]; + + constructor(toasterService: ToasterService,private http: HttpClient,private router:Router,public servicecService:ServiceService) { + this.toasterService = toasterService; + this.selection = []; + }; + + + ngOnInit() { + var allDataassets = this.servicecService.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.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)); + + }); + + }; +} http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/42ee8863/ui/angular/src/app/measure/measure-detail/measure-detail.component.css ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/measure-detail/measure-detail.component.css b/ui/angular/src/app/measure/measure-detail/measure-detail.component.css new file mode 100644 index 0000000..214f8c6 --- /dev/null +++ b/ui/angular/src/app/measure/measure-detail/measure-detail.component.css @@ -0,0 +1,18 @@ +/* +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. +*/ \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/42ee8863/ui/angular/src/app/measure/measure-detail/measure-detail.component.html ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/measure-detail/measure-detail.component.html b/ui/angular/src/app/measure/measure-detail/measure-detail.component.html new file mode 100644 index 0000000..4552627 --- /dev/null +++ b/ui/angular/src/app/measure/measure-detail/measure-detail.component.html @@ -0,0 +1,105 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> + +<div class="container-fluid" id="viewruleContent"> + <div class="row"> + <h5 class="over-title margin-bottom-15">View Measure</h5> + </div><!--//row--> + <div class="row"> + <div class="col-sm-6 col-xs-12"> + <div id="viewruleDefinition" class="viewrule-content"> + <div class="row"> + <label class="col-xs-4"> + Measure Name: + </label> + <div class="col-xs-8 " style="color: #fff"> + {{ruleData.name}} + </div> + </div> + <div class="row"> + <label class="col-xs-4"> + Measure Description: + </label> + <div class="col-xs-8 " style="color: #fff"> + {{ruleData.description}} + </div> + </div> + <div class="row"> + <label class="col-xs-4"> + Measure Type: + </label> + <div class="col-xs-8 " style="color: #fff"> + {{ruleData.type}} + </div> + </div> + <div class="row"> + <label for="systemSelector" class="col-xs-4"> + Organization: + </label> + <div class="col-xs-8 " style="color: #fff"> + {{ruleData.organization}} + </div> + </div> + <div class="row"> + <label class="col-xs-4"> + DataAsset: + </label> + <div class="col-xs-8" style="color: #fff"> + {{sourceTable}} {{targetTable}} + </div> + </div> + <div class="row"> + <label class="col-xs-4"> + Owner: + </label> + <div class="col-xs-8" style="color: #fff"> + {{ruleData.owner}} + </div> + </div> + </div> + </div> + </div><!--//row--> + <br/> + <div ng-if="ruleData.type=='accuracy'"><!--Accuracy--> + <h5 class="row">Mapping rules</h5> + <div class=""> + <p>{{ruleData.evaluateRule.rules}}</p> + <p> + <label style="color:#B2C831">Accuracy Calculation Formula as Below:</label> + </p> + <div class="col-md-12 col-lg-12 col-sm-12" style="color:#fff;font-size:16px;display: flex;align-items: center"> + <div class="" style="text-align:right;display:block;float:left;width:20%;"> + Accuracy Rate(%) = + </div> + <div class="" style="text-align:center;display:block;float:left;margin:0 10px 0 10px"> + <div class="formula-text-up" style="border-bottom:1px solid;"> + Total Count of Matched records between <span class="badge">{{sourceLength}}</span> <span style="color:green;">{{targetTable}}</span> and <span class="badge">{{sourceLength}}</span> <span style="color:green;">{{sourceTable}}</span> fields + </div> + <div class=""> + Total Count of records in <span style="color:green;font-weight:bold;">{{targetDB}} {{targetTable}}</span> + </div> + </div> + <div class="" style="text-align:left;display:block;float:left;width:10%;"> + x 100% + </div> + </div> + </div> + </div> +</div> + \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/42ee8863/ui/angular/src/app/measure/measure-detail/measure-detail.component.spec.ts ---------------------------------------------------------------------- diff --git a/ui/angular/src/app/measure/measure-detail/measure-detail.component.spec.ts b/ui/angular/src/app/measure/measure-detail/measure-detail.component.spec.ts new file mode 100644 index 0000000..4a5d8a2 --- /dev/null +++ b/ui/angular/src/app/measure/measure-detail/measure-detail.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 { MeasureDetailComponent } from './measure-detail.component'; + +describe('MeasureDetailComponent', () => { + let component: MeasureDetailComponent; + let fixture: ComponentFixture<MeasureDetailComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MeasureDetailComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MeasureDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +});
