METRON-988 UI for viewing alerts generated by Metron (iraghumitra via nickwallen) closes apache/metron#620
Project: http://git-wip-us.apache.org/repos/asf/metron/repo Commit: http://git-wip-us.apache.org/repos/asf/metron/commit/7d554444 Tree: http://git-wip-us.apache.org/repos/asf/metron/tree/7d554444 Diff: http://git-wip-us.apache.org/repos/asf/metron/diff/7d554444 Branch: refs/heads/master Commit: 7d554444e7701eb4e8787d3c395f65b94db762c4 Parents: de154b0 Author: iraghumitra <[email protected]> Authored: Thu Aug 10 10:07:58 2017 -0400 Committer: nickallen <[email protected]> Committed: Thu Aug 10 10:07:58 2017 -0400 ---------------------------------------------------------------------- metron-interface/metron-alerts/.gitignore | 5 + metron-interface/metron-alerts/NOTICE | 34 + metron-interface/metron-alerts/README.md | 80 + .../metron-alerts/alerts-server-e2e.js | 142 + metron-interface/metron-alerts/angular-cli.json | 63 + metron-interface/metron-alerts/assembly.xml | 55 + .../e2e/alerts-list/alerts-list.e2e-spec.ts | 136 + .../e2e/alerts-list/alerts-list.po.ts | 244 + .../configure-table/configure-table.e2e-spec.ts | 48 + .../save-search/save-search.e2e-spec.ts | 82 + .../e2e/matchers/custom-matchers.d.ts | 23 + .../e2e/matchers/custom-matchers.ts | 36 + .../metron-alerts/e2e/mock-data/alert-list.json | 8496 ++++++++++++++++ .../e2e/mock-data/cluster-state.json | 9261 ++++++++++++++++++ .../metron-alerts/e2e/tsconfig.json | 16 + metron-interface/metron-alerts/karma.conf.js | 61 + .../metron-alerts/package-lock.json | 6427 ++++++++++++ metron-interface/metron-alerts/package.json | 60 + metron-interface/metron-alerts/pom.xml | 133 + .../metron-alerts/protractor.conf.js | 61 + metron-interface/metron-alerts/proxy.conf.json | 19 + .../metron-alerts/scripts/alerts-server.js | 90 + .../metron-alerts/scripts/package.json | 21 + .../scripts/prepend_license_header.sh | 42 + .../metron-alerts/scripts/start-dev.sh | 19 + .../scripts/start-server-for-e2e.sh | 20 + .../metron-alerts/scripts/start_alerts_ui.sh | 24 + .../metron-alerts/src/_variables.scss | 99 + .../alert-details/alert-details.component.html | 47 + .../alert-details/alert-details.component.scss | 72 + .../alert-details/alert-details.component.ts | 108 + .../alert-details/alerts-details.module.ts | 29 + .../alert-details/alerts-details.routing.ts | 24 + .../alerts-list/alerts-list.component.html | 100 + .../alerts-list/alerts-list.component.scss | 311 + .../alerts/alerts-list/alerts-list.component.ts | 399 + .../alerts/alerts-list/alerts-list.module.ts | 38 + .../alerts/alerts-list/alerts-list.routing.ts | 27 + .../src/app/alerts/alerts-list/query-builder.ts | 139 + .../configure-rows/configure-rows-enums.ts | 37 + .../configure-rows.component.html | 45 + .../configure-rows.component.scss | 85 + .../configure-rows.component.spec.ts | 39 + .../configure-rows/configure-rows.component.ts | 112 + .../configure-rows/configure-rows.module.ts | 28 + .../configure-table.component.html | 72 + .../configure-table.component.scss | 55 + .../configure-table.component.ts | 174 + .../configure-table/configure-table.module.ts | 30 + .../configure-table/configure-table.routing.ts | 24 + .../save-search/save-search.component.html | 34 + .../save-search/save-search.component.scss | 22 + .../save-search/save-search.component.spec.ts | 38 + .../alerts/save-search/save-search.component.ts | 77 + .../alerts/save-search/save-search.module.ts | 29 + .../alerts/save-search/save-search.routing.ts | 24 + .../saved-searches.component.html | 30 + .../saved-searches.component.scss | 22 + .../saved-searches.component.spec.ts | 38 + .../saved-searches/saved-searches.component.ts | 130 + .../saved-searches/saved-searches.module.ts | 30 + .../saved-searches/saved-searches.routing.ts | 24 + .../metron-alerts/src/app/app-routing.module.ts | 33 + .../metron-alerts/src/app/app.component.html | 28 + .../metron-alerts/src/app/app.component.scss | 25 + .../metron-alerts/src/app/app.component.spec.ts | 31 + .../metron-alerts/src/app/app.component.ts | 26 + .../src/app/app.config.interface.ts | 20 + .../metron-alerts/src/app/app.config.ts | 17 + .../metron-alerts/src/app/app.module.ts | 76 + metron-interface/metron-alerts/src/app/index.ts | 20 + .../metron-alerts/src/app/model/alert.ts | 61 + .../src/app/model/alerts-search-response.ts | 23 + .../src/app/model/column-metadata.ts | 34 + .../metron-alerts/src/app/model/column-names.ts | 26 + .../metron-alerts/src/app/model/filter.ts | 26 + .../metron-alerts/src/app/model/pagination.ts | 22 + .../metron-alerts/src/app/model/rest-error.ts | 22 + .../metron-alerts/src/app/model/save-search.ts | 48 + .../src/app/model/search-request.ts | 24 + .../src/app/model/table-metadata.ts | 40 + .../src/app/service/alert.service.ts | 69 + .../src/app/service/cluster-metadata.service.ts | 38 + .../src/app/service/column-names.service.ts | 71 + .../src/app/service/configure-table.service.ts | 50 + .../src/app/service/data-source.ts | 62 + .../service/elasticsearch-localstorage-impl.ts | 294 + .../src/app/service/save-search.service.ts | 75 + .../src/app/service/workflow.service.ts | 36 + .../shared/collapse/collapse-component-data.ts | 32 + .../app/shared/collapse/collapse.component.html | 31 + .../app/shared/collapse/collapse.component.scss | 92 + .../shared/collapse/collapse.component.spec.ts | 39 + .../app/shared/collapse/collapse.component.ts | 58 + .../src/app/shared/collapse/collapse.module.ts | 30 + .../directives/alert-search.directive.spec.ts | 21 + .../shared/directives/alert-search.directive.ts | 208 + .../directives/alert-severity.directive.ts | 53 + .../directives/nav-content.directive.spec.ts | 25 + .../shared/directives/nav-content.directive.ts | 35 + .../metron-alerts/src/app/shared/index.ts | 0 .../shared/list-group/list-group.component.html | 19 + .../shared/list-group/list-group.component.scss | 29 + .../list-group/list-group.component.spec.ts | 42 + .../shared/list-group/list-group.component.ts | 47 + .../app/shared/list-group/list-grup.module.ts | 30 + .../src/app/shared/metron-dialog-box.ts | 91 + .../shared/metron-table/metron-sorter/index.ts | 18 + .../metron-sorter/metron-sorter.component.html | 21 + .../metron-sorter/metron-sorter.component.scss | 17 + .../metron-sorter.component.spec.ts | 92 + .../metron-sorter/metron-sorter.component.ts | 46 + .../metron-sorter/metron-sorter.module.ts | 30 + .../metron-table-pagination.component.html | 20 + .../metron-table-pagination.component.scss | 30 + .../metron-table-pagination.component.spec.ts | 42 + .../metron-table-pagination.component.ts | 49 + .../metron-table-pagination.module.ts | 30 + .../metron-table/metron-table.directive.ts | 120 + .../shared/pipes/center-ellipses.pipe.spec.ts | 25 + .../app/shared/pipes/center-ellipses.pipe.ts | 45 + .../pipes/column-name-translate.pipe.spec.ts | 25 + .../shared/pipes/column-name-translate.pipe.ts | 36 + .../src/app/shared/shared.module.ts | 51 + .../src/app/shared/switch/switch.component.html | 20 + .../src/app/shared/switch/switch.component.scss | 91 + .../app/shared/switch/switch.component.spec.ts | 42 + .../src/app/shared/switch/switch.component.ts | 33 + .../src/app/shared/switch/switch.module.ts | 30 + .../metron-alerts/src/app/utils/constants.ts | 22 + .../src/app/utils/elasticsearch-utils.ts | 74 + .../metron-alerts/src/app/utils/enums.ts | 21 + .../metron-alerts/src/app/utils/httpUtil.ts | 45 + .../metron-alerts/src/assets/.gitkeep | 0 .../metron-alerts/src/assets/.npmignore | 0 .../metron-alerts/src/assets/ace/LICENSE | 24 + .../metron-alerts/src/assets/ace/mode-lucene.js | 69 + .../src/assets/ace/theme-monokai.js | 105 + .../src/assets/fonts/Roboto/LICENSE.txt | 202 + .../src/assets/fonts/Roboto/Roboto-Black.ttf | Bin 0 -> 163488 bytes .../assets/fonts/Roboto/Roboto-BlackItalic.ttf | Bin 0 -> 165444 bytes .../src/assets/fonts/Roboto/Roboto-Bold.ttf | Bin 0 -> 162464 bytes .../assets/fonts/Roboto/Roboto-BoldItalic.ttf | Bin 0 -> 163644 bytes .../src/assets/fonts/Roboto/Roboto-Italic.ttf | Bin 0 -> 161484 bytes .../src/assets/fonts/Roboto/Roboto-Light.ttf | Bin 0 -> 162420 bytes .../assets/fonts/Roboto/Roboto-LightItalic.ttf | Bin 0 -> 166492 bytes .../src/assets/fonts/Roboto/Roboto-Medium.ttf | Bin 0 -> 162588 bytes .../assets/fonts/Roboto/Roboto-MediumItalic.ttf | Bin 0 -> 165636 bytes .../src/assets/fonts/Roboto/Roboto-Regular.ttf | Bin 0 -> 162876 bytes .../src/assets/fonts/Roboto/Roboto-Thin.ttf | Bin 0 -> 163132 bytes .../assets/fonts/Roboto/Roboto-ThinItalic.ttf | Bin 0 -> 168276 bytes .../metron-alerts/src/assets/fonts/font.css | 101 + .../metron-alerts/src/assets/images/login.jpg | Bin 0 -> 335521 bytes .../metron-alerts/src/assets/images/logo.png | Bin 0 -> 21186 bytes .../src/environments/environment.js | 25 + .../src/environments/environment.prod.js | 21 + .../src/environments/environment.prod.ts | 21 + .../src/environments/environment.ts | 25 + metron-interface/metron-alerts/src/favicon.ico | Bin 0 -> 1150 bytes metron-interface/metron-alerts/src/font.css | 101 + metron-interface/metron-alerts/src/index.html | 28 + metron-interface/metron-alerts/src/main.ts | 28 + .../metron-alerts/src/metron-dialog.scss | 56 + metron-interface/metron-alerts/src/polyfills.ts | 68 + metron-interface/metron-alerts/src/slider.scss | 139 + metron-interface/metron-alerts/src/styles.scss | 244 + metron-interface/metron-alerts/src/test.ts | 49 + .../metron-alerts/src/tsconfig.app.json | 16 + .../metron-alerts/src/tsconfig.spec.json | 20 + metron-interface/metron-alerts/src/typings.d.ts | 22 + metron-interface/metron-alerts/src/vendor.scss | 71 + metron-interface/metron-alerts/tsconfig.json | 20 + metron-interface/metron-alerts/tslint.json | 103 + metron-interface/pom.xml | 1 + 174 files changed, 33103 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/metron/blob/7d554444/metron-interface/metron-alerts/.gitignore ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/.gitignore b/metron-interface/metron-alerts/.gitignore new file mode 100644 index 0000000..0667762 --- /dev/null +++ b/metron-interface/metron-alerts/.gitignore @@ -0,0 +1,5 @@ +# Created by .ignore support plugin (hsz.mobi) +.idea/ +metron-alerts.iml +node_modules/ +/dist/ http://git-wip-us.apache.org/repos/asf/metron/blob/7d554444/metron-interface/metron-alerts/NOTICE ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/NOTICE b/metron-interface/metron-alerts/NOTICE new file mode 100644 index 0000000..68fa7b1 --- /dev/null +++ b/metron-interface/metron-alerts/NOTICE @@ -0,0 +1,34 @@ +metron-alerts +Copyright 2006-2017 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +This product includes Font Awesome 4.7.0 (http://fortawesome.github.com/Font-Awesome - SIL OFL 1.1) +Copyright (c) 2013 Dave Gandy + +This product includes Angular Common/Compiler/Core/Forms/Http/Router 4.3.2 (https://github.com/angular/angular - MIT) +Copyright (c) 2014-2017 Google, Inc. http://angular.io + +This product includes Ace Builds 1.2.8 (https://github.com/ajaxorg/ace-builds - BSD) +Copyright (c) 2010, Ajax.org B.V. + +This product includes Bootstrap 4.0.0 (https://github.com/twbs/bootstrap - MIT) +Copyright (c) 2011-2017 Twitter, Inc. +Copyright (c) 2011-2017 The Bootstrap Authors + +This product includes core-js 2.4.1 (https://github.com/zloirock/core-js - MIT) +Copyright (c) 2014-2016 Denis Pushkarev + +This product includes jquery 3.2.1 (https://github.com/jquery/jquery - MIT) +Copyright JS Foundation and other contributors, https://js.foundation/ + +This product includes symbol-observable 1.0.4 (https://github.com/blesh/symbol-observable - MIT) +Copyright (c) Sindre Sorhus <[email protected]> (sindresorhus.com) +Copyright (c) Ben Lesh <[email protected]> + +This product includes tether 1.4.0 (https://github.com/HubSpot/tether - MIT) +Copyright (c) 2014-2017 HubSpot, Inc. + +This product includes zone.js 0.8.16 (http://fortawesome.github.com/Font-Awesome - SIL OFL 1.1) +Copyright (c) 2016 Google, Inc. http://git-wip-us.apache.org/repos/asf/metron/blob/7d554444/metron-interface/metron-alerts/README.md ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/README.md b/metron-interface/metron-alerts/README.md new file mode 100644 index 0000000..adba151 --- /dev/null +++ b/metron-interface/metron-alerts/README.md @@ -0,0 +1,80 @@ +- [Caveats](#caveats) +- [Prerequisites](#prerequisites) +- [Development Setup](#development-setup) +- [E2E Tests](#e2e-tests) +- [Mpack Integration](#mpack-integration) +- [Installing on an existing Cluster](#installing-on-an-existing-cluster) + +## Caveats +* UI doesn't have an authentication module yet +* UI uses local storage to save all the data. A middleware needs to be designed and developed for persisting the data + +## Prerequisites +* Elastic search should be up and running and should have some alerts populated by metron topologies +* The alerts can be populated using Quick Dev, Full Dev or any other setup +* UI is developed using angular4 and uses angular-cli +* node.JS >= 7.8.0 + +## Development Setup + +Install all the dependent node_modules using the following command +``` +cd metron/metron-interface/metron-alerts +npm install +``` +UI can be run by using the following command +``` +./scripts/start-dev.sh +``` +**NOTE**: *In the development mode ui by default connects to ES at http://node1:9200 for fetching data. If you wish to change it you can change the ES url at metron/metron-interface/metron-alerts/proxy.conf.json* + +## E2E Tests + +An expressjs server is available for mocking the elastic search api. + +1. Run e2e webserver : + ``` + cd metron/metron-interface/metron-alerts + sh ./scripts/start-server-for-e2e.sh + ``` + +1. run e2e test using the following command + ``` + cd metron/metron-interface/metron-alerts + npm run e2e + ``` + +**NOTE**: *e2e tests covers all the general workflows and we will extend them as we need* + +## Mpack Integration +Yet to come + +## Installing on an existing Cluster +1. Build Metron: + ``` + mvn clean package -DskipTests + ``` + +1. Copy `metron/metron-interface/metron-alerts/target/metron-alerts-METRON_VERSION-archive.tar.gz` to the desired host. + +1. Untar the archive in the target directory. The directory structure will look like: + ``` + bin + start_alerts_ui.sh + web + alerts-ui + package.json + server.js + web assets (html, css, js, ...) + ``` + +1. [Expressjs](https://github.com/expressjs/express) webserver script is included in the build that will serve the application. (The script has few rewrite rules and we can replace expressjs with any other webserver) + +1. Then start the application with the script: + ``` + ./bin/start_alerts_ui.sh + Usage: server.js -p [port] -r [restUrl] + Options: + -p Port to run metron alerts ui [required] + -r, --resturl Url where elastic search rest api is available [required] + ``` http://git-wip-us.apache.org/repos/asf/metron/blob/7d554444/metron-interface/metron-alerts/alerts-server-e2e.js ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/alerts-server-e2e.js b/metron-interface/metron-alerts/alerts-server-e2e.js new file mode 100644 index 0000000..e30ef44 --- /dev/null +++ b/metron-interface/metron-alerts/alerts-server-e2e.js @@ -0,0 +1,142 @@ +#!/usr/bin/env node +/** + * 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. + */ + +'use strict'; + +var os = require('os'); +var jsonfile = require('jsonfile'); +var bodyParser = require('body-parser'); +var app = require('express')(); +var path = require('path'); +var compression = require('compression'); +var serveStatic = require('serve-static'); +var favicon = require('serve-favicon'); +var proxy = require('http-proxy-middleware'); +var argv = require('optimist') + .demand(['p']) + .usage('Usage: server.js -p [port]') + .describe('p', 'Port to run metron alerts ui') + .argv; + +var port = argv.p; +var metronUIAddress = ''; +var ifaces = os.networkInterfaces(); +var restUrl = argv.r || argv.resturl; +var conf = { + "elastic": { + "target": restUrl, + "secure": false + } +}; + +Object.keys(ifaces).forEach(function (dev) { + ifaces[dev].forEach(function (details) { + if (details.family === 'IPv4') { + metronUIAddress += '\n'; + metronUIAddress += 'http://' + details.address + ':' + port; + } + }); +}); + +function setCustomCacheControl (res, path) { + if (serveStatic.mime.lookup(path) === 'text/html') { + res.setHeader('Cache-Control', 'public, max-age=10') + } + res.setHeader("Expires", new Date(Date.now() + 2592000000).toUTCString()); +} + +var indexHTML = function(req, res){ + res.sendFile(path.resolve('dist/index.html')); +}; + +var searchResult = function(req, res){ + console.log('Serving ', req.originalUrl ,'from alert-list.json'); + jsonfile.readFile('e2e/mock-data/alert-list.json', function(err, obj) { + if(err) { + res.json({status: 'error', reason: err.toString()}); + return; + } + + var filter = req.body.query.query_string.query; + if (filter !== '*') { + filter = filter.replace(/\\/g, ''); + var lastIndex = filter.lastIndexOf(':'); + var key = filter.substr(0, lastIndex); + var value = filter.substr(lastIndex+1); + obj.hits.hits = obj.hits.hits.filter(function (hits) { + return hits._source[key] === value; + }); + } + + var sortField = req.body.sort && req.body.sort.length === 1 && req.body.sort[0]; + if (sortField) { + var key = Object.keys(sortField)[0]; + var order = sortField[key].order; + obj.hits.hits = obj.hits.hits.sort(function(o1, o2) { + if (!o1._source[key] || !o2._source[key]) { + return -1; + } + + if (typeof(o1._source[key]) === 'number' && typeof(o2._source[key]) === 'number') { + return order === 'desc' ? o2._source[key]- (o1._source[key]) : o1._source[key] - (o2._source[key]); + } else { + return order === 'desc' ? o2._source[key].localeCompare(o1._source[key]) : o1._source[key].localeCompare(o2._source[key]); + } + + }); + } + + obj.hits.total = obj.hits.hits.length; + obj.hits.hits = obj.hits.hits.splice(req.body.from, req.body.size); + res.json(obj); + }); +}; + +var clusterState = function(req, res){ + console.log('Serving ', req.originalUrl ,'from cluster-state.json'); + jsonfile.readFile('e2e/mock-data/cluster-state.json', function(err, obj) { + if(err) { + res.json({status: 'error', reason: err.toString()}); + return; + } + res.json(obj); + }); +}; + + +app.use(compression()); +app.use(bodyParser.json()); +app.use(favicon(path.join(__dirname, 'dist/favicon.ico'))); +app.use(serveStatic(path.join(__dirname, 'dist'), { + maxAge: '1d', + setHeaders: setCustomCacheControl +})); + +app.post('^/search/*', searchResult); +app.use('/_cluster', clusterState); +app.get('/alerts-list', indexHTML); +app.get('', indexHTML); +app.use(function(req, res, next){ + res.status(404).sendStatus(304); +}); + + +app.listen(port, function(){ + console.log("Metron alerts ui is listening on " + metronUIAddress); +}); http://git-wip-us.apache.org/repos/asf/metron/blob/7d554444/metron-interface/metron-alerts/angular-cli.json ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/angular-cli.json b/metron-interface/metron-alerts/angular-cli.json new file mode 100644 index 0000000..68922d7 --- /dev/null +++ b/metron-interface/metron-alerts/angular-cli.json @@ -0,0 +1,63 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "project": { + "name": "metron-alerts" + }, + "apps": [ + { + "root": "src", + "outDir": "dist", + "assets": [ + "assets", + "favicon.ico" + ], + "index": "index.html", + "main": "main.ts", + "polyfills": "polyfills.ts", + "test": "test.ts", + "tsconfig": "tsconfig.app.json", + "testTsconfig": "tsconfig.spec.json", + "prefix": "app", + "styles": [ + "../node_modules/font-awesome/css/font-awesome.css", + "vendor.scss", + "styles.scss" + ], + "scripts": [ + "../node_modules/jquery/dist/jquery.js", + "../node_modules/tether/dist/js/tether.js", + "../node_modules/ace-builds/src-noconflict/ace.js" + ], + "environmentSource": "environments/environment.ts", + "environments": { + "dev": "environments/environment.ts", + "prod": "environments/environment.prod.ts" + } + } + ], + "e2e": { + "protractor": { + "config": "./protractor.conf.js" + } + }, + "lint": [ + { + "project": "src/tsconfig.app.json" + }, + { + "project": "src/tsconfig.spec.json" + }, + { + "project": "e2e/tsconfig.e2e.json" + } + ], + "test": { + "karma": { + "config": "./karma.conf.js" + } + }, + "defaults": { + "styleExt": "scss", + "component": {} + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/7d554444/metron-interface/metron-alerts/assembly.xml ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/assembly.xml b/metron-interface/metron-alerts/assembly.xml new file mode 100644 index 0000000..63e7380 --- /dev/null +++ b/metron-interface/metron-alerts/assembly.xml @@ -0,0 +1,55 @@ +<!-- + 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. + --> + +<assembly> + <id>archive</id> + <formats> + <format>tar.gz</format> + </formats> + <includeBaseDirectory>false</includeBaseDirectory> + <fileSets> + <fileSet> + <directory>${project.basedir}/dist</directory> + <outputDirectory>/web/alerts-ui</outputDirectory> + <excludes> + </excludes> + <fileMode>0644</fileMode> + </fileSet> + <fileSet> + <directory>${project.basedir}/scripts</directory> + <outputDirectory>/web/alerts-ui</outputDirectory> + <includes> + <include>package.json</include> + <include>alerts-server.js</include> + </includes> + <fileMode>0644</fileMode> + </fileSet> + <fileSet> + <directory>${project.basedir}/scripts</directory> + <outputDirectory>/bin</outputDirectory> + <useDefaultExcludes>true</useDefaultExcludes> + <includes> + <include>start_alerts_ui.sh</include> + </includes> + <fileMode>0755</fileMode> + <lineEnding>unix</lineEnding> + <filtered>true</filtered> + </fileSet> + </fileSets> + <files> + <file> + <source>src/favicon.ico</source> + <outputDirectory>/web/alerts-ui</outputDirectory> + </file> + </files> +</assembly> http://git-wip-us.apache.org/repos/asf/metron/blob/7d554444/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.e2e-spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.e2e-spec.ts b/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.e2e-spec.ts new file mode 100644 index 0000000..454ee6e --- /dev/null +++ b/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.e2e-spec.ts @@ -0,0 +1,136 @@ +/// <reference path="../matchers/custom-matchers.d.ts"/> +/** + * 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 { MetronAlertsPage } from './alerts-list.po'; +import { customMatchers } from '../matchers/custom-matchers'; + +describe('metron-alerts App', function() { + let page: MetronAlertsPage; + let columnNames = [ 'Score', '_id', 'timestamp', 'source:type', 'ip_src_addr', 'enrichm...:country', + 'ip_dst_addr', 'host', 'alert_status', '', '' ]; + let colNamesColumnConfig = [ 'score', '_id', 'timestamp', 'source:type', 'ip_src_addr', 'enrichments:geo:ip_dst_addr:country', + 'ip_dst_addr', 'host', 'alert_status' ]; + + beforeEach(() => { + page = new MetronAlertsPage(); + jasmine.addMatchers(customMatchers); + }); + + it('should have all the UI elements', () => { + page.navigateTo(); + page.clearLocalStorage(); + + expect(page.isMetronLogoPresent()).toEqualBcoz(true, 'for Metron Logo'); + expect(page.isSavedSearchButtonPresent()).toEqualBcoz(true, 'for SavedSearch Button'); + expect(page.isClearSearchPresent()).toEqualBcoz(true, 'for Clear Search'); + expect(page.isSearchButtonPresent()).toEqualBcoz(true, 'for Search Button'); + expect(page.isSaveSearchButtonPresent()).toEqualBcoz(true, 'for Save Search Button'); + expect(page.isTableSettingsButtonPresent()).toEqualBcoz(true, 'for table settings button'); + expect(page.isPausePlayRefreshButtonPresent()).toEqualBcoz(true, 'for pause/play button'); + expect(page.isConfigureTableColumnsPresent()).toEqualBcoz(true, 'for alerts table column configure button'); + + expect(page.getAlertTableTitle()).toEqualBcoz('Alerts (25 of 169)', 'for alerts title'); + expect(page.getActionDropdownItems()).toEqualBcoz([ 'Open', 'Dismiss', 'Escalate', 'Resolve' ], 'for default dropdown actions'); + expect(page.getTableColumnNames()).toEqualBcoz(columnNames, 'for default column names for alert list table'); + }); + + it('should have all pagination controls and they should be working', () => { + expect(page.isChevronLeftEnabled()).toEqualBcoz(false, 'for left chevron to be disabled for first page'); + expect(page.getPaginationText()).toEqualBcoz('1 - 25 of 169', 'for pagination text'); + expect(page.isChevronRightEnabled()).toEqualBcoz(true, 'for right chevron to be enabled for first page'); + + page.clickChevronRight(); + + expect(page.isChevronLeftEnabled()).toEqualBcoz(true, 'for left chevron to be enabled for second page'); + expect(page.getPaginationText()).toEqualBcoz('26 - 50 of 169', 'for pagination text'); + expect(page.isChevronRightEnabled()).toEqualBcoz(true, 'for right chevron to be enabled for second page'); + + page.clickChevronRight(); + + expect(page.isChevronLeftEnabled()).toEqualBcoz(true, 'for left chevron to be enabled for third page'); + expect(page.getPaginationText()).toEqualBcoz('51 - 75 of 169', 'for pagination text'); + expect(page.isChevronRightEnabled()).toEqualBcoz(true, 'for right chevron to be enabled for third page'); + + page.clickChevronRight(4); + + expect(page.isChevronLeftEnabled()).toEqualBcoz(true, 'for left chevron to be enabled for last page'); + expect(page.getPaginationText()).toEqualBcoz('151 - 169 of 169', 'for pagination text'); + expect(page.isChevronRightEnabled()).toEqualBcoz(false, 'for right chevron to be disabled for last page'); + + page.clickChevronLeft(7); + + expect(page.isChevronLeftEnabled()).toEqualBcoz(false, 'for left chevron to be disabled for first page again'); + expect(page.getPaginationText()).toEqualBcoz('1 - 25 of 169', 'for pagination text'); + expect(page.isChevronRightEnabled()).toEqualBcoz(true, 'for right chevron to be enabled for first page again'); + + }); + + it('should have all settings controls and they should be working', () => { + let settingsPaneLbelNames = [ 'REFRESH RATE', 'ROWS PER PAGE', 'HIDE Resolved Alerts', 'HIDE Dismissed Alerts' ]; + let settingPaneRefreshIntervals = [ '5s', '10s', '15s', '30s', '1m', '10m', '1h' ]; + let settingsPanePageSize = [ '10', '25', '50', '100', '250', '500', '1000' ]; + + page.clickSettings(); + + expect(page.getSettingsLabels()).toEqualBcoz(settingsPaneLbelNames, 'for table settings labels'); + + expect(page.getRefreshRateOptions()).toEqualBcoz(settingPaneRefreshIntervals, 'for table settings refresh rate labels'); + expect(page.getRefreshRateSelectedOption()).toEqualBcoz([ '1m' ], 'for table settings default refresh rate'); + + page.clickRefreshInterval('10s'); + expect(page.getRefreshRateSelectedOption()).toEqualBcoz([ '10s' ], 'for refresh interval 10s'); + + page.clickRefreshInterval('1h'); + expect(page.getRefreshRateSelectedOption()).toEqualBcoz([ '1h' ], 'for refresh interval 1h'); + + expect(page.getPageSizeOptions()).toEqualBcoz(settingsPanePageSize, 'for table settings refresh rate labels'); + expect(page.getPageSizeSelectedOption()).toEqualBcoz([ '25' ], 'for table settings default page size'); + + page.clickPageSize('10'); + expect(page.getPageSizeSelectedOption()).toEqualBcoz([ '10' ], 'for page size 10'); + + page.clickPageSize('100'); + expect(page.getPageSizeSelectedOption()).toEqualBcoz([ '100' ], 'for page size 100'); + + page.clickSettings(); + }); + + it('play pause should start polling and stop polling ', () => { + expect(page.getPlayPauseState()).toEqual('fa fa-pause', 'for default pause option'); + + page.clickPlayPause(); + expect(page.getPlayPauseState()).toEqual('fa fa-play', 'for default pause option'); + + page.clickPlayPause(); + expect(page.getPlayPauseState()).toEqual('fa fa-pause', 'for default pause option'); + }); + + it('should select columns from table configuration', () => { + let newColNamesColumnConfig = [ 'score', 'timestamp', 'source:type', 'ip_src_addr', 'enrichments:geo:ip_dst_addr:country', + 'ip_dst_addr', 'host', 'alert_status', 'guid' ]; + + page.clickConfigureTable(); + expect(page.getSelectedColumnNames()).toEqual(colNamesColumnConfig, 'for default selected column names'); + page.toggleSelectCol('_id'); + page.toggleSelectCol('guid', 'method'); + expect(page.getSelectedColumnNames()).toEqual(newColNamesColumnConfig, 'for guid added to selected column names'); + page.saveConfigureColumns(); + + }); + +}); http://git-wip-us.apache.org/repos/asf/metron/blob/7d554444/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.po.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.po.ts b/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.po.ts new file mode 100644 index 0000000..cefa137 --- /dev/null +++ b/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.po.ts @@ -0,0 +1,244 @@ +/** + * 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 {browser, element, by, protractor} from 'protractor'; + +export class MetronAlertsPage { + navigateTo() { + browser.waitForAngularEnabled(false); + return browser.get('/alerts-list'); + } + + clearLocalStorage() { + browser.executeScript('window.localStorage.clear();'); + } + + isMetronLogoPresent() { + return element(by.css('img[src="../assets/images/logo.png"]')).isPresent(); + } + + isSavedSearchButtonPresent() { + return element(by.buttonText('Searches')).isPresent(); + } + + isClearSearchPresent() { + return element(by.css('.btn-search-clear')).isPresent(); + } + + isSearchButtonPresent() { + return element(by.css('.btn-search-clear')).isPresent(); + } + + isSaveSearchButtonPresent() { + return element(by.css('.save-button')).isPresent(); + } + + isTableSettingsButtonPresent() { + return element(by.css('.btn.settings')).isPresent(); + } + + isPausePlayRefreshButtonPresent() { + return element(by.css('.btn.pause-play')).isPresent(); + } + + isActionsButtonPresent() { + return element.all(by.buttonText('ACTIONS')).isPresent(); + } + + isConfigureTableColumnsPresent() { + return element(by.css('.fa.fa-cog.configure-table-icon')).isPresent(); + } + + getAlertTableTitle() { + return element(by.css('.col-form-label-lg')).getText(); + } + + getActionDropdownItems() { + return element(by.buttonText('ACTIONS')).click().then(() => element.all(by.css('.dropdown-menu .dropdown-item.disabled')).getText()); + } + + getTableColumnNames() { + return element.all(by.css('app-alerts-list .table th')).getText(); + } + + getPaginationText() { + return element(by.css('metron-table-pagination span')).getText(); + } + + isChevronLeftEnabled() { + return element(by.css('metron-table-pagination .fa.fa-chevron-left')).getAttribute('class').then((classes) => { + return classes.split(' ').indexOf('disabled') === -1; + }); + } + + isChevronRightEnabled() { + return element(by.css('metron-table-pagination .fa.fa-chevron-right')).getAttribute('class').then((classes) => { + return classes.split(' ').indexOf('disabled') === -1; + }); + } + + clickChevronRight(times = 1) { + for (let i = 0; i < times; i++) { + element(by.css('metron-table-pagination .fa.fa-chevron-right')).click(); + } + } + + clickChevronLeft(times = 1) { + for (let i = 0; i < times; i++) { + element(by.css('metron-table-pagination .fa.fa-chevron-left')).click(); + } + } + + clickSettings() { + return element(by.css('.btn.settings')).click(); + } + + getSettingsLabels() { + return element.all(by.css('form label:not(.switch)')).getText(); + } + + getRefreshRateOptions() { + return element.all(by.css('.preset-row.refresh-interval .preset-cell')).getText(); + } + + getRefreshRateSelectedOption() { + return element.all(by.css('.preset-row.refresh-interval .preset-cell.is-active')).getText(); + } + + getPageSizeOptions() { + return element.all(by.css('.preset-row.page-size .preset-cell')).getText(); + } + + getPageSizeSelectedOption() { + return element.all(by.css('.preset-row.page-size .preset-cell.is-active')).getText(); + } + + clickRefreshInterval(intervalText: string) { + return element(by.cssContainingText('.refresh-interval .preset-cell', intervalText)).click(); + } + + clickPageSize(pageSizeText: string) { + return element.all(by.cssContainingText('.page-size .preset-cell', pageSizeText)).first().click(); + } + + clickConfigureTable() { + element(by.css('app-alerts-list .fa.fa-cog.configure-table-icon')).click(); + browser.sleep(1000); + } + + clickCloseSavedSearch() { + element(by.css('app-saved-searches .close-button')).click(); + } + + clickSavedSearch() { + element(by.buttonText('Searches')).click(); + browser.sleep(1000); + } + + clickPlayPause() { + element(by.css('.btn.pause-play')).click(); + } + + clickTableText(name: string) { + element.all(by.linkText(name)).get(0).click(); + } + + clickClearSearch() { + element(by.css('.btn-search-clear')).click(); + } + + getSavedSearchTitle() { + return element(by.css('app-saved-searches .form-title')).getText(); + } + + getPlayPauseState() { + return element(by.css('.btn.pause-play i')).getAttribute('class'); + } + + getSearchText() { + return element(by.css('.ace_line')).getText(); + } + + getRecentSearchOptions() { + browser.sleep(1000); + let map = {}; + let recentSearches = element.all(by.css('metron-collapse')).get(0); + return recentSearches.all(by.css('a')).getText().then(title => { + return recentSearches.all(by.css('.collapse.show')).getText().then(values => { + map[title] = values; + return map; + }); + }); + } + + getSavedSearchOptions() { + browser.sleep(1000); + let map = {}; + let recentSearches = element.all(by.css('metron-collapse')).get(1); + return recentSearches.all(by.css('a')).getText().then(title => { + return recentSearches.all(by.css('.collapse.show')).getText().then(values => { + map[title] = values; + return map; + }); + }); + } + + getSelectedColumnNames() { + return element.all(by.css('app-configure-table input[type="checkbox"]:checked')).map(ele => { + return ele.getAttribute('id').then(id => id.replace(/select-deselect-/, '')); + }); + } + + toggleSelectCol(name: string, scrollTo = '') { + scrollTo = scrollTo === '' ? name : scrollTo; + let ele = element(by.css('app-configure-table label[for="select-deselect-' + name + '"]')); + let scrollToEle = element(by.css('app-configure-table label[for="select-deselect-' + scrollTo + '"]')); + browser.actions().mouseMove(scrollToEle).perform().then(() => ele.click()); + } + + saveSearch(name: string) { + return element(by.css('.save-button')).click().then(() => element(by.css('app-save-search #name')).sendKeys(name)) + .then(() => element(by.css('app-save-search button[type="submit"]')).click()); + } + + saveConfigureColumns() { + element(by.css('app-configure-table')).element(by.buttonText('SAVE')).click(); + } + + clickRemoveSearchChip() { + let aceLine = element.all(by.css('.ace_keyword')).get(0); + browser.actions().mouseMove(aceLine).perform().then(() => { + this.waitForElementPresence(element(by.css('.ace_value i'))).then(() => { + element.all(by.css('.ace_value i')).get(0).click(); + }); + }); + } + + setSearchText(search: string) { + this.clickClearSearch(); + element(by.css('app-alerts-list .ace_text-input')).sendKeys(protractor.Key.BACK_SPACE); + element(by.css('app-alerts-list .ace_text-input')).sendKeys(search); + element(by.css('app-alerts-list .ace_text-input')).sendKeys(protractor.Key.ENTER); + browser.sleep(2000); + } + + waitForElementPresence (element ) { + let EC = protractor.ExpectedConditions; + return browser.wait(EC.presenceOf(element)); + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/7d554444/metron-interface/metron-alerts/e2e/alerts-list/configure-table/configure-table.e2e-spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/e2e/alerts-list/configure-table/configure-table.e2e-spec.ts b/metron-interface/metron-alerts/e2e/alerts-list/configure-table/configure-table.e2e-spec.ts new file mode 100644 index 0000000..55d2a6e --- /dev/null +++ b/metron-interface/metron-alerts/e2e/alerts-list/configure-table/configure-table.e2e-spec.ts @@ -0,0 +1,48 @@ +/// <reference path="../../matchers/custom-matchers.d.ts"/> +/** + * 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 { MetronAlertsPage } from '../alerts-list.po'; +import {customMatchers} from '../../matchers/custom-matchers'; + +describe('metron-alerts configure table', function() { + let page: MetronAlertsPage; + let colNamesColumnConfig = [ 'score', '_id', 'timestamp', 'source:type', 'ip_src_addr', 'enrichments:geo:ip_dst_addr:country', + 'ip_dst_addr', 'host', 'alert_status' ]; + + beforeEach(() => { + page = new MetronAlertsPage(); + jasmine.addMatchers(customMatchers); + }); + + it('should select columns from table configuration', () => { + let newColNamesColumnConfig = [ 'score', 'timestamp', 'source:type', 'ip_src_addr', 'enrichments:geo:ip_dst_addr:country', + 'ip_dst_addr', 'host', 'alert_status', 'guid' ]; + + page.clearLocalStorage(); + page.navigateTo(); + + page.clickConfigureTable(); + expect(page.getSelectedColumnNames()).toEqual(colNamesColumnConfig, 'for default selected column names'); + page.toggleSelectCol('_id'); + page.toggleSelectCol('guid', 'method'); + expect(page.getSelectedColumnNames()).toEqual(newColNamesColumnConfig, 'for guid added to selected column names'); + page.saveConfigureColumns(); + + }); + +}); http://git-wip-us.apache.org/repos/asf/metron/blob/7d554444/metron-interface/metron-alerts/e2e/alerts-list/save-search/save-search.e2e-spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/e2e/alerts-list/save-search/save-search.e2e-spec.ts b/metron-interface/metron-alerts/e2e/alerts-list/save-search/save-search.e2e-spec.ts new file mode 100644 index 0000000..fc02e12 --- /dev/null +++ b/metron-interface/metron-alerts/e2e/alerts-list/save-search/save-search.e2e-spec.ts @@ -0,0 +1,82 @@ +/// <reference path="../../matchers/custom-matchers.d.ts"/> +/** + * 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 { customMatchers } from '../../matchers/custom-matchers'; +import {MetronAlertsPage} from '../alerts-list.po'; + +describe('metron-alerts Search', function() { + let page: MetronAlertsPage; + + beforeEach(() => { + page = new MetronAlertsPage(); + jasmine.addMatchers(customMatchers); + }); + + it('should display all the default values for saved searches', () => { + page.clearLocalStorage(); + page.navigateTo(); + + page.clickSavedSearch(); + expect(page.getSavedSearchTitle()).toEqualBcoz('Searches', 'for saved searches title'); + expect(page.getRecentSearchOptions()).toEqual({ 'Recent Searches': [ 'No Recent Searches yet' ] }, 'for recent search options'); + expect(page.getSavedSearchOptions()).toEqual({ 'Saved Searches': [ 'No Saved Searches yet' ] }, 'for saved search options'); + page.clickCloseSavedSearch(); + + }); + + it('should have all save search controls and they save search should be working', () => { + page.saveSearch('e2e-1'); + page.clickSavedSearch(); + expect(page.getSavedSearchOptions()).toEqual({ 'Saved Searches': [ 'e2e-1' ] }, 'for saved search options e2e-1'); + page.clickCloseSavedSearch(); + }); + + it('should populate search items when selected on table', () => { + page.clickTableText('US'); + expect(page.getSearchText()).toEqual('enrichments:geo:ip_dst_addr:country:US', 'for search text ip_dst_addr_country US'); + page.clickClearSearch(); + expect(page.getSearchText()).toEqual('*', 'for clear search'); + }); + + it('should delete search items from search box', () => { + page.clickTableText('US'); + expect(page.getSearchText()).toEqual('enrichments:geo:ip_dst_addr:country:US', 'for search text ip_dst_addr_country US'); + page.clickRemoveSearchChip(); + expect(page.getSearchText()).toEqual('*', 'for search chip remove'); + }); + + it('should delete first search items from search box having multiple search fields', () => { + page.clickTableText('US'); + page.clickTableText('bro'); + expect(page.getSearchText()).toEqual('enrichments:geo:ip_dst_addr:country:US AND source:type:bro', 'for search text US and bro'); + page.clickRemoveSearchChip(); + expect(page.getSearchText()).toEqual('source:type:bro', 'for search text bro after US is removed'); + page.clickRemoveSearchChip(); + expect(page.getSearchText()).toEqual('*', 'for search chip remove for two search texts'); + }); + + it('manually entering search queries to search box and pressing enter key should search', () => { + page.setSearchText('enrichments:geo:ip_dst_addr:country:US'); + expect(page.getPaginationText()).toEqualBcoz('1 - 22 of 22', + 'for pagination text with search text enrichments:geo:ip_dst_addr:country:US'); + page.setSearchText('enrichments:geo:ip_dst_addr:country:RU'); + expect(page.getPaginationText()).toEqualBcoz('1 - 25 of 44', + 'for pagination text with search text enrichments:geo:ip_dst_addr:country:RU as text'); + }); + +}); http://git-wip-us.apache.org/repos/asf/metron/blob/7d554444/metron-interface/metron-alerts/e2e/matchers/custom-matchers.d.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/e2e/matchers/custom-matchers.d.ts b/metron-interface/metron-alerts/e2e/matchers/custom-matchers.d.ts new file mode 100644 index 0000000..fcaa8b6 --- /dev/null +++ b/metron-interface/metron-alerts/e2e/matchers/custom-matchers.d.ts @@ -0,0 +1,23 @@ +/** + * 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. + */ + +declare module jasmine { + interface Matchers { + toEqualBcoz(expected: any, message: string): boolean; + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/7d554444/metron-interface/metron-alerts/e2e/matchers/custom-matchers.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/e2e/matchers/custom-matchers.ts b/metron-interface/metron-alerts/e2e/matchers/custom-matchers.ts new file mode 100644 index 0000000..3c832c5 --- /dev/null +++ b/metron-interface/metron-alerts/e2e/matchers/custom-matchers.ts @@ -0,0 +1,36 @@ +/** + * 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. + */ +let customMatchers: jasmine.CustomMatcherFactories = { + toEqualBcoz: function (util: jasmine.MatchersUtil, customEqualityTesters: Array<jasmine.CustomEqualityTester>): jasmine.CustomMatcher { + return { + compare: function (actual: any, expected: any, message = ''): jasmine.CustomMatcherResult { + if (expected === undefined) { + expected = ''; + } + let result = {pass: false, message: ''}; + result.pass = util.equals(actual, expected, customEqualityTesters); + if (!result.pass) { + result.message = 'Expected \'' + actual + '\' to equal \'' + expected + '\' ' + message; + } + return result; + } + }; + } +}; + +export {customMatchers};
