http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.ts b/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.ts new file mode 100644 index 0000000..958b27d --- /dev/null +++ b/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.ts @@ -0,0 +1,58 @@ +/** + * 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 {Injectable, Inject} from '@angular/core'; +import {Http, Headers, RequestOptions, Response} from '@angular/http'; +import {Observable} from 'rxjs/Observable'; +import {IndexingConfigurations} from '../model/sensor-indexing-config'; +import {HttpUtil} from '../util/httpUtil'; +import {IAppConfig} from '../app.config.interface'; +import {APP_CONFIG} from '../app.config'; + +@Injectable() +export class SensorIndexingConfigService { + url = this.config.apiEndpoint + '/sensor/indexing/config'; + defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'}; + + constructor(private http: Http, @Inject(APP_CONFIG) private config: IAppConfig) { + } + + public post(name: string, sensorIndexingConfig: IndexingConfigurations): Observable<IndexingConfigurations> { + return this.http.post(this.url + '/' + name, JSON.stringify(sensorIndexingConfig), + new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public get(name: string): Observable<IndexingConfigurations> { + return this.http.get(this.url + '/' + name, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public getAll(): Observable<IndexingConfigurations[]> { + return this.http.get(this.url, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public deleteSensorIndexingConfig(name: string): Observable<Response> { + return this.http.delete(this.url + '/' + name, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .catch(HttpUtil.handleError); + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.spec.ts b/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.spec.ts new file mode 100644 index 0000000..3da4065 --- /dev/null +++ b/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.spec.ts @@ -0,0 +1,97 @@ +/** + * 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, inject, TestBed} from '@angular/core/testing'; +import {MockBackend, MockConnection} from '@angular/http/testing'; +import {SensorParserConfig} from '../model/sensor-parser-config'; +import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from '@angular/http'; +import '../rxjs-operators'; +import {METRON_REST_CONFIG, APP_CONFIG} from '../app.config'; +import {SensorParserConfigHistoryService} from './sensor-parser-config-history.service'; +import {IAppConfig} from '../app.config.interface'; +import {SensorParserConfigHistory} from '../model/sensor-parser-config-history'; + +describe('SensorParserConfigHistoryService', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + SensorParserConfigHistoryService, + {provide: XHRBackend, useClass: MockBackend}, + {provide: APP_CONFIG, useValue: METRON_REST_CONFIG} + ] + }) + .compileComponents(); + })); + + it('can instantiate service when inject service', + inject([SensorParserConfigHistoryService], (service: SensorParserConfigHistoryService) => { + expect(service instanceof SensorParserConfigHistoryService).toBe(true); + })); + + it('can instantiate service with "new"', inject([Http, APP_CONFIG], (http: Http, config: IAppConfig) => { + expect(http).not.toBeNull('http should be provided'); + let service = new SensorParserConfigHistoryService(http, config); + expect(service instanceof SensorParserConfigHistoryService).toBe(true, 'new service should be ok'); + })); + + + it('can provide the mockBackend as XHRBackend', + inject([XHRBackend], (backend: MockBackend) => { + expect(backend).not.toBeNull('backend should be provided'); + })); + + describe('when service functions', () => { + let sensorParserConfigHistoryService: SensorParserConfigHistoryService; + let mockBackend: MockBackend; + let sensorParserConfigHistory = new SensorParserConfigHistory(); + let sensorParserConfig = new SensorParserConfig(); + sensorParserConfig.sensorTopic = 'bro'; + sensorParserConfigHistory.config = sensorParserConfig; + let sensorParserConfigHistoryResponse: Response; + let allSensorParserConfigHistoryResponse: Response; + + beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: MockBackend, config: IAppConfig) => { + mockBackend = be; + sensorParserConfigHistoryService = new SensorParserConfigHistoryService(http, config); + sensorParserConfigHistoryResponse = new Response(new ResponseOptions({status: 200, body: sensorParserConfig})); + allSensorParserConfigHistoryResponse = new Response(new ResponseOptions({status: 200, body: [sensorParserConfig]})); + })); + + it('get', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sensorParserConfigHistoryResponse)); + + sensorParserConfigHistoryService.get('bro').subscribe( + result => { + expect(result).toEqual(sensorParserConfigHistory); + }, error => console.log(error)); + }))); + + it('getAll', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(allSensorParserConfigHistoryResponse)); + + sensorParserConfigHistoryService.getAll().subscribe( + result => { + expect(result).toEqual([sensorParserConfigHistory]); + }, error => console.log(error)); + }))); + }); + +}); + + http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.ts b/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.ts new file mode 100644 index 0000000..bd5cbc5 --- /dev/null +++ b/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.ts @@ -0,0 +1,61 @@ +/** + * 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 {Injectable, Inject} from '@angular/core'; +import {Http, Headers, RequestOptions, Response} from '@angular/http'; +import {Observable} from 'rxjs/Observable'; +import {HttpUtil} from '../util/httpUtil'; +import {IAppConfig} from '../app.config.interface'; +import {SensorParserConfigHistory} from '../model/sensor-parser-config-history'; +import {APP_CONFIG} from '../app.config'; +import {SensorParserConfig} from '../model/sensor-parser-config'; + +@Injectable() +export class SensorParserConfigHistoryService { + url = this.config.apiEndpoint + '/sensor/parser/config'; + defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'}; + + constructor(private http: Http, @Inject(APP_CONFIG) private config: IAppConfig) { + + } + + public get(name: string): Observable<SensorParserConfigHistory> { + return this.http.get(this.url + '/' + name, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map((response: Response) => { + let sensorParserConfigHistory = new SensorParserConfigHistory(); + sensorParserConfigHistory.config = response.json(); + return sensorParserConfigHistory; + }) + .catch(HttpUtil.handleError); + } + + public getAll(): Observable<SensorParserConfigHistory[]> { + return this.http.get(this.url, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map((response: Response) => { + let sensorParserConfigHistoryArray = []; + let sensorParserConfigs: SensorParserConfig[] = response.json(); + for (let sensorParserConfig of sensorParserConfigs) { + let sensorParserConfigHistory = new SensorParserConfigHistory(); + sensorParserConfigHistory.config = sensorParserConfig; + sensorParserConfigHistoryArray.push(sensorParserConfigHistory); + } + return sensorParserConfigHistoryArray; + }) + .catch(HttpUtil.handleError); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/sensor-parser-config.service.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/sensor-parser-config.service.spec.ts b/metron-interface/metron-config/src/app/service/sensor-parser-config.service.spec.ts new file mode 100644 index 0000000..bae4656 --- /dev/null +++ b/metron-interface/metron-config/src/app/service/sensor-parser-config.service.spec.ts @@ -0,0 +1,149 @@ +/** + * 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, inject, TestBed} from '@angular/core/testing'; +import {MockBackend, MockConnection} from '@angular/http/testing'; +import {SensorParserConfigService} from './sensor-parser-config.service'; +import {SensorParserConfig} from '../model/sensor-parser-config'; +import {ParseMessageRequest} from '../model/parse-message-request'; +import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from '@angular/http'; +import '../rxjs-operators'; +import {APP_CONFIG, METRON_REST_CONFIG} from '../app.config'; +import {IAppConfig} from '../app.config.interface'; + +describe('SensorParserConfigService', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + SensorParserConfigService, + {provide: XHRBackend, useClass: MockBackend}, + {provide: APP_CONFIG, useValue: METRON_REST_CONFIG} + ] + }) + .compileComponents(); + })); + + it('can instantiate service when inject service', + inject([SensorParserConfigService], (service: SensorParserConfigService) => { + expect(service instanceof SensorParserConfigService).toBe(true); + })); + + it('can instantiate service with "new"', inject([Http, APP_CONFIG], (http: Http, config: IAppConfig) => { + expect(http).not.toBeNull('http should be provided'); + let service = new SensorParserConfigService(http, config); + expect(service instanceof SensorParserConfigService).toBe(true, 'new service should be ok'); + })); + + + it('can provide the mockBackend as XHRBackend', + inject([XHRBackend], (backend: MockBackend) => { + expect(backend).not.toBeNull('backend should be provided'); + })); + + describe('when service functions', () => { + let sensorParserConfigService: SensorParserConfigService; + let mockBackend: MockBackend; + let sensorParserConfig = new SensorParserConfig(); + sensorParserConfig.sensorTopic = 'bro'; + sensorParserConfig.parserClassName = 'parserClass'; + sensorParserConfig.parserConfig = {field: 'value'}; + let availableParsers = [{ 'Grok': 'org.apache.metron.parsers.GrokParser'}]; + let parseMessageRequest = new ParseMessageRequest(); + parseMessageRequest.sensorParserConfig = new SensorParserConfig(); + parseMessageRequest.sensorParserConfig.sensorTopic = 'bro'; + parseMessageRequest.sampleData = 'sampleData'; + let parsedMessage = { 'field': 'value'}; + let sensorParserConfig1 = new SensorParserConfig(); + sensorParserConfig1.sensorTopic = 'bro1'; + let sensorParserConfig2 = new SensorParserConfig(); + sensorParserConfig2.sensorTopic = 'bro2'; + let deleteResult = {success: ['bro1', 'bro2']}; + let sensorParserConfigResponse: Response; + let sensorParserConfigsResponse: Response; + let availableParserResponse: Response; + let parseMessageResponse: Response; + let deleteResponse: Response; + + beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: MockBackend, config: IAppConfig) => { + mockBackend = be; + sensorParserConfigService = new SensorParserConfigService(http, config); + sensorParserConfigResponse = new Response(new ResponseOptions({status: 200, body: sensorParserConfig})); + sensorParserConfigsResponse = new Response(new ResponseOptions({status: 200, body: [sensorParserConfig]})); + availableParserResponse = new Response(new ResponseOptions({status: 200, body: availableParsers})); + parseMessageResponse = new Response(new ResponseOptions({status: 200, body: parsedMessage})); + deleteResponse = new Response(new ResponseOptions({status: 200, body: deleteResult})); + })); + + it('post', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sensorParserConfigResponse)); + + sensorParserConfigService.post(sensorParserConfig).subscribe( + result => { + expect(result).toEqual(sensorParserConfig); + }, error => console.log(error)); + }))); + + it('get', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sensorParserConfigResponse)); + + sensorParserConfigService.get('bro').subscribe( + result => { + expect(result).toEqual(sensorParserConfig); + }, error => console.log(error)); + }))); + + it('getAll', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sensorParserConfigsResponse)); + + sensorParserConfigService.getAll().subscribe( + results => { + expect(results).toEqual([sensorParserConfig]); + }, error => console.log(error)); + }))); + + it('getAvailableParsers', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(availableParserResponse)); + + sensorParserConfigService.getAvailableParsers().subscribe( + results => { + expect(results).toEqual(availableParsers); + }, error => console.log(error)); + }))); + + it('parseMessage', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(parseMessageResponse)); + + sensorParserConfigService.parseMessage(parseMessageRequest).subscribe( + results => { + expect(results).toEqual(parsedMessage); + }, error => console.log(error)); + }))); + + it('deleteSensorParserConfigs', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(deleteResponse)); + + sensorParserConfigService.deleteSensorParserConfigs([sensorParserConfig1, sensorParserConfig2]).subscribe(result => { + expect(result.success.length).toEqual(2); + }); + }))); + }); + +}); + + http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/sensor-parser-config.service.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/sensor-parser-config.service.ts b/metron-interface/metron-config/src/app/service/sensor-parser-config.service.ts new file mode 100644 index 0000000..25cd833 --- /dev/null +++ b/metron-interface/metron-config/src/app/service/sensor-parser-config.service.ts @@ -0,0 +1,116 @@ +/** + * 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 {Injectable, Inject} from '@angular/core'; +import {Http, Headers, RequestOptions, Response} from '@angular/http'; +import {Observable} from 'rxjs/Observable'; +import {SensorParserConfig} from '../model/sensor-parser-config'; +import {HttpUtil} from '../util/httpUtil'; +import {Subject} from 'rxjs/Subject'; +import {ParseMessageRequest} from '../model/parse-message-request'; +import {IAppConfig} from '../app.config.interface'; +import {APP_CONFIG} from '../app.config'; + +@Injectable() +export class SensorParserConfigService { + url = this.config.apiEndpoint + '/sensor/parser/config'; + defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'}; + selectedSensorParserConfig: SensorParserConfig; + + dataChangedSource = new Subject<SensorParserConfig[]>(); + dataChanged$ = this.dataChangedSource.asObservable(); + + constructor(private http: Http, @Inject(APP_CONFIG) private config: IAppConfig) { + + } + + public post(sensorParserConfig: SensorParserConfig): Observable<SensorParserConfig> { + return this.http.post(this.url, JSON.stringify(sensorParserConfig), new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public get(name: string): Observable<SensorParserConfig> { + return this.http.get(this.url + '/' + name, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public getAll(): Observable<SensorParserConfig[]> { + return this.http.get(this.url, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public deleteSensorParserConfig(name: string): Observable<Response> { + return this.http.delete(this.url + '/' + name, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .catch(HttpUtil.handleError); + } + + public getAvailableParsers(): Observable<{}> { + return this.http.get(this.url + '/list/available', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public parseMessage(parseMessageRequest: ParseMessageRequest): Observable<{}> { + return this.http.post(this.url + '/parseMessage', parseMessageRequest, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public deleteSensorParserConfigs(sensors: SensorParserConfig[]): Observable<{success: Array<string>, failure: Array<string>}> { + let result: {success: Array<string>, failure: Array<string>} = {success: [], failure: []}; + let observable = Observable.create((observer => { + + let completed = () => { + if (observer) { + observer.next(result); + observer.complete(); + } + + this.dataChangedSource.next(sensors); + }; + + for (let i = 0; i < sensors.length; i++) { + this.deleteSensorParserConfig(sensors[i].sensorTopic).subscribe(results => { + result.success.push(sensors[i].sensorTopic); + if (result.success.length + result.failure.length === sensors.length) { + completed(); + } + }, error => { + result.failure.push(sensors[i].sensorTopic); + if (result.success.length + result.failure.length === sensors.length) { + completed(); + } + }); + } + + })); + + return observable; + } + + public setSeletedSensor(sensor: SensorParserConfig): void { + this.selectedSensorParserConfig = sensor; + } + + public getSelectedSensor(): SensorParserConfig { + return this.selectedSensorParserConfig; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/stellar.service.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/stellar.service.spec.ts b/metron-interface/metron-config/src/app/service/stellar.service.spec.ts new file mode 100644 index 0000000..163eefb --- /dev/null +++ b/metron-interface/metron-config/src/app/service/stellar.service.spec.ts @@ -0,0 +1,142 @@ +/** + * 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, inject, TestBed} from '@angular/core/testing'; +import {MockBackend, MockConnection} from '@angular/http/testing'; +import {StellarService} from './stellar.service'; +import {SensorParserContext} from '../model/sensor-parser-context'; +import {SensorParserConfig} from '../model/sensor-parser-config'; +import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from '@angular/http'; +import '../rxjs-operators'; +import {APP_CONFIG, METRON_REST_CONFIG} from '../app.config'; +import {IAppConfig} from '../app.config.interface'; + +describe('StellarService', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + StellarService, + {provide: XHRBackend, useClass: MockBackend}, + {provide: APP_CONFIG, useValue: METRON_REST_CONFIG} + ] + }) + .compileComponents(); + })); + + it('can instantiate service when inject service', + inject([StellarService], (service: StellarService) => { + expect(service instanceof StellarService).toBe(true); + })); + + it('can instantiate service with "new"', inject([Http, APP_CONFIG], (http: Http, config: IAppConfig) => { + expect(http).not.toBeNull('http should be provided'); + let service = new StellarService(http, config); + expect(service instanceof StellarService).toBe(true, 'new service should be ok'); + })); + + + it('can provide the mockBackend as XHRBackend', + inject([XHRBackend], (backend: MockBackend) => { + expect(backend).not.toBeNull('backend should be provided'); + })); + + describe('when service functions', () => { + let transformationValidationService: StellarService; + let mockBackend: MockBackend; + let transformationRules = ['rule1', 'rule2']; + let transformationRulesValidation = {rule1: true, rule2: false}; + let transformationValidation = new SensorParserContext(); + transformationValidation.sampleData = {'data': 'data'}; + transformationValidation.sensorParserConfig = new SensorParserConfig(); + transformationValidation.sensorParserConfig.sensorTopic = 'test'; + let transformations = ['STELLAR', 'REMOVE']; + let transformFunctions = [{'function1': 'desc1'}, {'function2': 'desc2'}]; + let simpleTransformFunctions = [{'simplefunction1': 'simpledesc1'}, {'simplefunction2': 'simpledesc2'}]; + let transformationRulesValidationResponse: Response; + let transformationValidationResponse: Response; + let transformationListResponse: Response; + let transformationListFunctionsResponse: Response; + let transformationListSimpleFunctionsResponse: Response; + + beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: MockBackend, config: IAppConfig) => { + mockBackend = be; + transformationValidationService = new StellarService(http, config); + transformationRulesValidationResponse = new Response(new ResponseOptions({ + status: 200, + body: transformationRulesValidation + })); + transformationValidationResponse = new Response(new ResponseOptions({ + status: 200, + body: transformationValidation + })); + transformationListResponse = new Response(new ResponseOptions({status: 200, body: transformations})); + transformationListFunctionsResponse = new Response(new ResponseOptions({status: 200, body: transformFunctions})); + transformationListSimpleFunctionsResponse = new Response(new ResponseOptions({status: 200, body: simpleTransformFunctions})); + })); + + it('validateRules', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(transformationRulesValidationResponse)); + + transformationValidationService.validateRules(transformationRules).subscribe( + result => { + expect(result).toEqual(transformationRulesValidation); + }, error => console.log(error)); + }))); + + it('validate', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(transformationValidationResponse)); + + transformationValidationService.validate(transformationValidation).subscribe( + result => { + expect(result).toEqual(transformationValidation); + }, error => console.log(error)); + }))); + + it('list', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(transformationListResponse)); + + transformationValidationService.list().subscribe( + result => { + expect(result).toEqual(transformations); + }, error => console.log(error)); + }))); + + it('listFunctions', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(transformationListFunctionsResponse)); + + transformationValidationService.listFunctions().subscribe( + result => { + expect(result).toEqual(transformFunctions); + }, error => console.log(error)); + }))); + + it('listSimpleFunctions', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(transformationListSimpleFunctionsResponse)); + + transformationValidationService.listSimpleFunctions().subscribe( + result => { + expect(result).toEqual(simpleTransformFunctions); + }, error => console.log(error)); + }))); + }); + +}); + + + http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/stellar.service.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/stellar.service.ts b/metron-interface/metron-config/src/app/service/stellar.service.ts new file mode 100644 index 0000000..be04906 --- /dev/null +++ b/metron-interface/metron-config/src/app/service/stellar.service.ts @@ -0,0 +1,68 @@ +/** + * 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 {Injectable, Inject} from '@angular/core'; +import {Http, Headers, RequestOptions} from '@angular/http'; +import {Observable} from 'rxjs/Observable'; +import {SensorParserContext} from '../model/sensor-parser-context'; +import {HttpUtil} from '../util/httpUtil'; +import {StellarFunctionDescription} from '../model/stellar-function-description'; +import {IAppConfig} from '../app.config.interface'; +import {APP_CONFIG} from '../app.config'; + +@Injectable() +export class StellarService { + url = this.config.apiEndpoint + '/stellar'; + defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'}; + + constructor(private http: Http, @Inject(APP_CONFIG) private config: IAppConfig) { + + } + + public validateRules(rules: string[]): Observable<{}> { + return this.http.post(this.url + '/validate/rules', JSON.stringify(rules), + new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public validate(transformationValidation: SensorParserContext): Observable<{}> { + return this.http.post(this.url + '/validate', JSON.stringify(transformationValidation), + new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public list(): Observable<string[]> { + return this.http.get(this.url + '/list', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public listFunctions(): Observable<StellarFunctionDescription[]> { + return this.http.get(this.url + '/list/functions', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public listSimpleFunctions(): Observable<StellarFunctionDescription[]> { + return this.http.get(this.url + '/list/simple/functions', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/storm.service.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/storm.service.spec.ts b/metron-interface/metron-config/src/app/service/storm.service.spec.ts new file mode 100644 index 0000000..528c1c3 --- /dev/null +++ b/metron-interface/metron-config/src/app/service/storm.service.spec.ts @@ -0,0 +1,252 @@ +/** + * 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, inject, TestBed} from '@angular/core/testing'; +import {MockBackend, MockConnection} from '@angular/http/testing'; +import {TopologyStatus} from '../model/topology-status'; +import {TopologyResponse} from '../model/topology-response'; +import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from '@angular/http'; +import '../rxjs-operators'; +import {APP_CONFIG, METRON_REST_CONFIG} from '../app.config'; +import {IAppConfig} from '../app.config.interface'; +import {StormService} from './storm.service'; + +describe('StormService', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + StormService, + {provide: XHRBackend, useClass: MockBackend}, + {provide: APP_CONFIG, useValue: METRON_REST_CONFIG} + ] + }) + .compileComponents(); + })); + + it('can instantiate service when inject service', + inject([StormService], (service: StormService) => { + expect(service instanceof StormService).toBe(true); + })); + + it('can instantiate service with "new"', inject([Http, APP_CONFIG], (http: Http, config: IAppConfig) => { + expect(http).not.toBeNull('http should be provided'); + let service = new StormService(http, config); + expect(service instanceof StormService).toBe(true, 'new service should be ok'); + })); + + + it('can provide the mockBackend as XHRBackend', + inject([XHRBackend], (backend: MockBackend) => { + expect(backend).not.toBeNull('backend should be provided'); + })); + + describe('when service functions', () => { + let stormService: StormService; + let mockBackend: MockBackend; + let allStatuses: TopologyStatus[] = []; + let broStatus = new TopologyStatus(); + broStatus.name = 'bro'; + broStatus.id = 'broid'; + broStatus.status = 'ACTIVE'; + allStatuses.push(broStatus); + let enrichmentStatus = new TopologyStatus(); + enrichmentStatus.name = 'enrichment'; + enrichmentStatus.id = 'enrichmentid'; + enrichmentStatus.status = 'ACTIVE'; + allStatuses.push(enrichmentStatus); + let indexingStatus = new TopologyStatus(); + indexingStatus.name = 'indexing'; + indexingStatus.id = 'indexingid'; + indexingStatus.status = 'ACTIVE'; + allStatuses.push(indexingStatus); + let startMessage: TopologyResponse = {status: 'success', message: 'STARTED'}; + let stopMessage: TopologyResponse = {status: 'success', message: 'STOPPED'}; + let activateMessage: TopologyResponse = {status: 'success', message: 'ACTIVE'}; + let deactivateMessage: TopologyResponse = {status: 'success', message: 'INACTIVE'}; + let allStatusesResponse: Response; + let enrichmentStatusResponse: Response; + let indexingStatusResponse: Response; + let broStatusResponse: Response; + let startResponse: Response; + let stopResponse: Response; + let activateResponse: Response; + let deactivateResponse: Response; + + beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: MockBackend, config: IAppConfig) => { + mockBackend = be; + stormService = new StormService(http, config); + allStatusesResponse = new Response(new ResponseOptions({status: 200, body: allStatuses})); + enrichmentStatusResponse = new Response(new ResponseOptions({status: 200, body: enrichmentStatus})); + indexingStatusResponse = new Response(new ResponseOptions({status: 200, body: indexingStatus})); + broStatusResponse = new Response(new ResponseOptions({status: 200, body: broStatus})); + startResponse = new Response(new ResponseOptions({status: 200, body: startMessage})); + stopResponse = new Response(new ResponseOptions({status: 200, body: stopMessage})); + activateResponse = new Response(new ResponseOptions({status: 200, body: activateMessage})); + deactivateResponse = new Response(new ResponseOptions({status: 200, body: deactivateMessage})); + })); + + it('getAll', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(allStatusesResponse)); + + stormService.getAll().subscribe( + result => { + expect(result).toEqual(allStatuses); + }, error => console.log(error)); + }))); + + it('getEnrichmentStatus', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(enrichmentStatusResponse)); + + stormService.getEnrichmentStatus().subscribe( + result => { + expect(result).toEqual(enrichmentStatus); + }, error => console.log(error)); + }))); + + it('activateEnrichment', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(activateResponse)); + + stormService.activateEnrichment().subscribe( + result => { + expect(result).toEqual(activateMessage); + }, error => console.log(error)); + }))); + + it('deactivateEnrichment', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(deactivateResponse)); + + stormService.deactivateEnrichment().subscribe( + result => { + expect(result).toEqual(deactivateMessage); + }, error => console.log(error)); + }))); + + it('startEnrichment', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(startResponse)); + + stormService.startEnrichment().subscribe( + result => { + expect(result).toEqual(startMessage); + }, error => console.log(error)); + }))); + + it('stopEnrichment', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(stopResponse)); + + stormService.stopEnrichment().subscribe( + result => { + expect(result).toEqual(stopMessage); + }, error => console.log(error)); + }))); + + it('getIndexingStatus', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(indexingStatusResponse)); + + stormService.getIndexingStatus().subscribe( + result => { + expect(result).toEqual(indexingStatus); + }, error => console.log(error)); + }))); + + it('activateIndexing', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(activateResponse)); + + stormService.activateIndexing().subscribe( + result => { + expect(result).toEqual(activateMessage); + }, error => console.log(error)); + }))); + + it('deactivateIndexing', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(deactivateResponse)); + + stormService.deactivateIndexing().subscribe( + result => { + expect(result).toEqual(deactivateMessage); + }, error => console.log(error)); + }))); + + it('startIndexing', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(startResponse)); + + stormService.startIndexing().subscribe( + result => { + expect(result).toEqual(startMessage); + }, error => console.log(error)); + }))); + + it('stopIndexing', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(stopResponse)); + + stormService.stopIndexing().subscribe( + result => { + expect(result).toEqual(stopMessage); + }, error => console.log(error)); + }))); + + it('getStatus', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(broStatusResponse)); + + stormService.getStatus('bro').subscribe( + result => { + expect(result).toEqual(broStatus); + }, error => console.log(error)); + }))); + + it('activateParser', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(activateResponse)); + + stormService.activateParser('bro').subscribe( + result => { + expect(result).toEqual(activateMessage); + }, error => console.log(error)); + }))); + + it('deactivateParser', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(deactivateResponse)); + + stormService.deactivateParser('bro').subscribe( + result => { + expect(result).toEqual(deactivateMessage); + }, error => console.log(error)); + }))); + + it('startParser', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(startResponse)); + + stormService.startParser('bro').subscribe( + result => { + expect(result).toEqual(startMessage); + }, error => console.log(error)); + }))); + + it('stopParser', async(inject([], () => { + mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(stopResponse)); + + stormService.stopParser('bro').subscribe( + result => { + expect(result).toEqual(stopMessage); + }, error => console.log(error)); + }))); + + + + }); + +}); http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/storm.service.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/service/storm.service.ts b/metron-interface/metron-config/src/app/service/storm.service.ts new file mode 100644 index 0000000..7a79e1f --- /dev/null +++ b/metron-interface/metron-config/src/app/service/storm.service.ts @@ -0,0 +1,144 @@ +/** + * 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 {Injectable, Inject} from '@angular/core'; +import {Http, Headers, RequestOptions} from '@angular/http'; +import {HttpUtil} from '../util/httpUtil'; +import {TopologyStatus} from '../model/topology-status'; +import {TopologyResponse} from '../model/topology-response'; +import {APP_CONFIG} from '../app.config'; +import {IAppConfig} from '../app.config.interface'; +import {Observable} from 'rxjs/Observable'; +import 'rxjs/add/observable/interval'; +import 'rxjs/add/operator/switchMap'; +import 'rxjs/add/operator/onErrorResumeNext'; + +@Injectable() +export class StormService { + url = this.config.apiEndpoint + '/storm'; + defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'}; + + constructor(private http: Http, @Inject(APP_CONFIG) private config: IAppConfig) { + + } + + public pollGetAll(): Observable<TopologyStatus[]> { + return Observable.interval(8000).switchMap(() => { + return this.http.get(this.url, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError) + .onErrorResumeNext(); + }); + } + + public getAll(): Observable<TopologyStatus[]> { + return this.http.get(this.url, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public getEnrichmentStatus(): Observable<TopologyStatus> { + return this.http.get(this.url + '/enrichment', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public activateEnrichment(): Observable<TopologyResponse> { + return this.http.get(this.url + '/enrichment/activate', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public deactivateEnrichment(): Observable<TopologyResponse> { + return this.http.get(this.url + '/enrichment/deactivate', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public startEnrichment(): Observable<TopologyResponse> { + return this.http.get(this.url + '/enrichment/start', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public stopEnrichment(): Observable<TopologyResponse> { + return this.http.get(this.url + '/enrichment/stop', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public getIndexingStatus(): Observable<TopologyStatus> { + return this.http.get(this.url + '/indexing', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public activateIndexing(): Observable<TopologyResponse> { + return this.http.get(this.url + '/indexing/activate', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public deactivateIndexing(): Observable<TopologyResponse> { + return this.http.get(this.url + '/indexing/deactivate', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public startIndexing(): Observable<TopologyResponse> { + return this.http.get(this.url + '/indexing/start', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public stopIndexing(): Observable<TopologyResponse> { + return this.http.get(this.url + '/indexing/stop', new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public getStatus(name: string): Observable<TopologyStatus> { + return this.http.get(this.url + '/' + name, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public activateParser(name: string): Observable<TopologyResponse> { + return this.http.get(this.url + '/parser/activate/' + name, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public deactivateParser(name: string): Observable<TopologyResponse> { + return this.http.get(this.url + '/parser/deactivate/' + name, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public startParser(name: string): Observable<TopologyResponse> { + return this.http.get(this.url + '/parser/start/' + name, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + + public stopParser(name: string): Observable<TopologyResponse> { + return this.http.get(this.url + '/parser/stop/' + name, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .map(HttpUtil.extractData) + .catch(HttpUtil.handleError); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.html ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.html b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.html new file mode 100644 index 0000000..72489ed --- /dev/null +++ b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.html @@ -0,0 +1,16 @@ +<!-- + 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 #aceEditor class="editor"></div> http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.scss ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.scss b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.scss new file mode 100644 index 0000000..3dbb13d --- /dev/null +++ b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.scss @@ -0,0 +1,32 @@ +/** + * 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 "../../_variables.scss"; +.editor { + border-radius: 4px; + border: 1px solid #4d4d4d; +} +* /deep/ { + .ace_emptyMessage + { + @include place-holder-text; + } + + .ace_gutter { + background: #272822 !important; + } +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.spec.ts b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.spec.ts new file mode 100644 index 0000000..89f6205 --- /dev/null +++ b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.spec.ts @@ -0,0 +1,26 @@ +/** + * 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 { AceEditorComponent } from './ace-editor.component'; + +describe('Component: AceEditor', () => { + it('should create an instance', () => { + let component = new AceEditorComponent(); + expect(component).toBeTruthy(); + }); +}); http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.ts b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.ts new file mode 100644 index 0000000..d5edd83 --- /dev/null +++ b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.ts @@ -0,0 +1,182 @@ +import { Component, AfterViewInit, ViewChild, ElementRef, forwardRef, Input} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import Editor = AceAjax.Editor; +import {AutocompleteOption} from '../../model/autocomplete-option'; + +declare var ace: any; + +@Component({ + selector: 'metron-config-ace-editor', + templateUrl: 'ace-editor.component.html', + styleUrls: ['ace-editor.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AceEditorComponent), + multi: true + } + ] +}) +export class AceEditorComponent implements AfterViewInit, ControlValueAccessor { + + inputJson: any = ''; + aceConfigEditor: Editor; + @Input() type: string = 'JSON'; + @Input() placeHolder: string = 'Enter text here'; + @Input() options: AutocompleteOption[] = []; + @ViewChild('aceEditor') aceEditorEle: ElementRef; + + private onTouchedCallback; + private onChangeCallback; + + constructor() { + ace.config.set('basePath', '/assets/ace'); + } + + ngAfterViewInit() { + ace.config.loadModule('ace/ext/language_tools', () => { this.initializeEditor(); }); + } + + writeValue(obj: any) { + this.inputJson = obj; + this.setInput(); + } + + registerOnChange(fn: any) { + this.onChangeCallback = fn; + } + + registerOnTouched(fn: any) { + this.onTouchedCallback = fn; + } + + setDisabledState(isDisabled: boolean) { + // TODO set readonly + } + + initializeEditor() { + this.aceConfigEditor = this.createEditor(this.aceEditorEle.nativeElement); + this.addPlaceHolder(); + this.setInput(); + } + + updatePlaceHolderText() { + let shouldShow = !this.aceConfigEditor.session.getValue().length; + let node = this.aceConfigEditor.renderer['emptyMessageNode']; + if (!shouldShow && node) { + this.aceConfigEditor.renderer.scroller.removeChild(this.aceConfigEditor.renderer['emptyMessageNode']); + this.aceConfigEditor.renderer['emptyMessageNode'] = null; + } else if (shouldShow && !node) { + node = this.aceConfigEditor.renderer['emptyMessageNode'] = document.createElement('div'); + node.textContent = this.placeHolder; + node.className = 'ace_invisible ace_emptyMessage'; + this.aceConfigEditor.renderer.scroller.appendChild(node); + } + } + + addPlaceHolder() { + this.aceConfigEditor.on('input', () => { this.updatePlaceHolderText(); }); + setTimeout(() => { this.updatePlaceHolderText(); }, 100); + } + + private createEditor(element: ElementRef) { + let parserConfigEditor = ace.edit(element); + parserConfigEditor.getSession().setMode(this.getEditorType()); + parserConfigEditor.getSession().setTabSize(2); + parserConfigEditor.getSession().setUseWrapMode(true); + parserConfigEditor.getSession().setWrapLimitRange(72, 72); + + parserConfigEditor.$blockScrolling = Infinity; + parserConfigEditor.setTheme('ace/theme/monokai'); + parserConfigEditor.setOptions({ + minLines: 10, + highlightActiveLine: false, + maxLines: Infinity, + enableBasicAutocompletion: true, + enableSnippets: true, + enableLiveAutocompletion: true + }); + parserConfigEditor.on('change', (e: any) => { + this.inputJson = this.aceConfigEditor.getValue(); + this.onChangeCallback(this.aceConfigEditor.getValue()); + }); + + if (this.type === 'GROK') { + parserConfigEditor.completers = [this.getGrokCompletion()]; + } + + return parserConfigEditor; + } + + private getGrokCompletion() { + let _this = this; + return { + getCompletions: function(editor, session, pos, prefix, callback) { + let autoCompletePrefix = ''; + let autoCompleteSuffix = ''; + let options = _this.options; + + let currentToken = editor.getSession().getTokenAt(pos.row, pos.column); + + // No value or user typed just a char + if (currentToken === null || currentToken.type === 'comment') { + autoCompletePrefix = '%{'; + autoCompleteSuffix = ':$0}'; + } else { + // }any<here> + if (currentToken.type === 'invalid') { + let lastToken = editor.getSession().getTokenAt(pos.row, (pos.column - currentToken.value.length)); + autoCompletePrefix = lastToken.value.endsWith('}') ? ' %{' : '%{'; + autoCompleteSuffix = ':$0}'; + } + + // In %{<here>} + if (currentToken.type === 'paren.rparen') { + autoCompletePrefix = currentToken.value.endsWith(' ') ? '%{' : ' %{'; + autoCompleteSuffix = ':$0}'; + } + + // %{NUM<here>:} + if (currentToken.type === 'paren.lparen' || currentToken.type === 'variable') { + let nextToken = editor.getSession().getTokenAt(pos.row, pos.column + 1); + autoCompletePrefix = ''; + autoCompleteSuffix = (nextToken && nextToken.value.indexOf(':') > -1) ? '' : ':$0}'; + } + + // %{NUMBER:<here>} + if (currentToken.type === 'seperator' || currentToken.type === 'string') { + let autocompleteVal = currentToken.value.replace(/:/g, ''); + let autocompletes = autocompleteVal.length === 0 ? 'variable' : ''; + options = [new AutocompleteOption(autocompletes)]; + } + } + + callback(null, options.map(function(autocompleteOption) { + return { + caption: autocompleteOption.name, + snippet: autoCompletePrefix + autocompleteOption.name + autoCompleteSuffix, + meta: 'grok-pattern', + score: Number.MAX_VALUE + }; + })); + + } + }; + } + + private getEditorType() { + if (this.type === 'GROK') { + return 'ace/mode/grok'; + } + + return 'ace/mode/json'; + } + + private setInput() { + if (this.aceConfigEditor && this.inputJson) { + this.aceConfigEditor.getSession().setValue(this.inputJson); + this.aceConfigEditor.resize(true); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.module.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.module.ts b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.module.ts new file mode 100644 index 0000000..56817da --- /dev/null +++ b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.module.ts @@ -0,0 +1,12 @@ +import {NgModule} from '@angular/core'; + +import {AceEditorComponent} from './ace-editor.component'; + +@NgModule({ + imports: [], + exports: [AceEditorComponent], + declarations: [AceEditorComponent], + providers: [], +}) +export class AceEditorModule { +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/ace-editor/index.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/shared/ace-editor/index.ts b/metron-interface/metron-config/src/app/shared/ace-editor/index.ts new file mode 100644 index 0000000..a8ba0f0 --- /dev/null +++ b/metron-interface/metron-config/src/app/shared/ace-editor/index.ts @@ -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. + */ +export * from './ace-editor.component' http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.html ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.html b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.html new file mode 100644 index 0000000..1a30ee7 --- /dev/null +++ b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.html @@ -0,0 +1,39 @@ +<!-- + 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. + --> +<form role="form" [formGroup]="configForm"> + <div *ngFor="let key of configKeys"> + <div class="row mx-0 configkey-row"> + <div class="col-md-10 advanced-input"><input type="text" class="form-control" name="advanced1" value="{{key}}" readonly></div> + <div class="col-md-2"> + </div> + </div> + <div class="row mx-0"> + <div class="col-md-10 advanced-input"><input type="text" class="form-control" formControlName="{{key}}" [(ngModel)]="config[key]"></div> + <div class="col-md-2" (click)="removeConfig(key)"> + <i class="fa fa-minus fa-4 icon-button" aria-hidden="true" ></i> + </div> + </div> + </div> + <div class="row mx-0 new-configkey-row"> + <div class="col-md-10 advanced-input"><input type="text" class="form-control" (focus)="clearKeyPlaceholder()" (blur)="saveNewConfig()" [ngClass]="{'input-placeholder': newConfigKey == 'enter field'}" formControlName="newConfigKey" [(ngModel)]="newConfigKey"></div> + <div class="col-md-2"> + </div> + </div> + <div class="row mx-0"> + <div class="col-md-10 advanced-input"><input type="text" class="form-control" (focus)="clearValuePlaceholder()" (blur)="saveNewConfig()" [ngClass]="{'input-placeholder': newConfigValue == 'enter value'}" formControlName="newConfigValue" [(ngModel)]="newConfigValue"></div> + <div class="col-md-2" (click)="addConfig()"> + <i class="fa fa-plus fa-4 icon-button" aria-hidden="true" ></i> + </div> + </div> +</form> http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.scss ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.scss b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.scss new file mode 100644 index 0000000..c43f0b1 --- /dev/null +++ b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.scss @@ -0,0 +1,61 @@ +/** + * 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 "../../_variables.scss"; + +.advanced { + padding-left: 10px; + cursor: pointer; +} + +.advanced-input { + padding-left: 0; + padding-right: 5px +} + +.advanced-link { + color: $field-button-color; + font-size: 14px; +} + +.advanced-title { + font-size: 16px; + color: $form-field-text-color; + display: inline-block; +} + +.small-close-button { + font-size: 16px; + padding-right: 10px; + cursor: pointer; +} + +.input-placeholder { + font-size: 11px; + font-style: italic; + color:#999999; +} + +.new-configkey-row { + margin-bottom: 5px; + padding-top: 10px +} + +.configkey-row { + margin-bottom: 5px; + margin-top: 10px +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.spec.ts b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.spec.ts new file mode 100644 index 0000000..a8f0ed0 --- /dev/null +++ b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.spec.ts @@ -0,0 +1,151 @@ +/** + * 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 {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {AdvancedConfigFormComponent} from './advanced-config-form.component'; + +describe('Component: AdvancedConfigFormComponent', () => { + + let comp: AdvancedConfigFormComponent; + let fixture: ComponentFixture<AdvancedConfigFormComponent>; + + beforeEach(async(() => { + + TestBed.configureTestingModule({ + imports: [FormsModule, ReactiveFormsModule], + declarations: [AdvancedConfigFormComponent] + }).compileComponents() + .then(() => { + fixture = TestBed.createComponent(AdvancedConfigFormComponent); + comp = fixture.componentInstance; + }); + + })); + + it('should create new forms for AdvancedConfigFormComponent', async(() => { + let component: AdvancedConfigFormComponent = fixture.componentInstance; + component.config = {'field1': 'value1', 'field2': 'value2'}; + component.ngOnInit(); + + expect(Object.keys(component.configForm.controls).length).toEqual(4); + expect(component.configForm.controls['newConfigKey'].value).toEqual('enter field'); + expect(component.configForm.controls['newConfigValue'].value).toEqual('enter value'); + expect(component.configForm.controls['field1'].value).toEqual('value1'); + expect(component.configForm.controls['field2'].value).toEqual('value2'); + })); + + it('OnChanges should recreate the form', async(() => { + let component: AdvancedConfigFormComponent = fixture.componentInstance; + component.config = {'field1': 'value1', 'field2': 'value2'}; + let changes = {'field1': {'currentValue' : 'value1', 'previousValue': 'value1'}}; + spyOn(component, 'createForm'); + + component.ngOnChanges(changes); + + expect(component.createForm).not.toHaveBeenCalled(); + expect(component.configKeys).toEqual([]); + + changes = {'field1': {'currentValue' : 'value1', 'previousValue': 'value-1-1'}}; + component.ngOnChanges(changes); + + expect(component.createForm).toHaveBeenCalled(); + expect(component.configKeys).toEqual(['field1', 'field2']); + + })); + + it('verify form interactions AdvancedConfigFormComponent', async(() => { + let component: AdvancedConfigFormComponent = fixture.componentInstance; + component.config = {'field1': 'value1', 'field2': 'value2'}; + component.ngOnInit(); + + + expect(component.newConfigKey).toEqual('enter field'); + expect(component.newConfigValue).toEqual('enter value'); + expect(component.configKeys).toEqual(['field1', 'field2']); + + component.clearKeyPlaceholder(); + expect(component.newConfigKey).toEqual(''); + component.clearValuePlaceholder(); + expect(component.newConfigValue).toEqual(''); + + component.newConfigKey = ''; + component.newConfigValue = ''; + component.saveNewConfig(); + expect(Object.keys(component.config).length).toEqual(2); + expect(component.config['field1']).toEqual('value1'); + expect(component.config['field2']).toEqual('value2'); + expect(component.newConfigKey).toEqual('enter field'); + expect(component.newConfigValue).toEqual('enter value'); + component.addConfig(); + expect(component.configKeys).toEqual(['field1', 'field2']); + expect(Object.keys(component.configForm.controls).length).toEqual(4); + expect(component.configForm.controls['newConfigKey'].value).toEqual('enter field'); + expect(component.configForm.controls['newConfigValue'].value).toEqual('enter value'); + expect(component.configForm.controls['field1'].value).toEqual('value1'); + expect(component.configForm.controls['field2'].value).toEqual('value2'); + + + component.newConfigKey = 'field3'; + component.newConfigValue = 'value3'; + component.saveNewConfig(); + expect(Object.keys(component.config).length).toEqual(3); + expect(component.config['field1']).toEqual('value1'); + expect(component.config['field2']).toEqual('value2'); + expect(component.config['field3']).toEqual('value3'); + expect(component.newConfigKey).toEqual('field3'); + expect(component.newConfigValue).toEqual('value3'); + component.addConfig(); + expect(component.configKeys).toEqual(['field1', 'field2', 'field3']); + expect(Object.keys(component.configForm.controls).length).toEqual(5); + expect(component.configForm.controls['newConfigKey'].value).toEqual('enter field'); + expect(component.configForm.controls['newConfigValue'].value).toEqual('enter value'); + expect(component.configForm.controls['field1'].value).toEqual('value1'); + expect(component.configForm.controls['field2'].value).toEqual('value2'); + expect(component.configForm.controls['field3'].value).toEqual('value3'); + + component.newConfigKey = 'field1'; + component.newConfigValue = 'newValue1'; + component.saveNewConfig(); + expect(Object.keys(component.config).length).toEqual(3); + expect(component.config['field1']).toEqual('newValue1'); + expect(component.config['field2']).toEqual('value2'); + expect(component.config['field3']).toEqual('value3'); + expect(component.newConfigKey).toEqual('enter field'); + expect(component.newConfigValue).toEqual('enter value'); + component.addConfig(); + expect(component.configKeys).toEqual(['field1', 'field2', 'field3']); + expect(Object.keys(component.configForm.controls).length).toEqual(5); + expect(component.configForm.controls['newConfigKey'].value).toEqual('enter field'); + expect(component.configForm.controls['newConfigValue'].value).toEqual('enter value'); + expect(component.configForm.controls['field1'].value).toEqual('value1'); + expect(component.configForm.controls['field2'].value).toEqual('value2'); + expect(component.configForm.controls['field3'].value).toEqual('value3'); + + component.removeConfig('field1'); + expect(Object.keys(component.config).length).toEqual(2); + expect(component.config['field2']).toEqual('value2'); + expect(component.config['field3']).toEqual('value3'); + expect(component.configKeys).toEqual(['field2', 'field3']); + expect(Object.keys(component.configForm.controls).length).toEqual(4); + expect(component.configForm.controls['newConfigKey'].value).toEqual('enter field'); + expect(component.configForm.controls['newConfigValue'].value).toEqual('enter value'); + expect(component.configForm.controls['field2'].value).toEqual('value2'); + expect(component.configForm.controls['field3'].value).toEqual('value3'); + })); + +}); http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.ts b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.ts new file mode 100644 index 0000000..f363391 --- /dev/null +++ b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.ts @@ -0,0 +1,110 @@ +/** + * 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, OnChanges, Input} from '@angular/core'; +import {FormGroup, Validators, FormControl} from '@angular/forms'; + +@Component({ + selector: 'metron-config-advanced-form', + templateUrl: 'advanced-config-form.component.html', + styleUrls: ['advanced-config-form.component.scss'] +}) +export class AdvancedConfigFormComponent implements OnInit, OnChanges { + + @Input() config: {}; + configKeys: string[] = []; + newConfigKey: string = 'enter field'; + newConfigValue: string = 'enter value'; + configForm: FormGroup; + + constructor() { + + } + + ngOnInit() { + this.configKeys = Object.keys(this.config); + this.configForm = this.createForm(); + } + + ngOnChanges(changes: {[propertyName: string]: any}) { + for (let propName of Object.keys(changes)) { + let chng = changes[propName]; + let cur = JSON.stringify(chng.currentValue); + let prev = JSON.stringify(chng.previousValue); + if (cur !== prev) { + this.configKeys = Object.keys(this.config); + this.configForm = this.createForm(); + } + } + } + + createForm(): FormGroup { + let group: any = {}; + for (let key of this.configKeys) { + group[key] = new FormControl(this.config[key], Validators.required); + } + group['newConfigKey'] = new FormControl(this.newConfigKey, Validators.required); + group['newConfigValue'] = new FormControl(this.newConfigValue, Validators.required); + return new FormGroup(group); + } + + clearKeyPlaceholder() { + if (this.newConfigKey === 'enter field') { + this.newConfigKey = ''; + } + } + + clearValuePlaceholder() { + if (this.newConfigValue === 'enter value') { + this.newConfigValue = ''; + } + } + + saveNewConfig() { + if (this.newConfigKey === '') { + this.newConfigKey = 'enter field'; + } + if (this.newConfigValue === '') { + this.newConfigValue = 'enter value'; + } + if (this.newConfigKey !== 'enter field' && this.newConfigValue !== 'enter value') { + let keyExists = this.config[this.newConfigKey] !== undefined; + this.config[this.newConfigKey] = this.newConfigValue; + if (keyExists) { + this.newConfigKey = 'enter field'; + this.newConfigValue = 'enter value'; + } + + } + } + + addConfig() { + if (this.newConfigKey !== 'enter field' && this.newConfigValue !== 'enter value') { + this.configKeys.push(this.newConfigKey); + this.configForm.addControl(this.newConfigKey, new FormControl(this.newConfigValue, Validators.required)); + this.newConfigKey = 'enter field'; + this.newConfigValue = 'enter value'; + } + } + + removeConfig(key: string) { + delete this.config[key]; + this.configKeys = Object.keys(this.config); + this.configForm.removeControl(key); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.module.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.module.ts b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.module.ts new file mode 100644 index 0000000..d29a369 --- /dev/null +++ b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.module.ts @@ -0,0 +1,14 @@ +import {NgModule} from '@angular/core'; + +import {AdvancedConfigFormComponent} from './advanced-config-form.component'; +import {SharedModule} from '../shared.module'; +import {ReactiveFormsModule} from '@angular/forms'; + +@NgModule({ + imports: [ ReactiveFormsModule, SharedModule ], + exports: [ AdvancedConfigFormComponent ], + declarations: [ AdvancedConfigFormComponent ], + providers: [], +}) +export class AdvancedConfigFormModule { +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/auth-guard.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/shared/auth-guard.spec.ts b/metron-interface/metron-config/src/app/shared/auth-guard.spec.ts new file mode 100644 index 0000000..78c246a --- /dev/null +++ b/metron-interface/metron-config/src/app/shared/auth-guard.spec.ts @@ -0,0 +1,92 @@ +/** + * 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, inject, TestBed} from '@angular/core/testing'; +import {EventEmitter} from '@angular/core'; +import {AuthGuard} from './auth-guard'; +import {AuthenticationService} from '../service/authentication.service'; +import {Router} from '@angular/router'; + +class MockAuthenticationService { + _isAuthenticationChecked: boolean; + _isAuthenticated: boolean; + onLoginEvent: EventEmitter<boolean> = new EventEmitter<boolean>(); + + public isAuthenticationChecked(): boolean { + return this._isAuthenticationChecked; + } + + public isAuthenticated(): boolean { + return this._isAuthenticated; + } +} + +class MockRouter { + navigateByUrl(): any {} +} + +describe('AuthGuard', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + providers: [ + AuthGuard, + {provide: AuthenticationService, useClass: MockAuthenticationService}, + {provide: Router, useClass: MockRouter} + ] + }) + .compileComponents(); + + })); + + it('can instantiate auth guard', + inject([AuthGuard], (authGuard: AuthGuard) => { + expect(authGuard instanceof AuthGuard).toBe(true); + })); + + it('test when authentication is checked', + inject([AuthGuard, AuthenticationService], (authGuard: AuthGuard, authenticationService: MockAuthenticationService) => { + authenticationService._isAuthenticationChecked = true; + authenticationService._isAuthenticated = true; + expect(authGuard.canActivate(null, null)).toBe(true); + + authenticationService._isAuthenticationChecked = true; + authenticationService._isAuthenticated = false; + expect(authGuard.canActivate(null, null)).toBe(false); + })); + + it('test when authentication is not checked', + inject([AuthGuard, AuthenticationService, Router], (authGuard: AuthGuard, + authenticationService: MockAuthenticationService, + router: MockRouter) => { + authenticationService._isAuthenticationChecked = false; + authGuard.canActivate(null, null).subscribe(isUserValid => { + expect(isUserValid).toBe(true); + }); + authenticationService.onLoginEvent.emit(true); + + + spyOn(router, 'navigateByUrl'); + authenticationService._isAuthenticationChecked = false; + authGuard.canActivate(null, null).subscribe(isUserValid => { + expect(isUserValid).toBe(false); + }); + authenticationService.onLoginEvent.emit(false); + expect(router.navigateByUrl).toHaveBeenCalledWith('/login'); + + })); +});