diff --git a/.gitignore b/.gitignore index 6078655b0..f47a3ae82 100644 --- a/.gitignore +++ b/.gitignore @@ -23,8 +23,11 @@ data/walkoff.config docs/_*/ npm-debug.log tests/testWorkflows/testGeneratedWorkflows_bkup +tests/tmp/* walkoff/api/composed_api.yaml walkoff/client/node_modules/ walkoff/client/build/ +walkoff/client/coverage/ +walkoff/client/dist/ !apps/*.py !interfaces/*.py \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 5533c680a..21f003a38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,93 @@ -language: python -os: - - linux -python: - - "2.7" - - "3.4" - - "3.5" - - "3.6" -install: - - pip install -r requirements.txt - - pip install coverage -before_script: - - python scripts/generate_certificates.py -script: - - coverage run run_all_tests.py - - coverage combine - - coverage report -branches: - only: - - master - - development +matrix: + include: + - language: python + os: linux + python: "2.7" + install: + - pip install -r requirements.txt + - pip install coverage + before_script: + - python scripts/generate_certificates.py + script: + - coverage run run_all_tests.py + - coverage combine + - coverage report + branches: + only: + - master + - development + + - language: python + os: linux + python: "3.4" + install: + - pip install -r requirements.txt + - pip install coverage + before_script: + - python scripts/generate_certificates.py + script: + - coverage run run_all_tests.py + - coverage combine + - coverage report + branches: + only: + - master + - development + + - language: python + os: linux + python: "3.5" + install: + - pip install -r requirements.txt + - pip install coverage + before_script: + - python scripts/generate_certificates.py + script: + - coverage run run_all_tests.py + - coverage combine + - coverage report + branches: + only: + - master + - development + + - language: python + os: linux + python: "3.6" + install: + - pip install -r requirements.txt + - pip install coverage + before_script: + - python scripts/generate_certificates.py + script: + - coverage run run_all_tests.py + - coverage combine + - coverage report + branches: + only: + - master + - development + + - language: node_js + node_js: + - "node" + addons: + apt: + sources: + - google-chrome + packages: + - google-chrome-stable + - google-chrome-beta + before_install: + - export CHROME_BIN=chromium-browser + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start + before_script: + - cd walkoff/client + - npm install + script: npm run test -- --single-run + branches: + only: + - master + - development diff --git a/CHANGELOG.md b/CHANGELOG.md index b2c5673a5..3f91e34a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Changelog + +## [0.6.5] +###### 2018-02-01 + +### Added +* Webpack is now used to increase UI performance + +### Changed +* Default return codes for the Walkoff app + +### Contributor +* Some UI tests are now run on Travis-CI + ## [0.6.4] ###### 2018-01-18 diff --git a/README.md b/README.md index 64f6fa5b0..dc75d5ade 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://img.shields.io/travis/iadgov/WALKOFF/master.svg?maxAge=3600&label=Linux)](https://travis-ci.org/iadgov/WALKOFF) Windows [![Build status](https://ci.appveyor.com/api/projects/status/hs6ujwd1f87n39ut/branch/master?svg=true)](https://ci.appveyor.com/project/iadgovuser11/walkoff/branch/master) +[![Build Status](https://img.shields.io/travis/iadgov/WALKOFF/master.svg?maxAge=3600&label=Linux)](https://travis-ci.org/iadgov/WALKOFF) [![Build status](https://ci.appveyor.com/api/projects/status/hs6ujwd1f87n39ut/branch/master?svg=true)](https://ci.appveyor.com/project/iadgovuser11/walkoff/branch/master) [![Maintainability](https://api.codeclimate.com/v1/badges/330249e13845a07a69a2/maintainability)](https://codeclimate.com/github/iadgov/WALKOFF/maintainability)[![GitHub (pre-)release](https://img.shields.io/github/release/iadgov/WALKOFF/all.svg?style=flat)](release) diff --git a/apps/Walkoff/api.yaml b/apps/Walkoff/api.yaml index 02d932163..754beb315 100644 --- a/apps/Walkoff/api.yaml +++ b/apps/Walkoff/api.yaml @@ -21,22 +21,22 @@ actions: Success: schema: type: string - enum: [Success] + enum: ["Success"] TimedOut: failure: true schema: type: string - enum: [Connection timed out] + enum: ["Connection timed out"] WalkoffNotFound: failure: true schema: type: string - enum: [Could not locate Walkoff instance] + enum: ["Could not locate Walkoff instance"] AuthenticationError: failure: true schema: type: string - enum: [Invalid login] + enum: ["Invalid login"] UnknownResponse: failure: true schema: @@ -50,19 +50,20 @@ actions: description: Timeout on the request (in seconds) type: number default: 2.0 + default_return: Success returns: Success: schema: type: string - enum: [Success] + enum: ["Success"] TimedOut: schema: type: string - enum: [Connection timed out] + enum: ["Connection timed out"] NotConnected: schema: type: string - enum: [Not connected to Walkoff] + enum: ["Not connected to Walkoff"] is connected: run: app.Walkoff.is_connected description: Is Walkoff connected? @@ -78,6 +79,7 @@ actions: description: Timeout on the request (in seconds) type: number default: 2.0 + default_return: Success returns: Success: schema: @@ -125,15 +127,15 @@ actions: TimedOut: schema: type: string - enum: [Connection timed out] + enum: ["Connection timed out"] Unauthorized: schema: type: string - enum: [Unauthorized credentials] + enum: ["Unauthorized credentials"] NotConnected: schema: type: string - enum: [Not connected to Walkoff] + enum: ["Not connected to Walkoff"] UnknownResponse: schema: type: string @@ -146,6 +148,7 @@ actions: description: Timeout on the request (in seconds) type: number default: 2.0 + default_return: Success returns: Success: schema: @@ -169,15 +172,15 @@ actions: TimedOut: schema: type: string - enum: [Connection timed out] + enum: ["Connection timed out"] Unauthorized: schema: type: string - enum: [Unauthorized credentials] + enum: ["Unauthorized credentials"] NotConnected: schema: type: string - enum: [Not connected to Walkoff] + enum: ["Not connected to Walkoff"] UnknownResponse: schema: type: string @@ -190,6 +193,7 @@ actions: description: Timeout on the request (in seconds) type: number default: 2.0 + default_return: Success returns: Success: schema: @@ -217,15 +221,15 @@ actions: TimedOut: schema: type: string - enum: [Connection timed out] + enum: ["Connection timed out"] Unauthorized: schema: type: string - enum: [Unauthorized credentials] + enum: ["Unauthorized credentials"] NotConnected: schema: type: string - enum: [Not connected to Walkoff] + enum: ["Not connected to Walkoff"] UnknownResponse: schema: type: string @@ -238,6 +242,7 @@ actions: description: Timeout on the request (in seconds) type: number default: 2.0 + default_return: Success returns: Success: schema: @@ -262,15 +267,15 @@ actions: TimedOut: schema: type: string - enum: [Connection timed out] + enum: ["Connection timed out"] Unauthorized: schema: type: string - enum: [Unauthorized credentials] + enum: ["Unauthorized credentials"] NotConnected: schema: type: string - enum: [Not connected to Walkoff] + enum: ["Not connected to Walkoff"] UnknownResponse: schema: type: string @@ -283,6 +288,7 @@ actions: description: Timeout on the request (in seconds) type: number default: 2.0 + default_return: Success returns: Success: schema: @@ -306,15 +312,15 @@ actions: TimedOut: schema: type: string - enum: [Connection timed out] + enum: ["Connection timed out"] Unauthorized: schema: type: string - enum: [Unauthorized credentials] + enum: ["Unauthorized credentials"] NotConnected: schema: type: string - enum: [Not connected to Walkoff] + enum: ["Not connected to Walkoff"] UnknownResponse: schema: type: string @@ -335,6 +341,7 @@ actions: description: Timeout on the request (in seconds) type: number default: 2.0 + default_return: Success returns: Success: schema: @@ -342,19 +349,19 @@ actions: WorkflowNotFound: schema: type: string - enum: [Playbook not found, Workflow not found] + enum: ["Playbook not found, Workflow not found"] TimedOut: schema: type: string - enum: [Connection timed out] + enum: ["Connection timed out"] Unauthorized: schema: type: string - enum: [Unauthorized credentials] + enum: ["Unauthorized credentials"] NotConnected: schema: type: string - enum: [Not connected to Walkoff] + enum: ["Not connected to Walkoff"] UnknownResponse: schema: type: string @@ -375,6 +382,7 @@ actions: description: Timeout on the request (in seconds) type: number default: 2.0 + default_return: Success returns: Success: schema: @@ -385,15 +393,15 @@ actions: TimedOut: schema: type: string - enum: [Connection timed out] + enum: ["Connection timed out"] Unauthorized: schema: type: string - enum: [Unauthorized credentials] + enum: ["Unauthorized credentials"] NotConnected: schema: type: string - enum: [Not connected to Walkoff] + enum: ["Not connected to Walkoff"] UnknownResponse: schema: type: string @@ -418,6 +426,7 @@ actions: description: Timeout on the request (in seconds) type: number default: 2.0 + default_return: Success returns: Success: schema: @@ -425,15 +434,15 @@ actions: TimedOut: schema: type: string - enum: [Connection timed out] + enum: ["Connection timed out"] Unauthorized: schema: type: string - enum: [Unauthorized credentials] + enum: ["Unauthorized credentials"] NotConnected: schema: type: string - enum: [Not connected to Walkoff] + enum: ["Not connected to Walkoff"] UnknownResponse: schema: type: string @@ -458,6 +467,7 @@ actions: description: Timeout on the request (in seconds) type: number default: 2.0 + default_return: Success returns: Success: schema: @@ -465,15 +475,15 @@ actions: TimedOut: schema: type: string - enum: [Connection timed out] + enum: ["Connection timed out"] Unauthorized: schema: type: string - enum: [Unauthorized credentials] + enum: ["Unauthorized credentials"] NotConnected: schema: type: string - enum: [Not connected to Walkoff] + enum: ["Not connected to Walkoff"] UnknownResponse: schema: type: string @@ -501,6 +511,7 @@ actions: description: Timeout on the request (in seconds) type: number default: 2.0 + default_return: Success returns: Success: schema: @@ -526,15 +537,15 @@ actions: TimedOut: schema: type: string - enum: [Connection timed out] + enum: ["Connection timed out"] Unauthorized: schema: type: string - enum: [Unauthorized credentials] + enum: ["Unauthorized credentials"] NotConnected: schema: type: string - enum: [Not connected to Walkoff] + enum: ["Not connected to Walkoff"] UnknownResponse: schema: type: string @@ -547,6 +558,7 @@ actions: description: Timeout on the request (in seconds) type: number default: 2.0 + default_return: Success returns: Success: schema: @@ -607,15 +619,15 @@ actions: TimedOut: schema: type: string - enum: [Connection timed out] + enum: ["Connection timed out"] Unauthorized: schema: type: string - enum: [Unauthorized credentials] + enum: ["Unauthorized credentials"] NotConnected: schema: type: string - enum: [Not connected to Walkoff] + enum: ["Not connected to Walkoff"] UnknownResponse: schema: type: string @@ -640,6 +652,7 @@ actions: description: time to wait in between subsequent requests (in seconds) type: number default: 0.1 + default_return: Success returns: Success: schema: @@ -695,15 +708,15 @@ actions: TimedOut: schema: type: string - enum: [Connection timed out] + enum: ["Connection timed out"] Unauthorized: schema: type: string - enum: [Unauthorized credentials] + enum: ["Unauthorized credentials"] NotConnected: schema: type: string - enum: [Not connected to Walkoff] + enum: ["Not connected to Walkoff"] UnknownResponse: schema: type: string diff --git a/appveyor.yml b/appveyor.yml index 7506b19b0..37594a8f5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 0.6.2.{build} +version: 0.6.5.{build} branches: only: diff --git a/walkoff/__init__.py b/walkoff/__init__.py index a68d2bd0a..e2f45ae21 100644 --- a/walkoff/__init__.py +++ b/walkoff/__init__.py @@ -1 +1 @@ -__version__ = '0.6.3' +__version__ = '0.6.5' diff --git a/walkoff/client/helpers.js b/walkoff/client/helpers.js new file mode 100644 index 000000000..b5cb9ada0 --- /dev/null +++ b/walkoff/client/helpers.js @@ -0,0 +1,28 @@ +/** + * @author: @AngularClass + */ +var path = require('path'); + +const EVENT = process.env.npm_lifecycle_event || ''; + +// Helper functions +var ROOT = path.resolve(__dirname); + +function hasProcessFlag(flag) { + return process.argv.join('').indexOf(flag) > -1; +} + +function hasNpmFlag(flag) { + return EVENT.includes(flag); +} + +function isWebpackDevServer() { + return process.argv[1] && !!(/webpack-dev-server/.exec(process.argv[1])); +} + +var root = path.join.bind(path, ROOT); + +exports.hasProcessFlag = hasProcessFlag; +exports.hasNpmFlag = hasNpmFlag; +exports.isWebpackDevServer = isWebpackDevServer; +exports.root = root; \ No newline at end of file diff --git a/walkoff/client/karma.conf.js b/walkoff/client/karma.conf.js new file mode 100644 index 000000000..d8cd7541e --- /dev/null +++ b/walkoff/client/karma.conf.js @@ -0,0 +1,141 @@ +module.exports = (config) => { + // const coverage = config.singleRun ? ['coverage'] : []; + let plugins = [ + 'karma-jasmine', + 'karma-webpack', + 'karma-coverage', + 'karma-chrome-launcher', + 'karma-remap-istanbul', + 'karma-sourcemap-loader', + // 'karma-jasmine-html-reporter', + // 'karma-coverage-istanbul-reporter', + ]; + + let configuration = { + basePath: '', + frameworks: ['jasmine'], + + // plugins: [ + // 'karma-jasmine', + // 'karma-webpack', + // 'karma-coverage', + // 'karma-chrome-launcher', + // 'karma-remap-istanbul', + // // 'karma-jasmine-html-reporter', + // // 'karma-coverage-istanbul-reporter', + // ], + plugins, + + client: { + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + + coverageIstanbulReporter: { + reports: ['html', 'lcovonly'], + fixWebpackSourcePaths: true + }, + + files: [ + { pattern: 'spec-bundle.js', watched: false }, + // '**/*.spec.ts' + // './tests.entry.ts', + // { + // pattern: '**/*.map', + // served: true, + // included: false, + // watched: true, + // }, + ], + + proxies: { + '/assets/': '/base/src/assets/', + }, + + preprocessors: { + 'spec-bundle.js': ['webpack', 'sourcemap', 'coverage'] + // './src/tests.entry.ts': [ + // 'webpack', + // 'sourcemap', + // ], + // './src/**/!(*.test|tests.*).(ts|js)': [ + // 'sourcemap', + // ], + }, + + webpack: require('./webpack.test')({ env: 'test' }), + // { + // plugins, + // entry: './tests.entry.ts', + // devtool: 'inline-source-map', + // resolve: { + // extensions: ['.webpack.js', '.web.js', '.ts', '.js'], + // }, + // module: { + // rules: combinedLoaders().concat(config.singleRun ? [ loaders.istanbulInstrumenter ] : [ ]), + // }, + // stats: { colors: true, reasons: true }, + // }, + coverageReporter: { + type: 'in-memory' + }, + + remapCoverageReporter: { + 'text-summary': null, + json: './coverage/coverage.json', + html: './coverage/html' + }, + + // Webpack please don't spam the console when running in karma! + webpackMiddleware: { + // webpack-dev-middleware configuration + // i.e. + noInfo: true, + // and use stats to turn off verbose output + stats: { + // options i.e. + chunks: false + } + }, + + exclude: [ + 'node_modules/**/*.spec.ts' + ], + + reporters: ['mocha', 'coverage', 'remap-coverage'], + // reporters: ['spec'].concat(coverage), + + // coverageReporter: { + // reporters: [ + // { type: 'json' }, + // ], + // dir: './coverage/', + // subdir: (browser) => { + // return browser.toLowerCase().split(/[ /-]/)[0]; // returns 'chrome' + // }, + // }, + + reporters: ['progress'], + port: 9876, + browsers: ['Chrome'], // Alternatively: 'PhantomJS' + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + captureTimeout: 6000, + customLaunchers: { + ChromeTravisCi: { + base: 'Chrome', + flags: ['--no-sandbox'] + } + }, + }; + + if (process.env.TRAVIS) { + configuration.browsers = [ + 'ChromeTravisCi' + ]; + + configuration.singleRun = true; + } + + config.set(configuration); +}; \ No newline at end of file diff --git a/walkoff/client/main.ts b/walkoff/client/main.ts deleted file mode 100644 index 4b152909a..000000000 --- a/walkoff/client/main.ts +++ /dev/null @@ -1,14 +0,0 @@ -// import { enableProdMode } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - -import { MainModule } from './main.module'; - -if (sessionStorage.getItem('refresh_token')) { - //TODO: figure out a good way of handling this - // Enable production mode unless running locally - // if (!/localhost/.test(document.location.host)) { - // enableProdMode(); - // } - - platformBrowserDynamic().bootstrapModule(MainModule); -} else { location.href = '/login'; } diff --git a/walkoff/client/package.json b/walkoff/client/package.json index 6c4dc2f5f..ed8a26fd0 100644 --- a/walkoff/client/package.json +++ b/walkoff/client/package.json @@ -1,11 +1,31 @@ { - "name": "WALKOFF", - "description": "WALKOFF", + "name": "walkoff", + "description": "WALKOFF automation and orchestration framework.", + "keywords": [ + "automation", + "orchestration", + "framework", + "workflow", + "python", + "angular", + "angular4", + "webpack", + "typescript" + ], "license": "MIT", "scripts": { - "build": "gulp ts", - "watch": "gulp", - "test": "karma start" + "build:dev": "npm run clean:dist && npm run webpack -- --config webpack.dev.js --progress --profile", + "build:prod": "npm run clean:dist && npm run webpack -- --config webpack.prod.js --progress --profile --bail", + "build": "npm run build:prod", + "clean:dist": "npm run rimraf -- dist", + "clean:install": "npm set progress=false && npm install", + "clean": "npm cache clean --force && npm run rimraf -- node_modules coverage dist", + "rimraf": "rimraf", + "test": "karma start", + "watch:dev": "npm run build:dev -- --watch", + "watch:test": "npm run test -- --auto-watch --no-single-run", + "watch": "npm run watch:dev", + "webpack": "node --max_old_space_size=4096 node_modules/webpack/bin/webpack.js" }, "dependencies": { "@angular/animations": "~4.0.3", @@ -51,10 +71,10 @@ "ng2-toasty": "~4.0.3", "ngx-contextmenu": "~1.3.4", "plugin-typescript": "~7.0.6", - "rxjs": "~5.3.0", + "rxjs": "^5.5.6", "select2": "~4.0.3", "systemjs": "~0.20.12", - "zone.js": "~0.8.5" + "zone.js": "~0.8.19" }, "repository": {}, "engines": { @@ -68,23 +88,42 @@ "@types/jstree": "^3.3.35", "@types/lodash": "~4.14.68", "@types/select2": "~4.0.38", + "add-asset-html-webpack-plugin": "^2.1.2", + "angular2-template-loader": "^0.6.2", + "assets-webpack-plugin": "^3.5.1", + "awesome-typescript-loader": "^3.4.1", + "css-loader": "^0.28.8", + "extract-text-webpack-plugin": "^3.0.2", + "file-loader": "^1.1.6", "gulp": "~3.9.1", "gulp-concat": "~2.6.1", "gulp-sourcemaps": "~2.6.0", "gulp-typescript": "~3.2.0", "gulp-uglify": "~3.0.0", + "istanbul-instrumenter-loader": "^3.0.0", "jasmine": "~2.4.1", "jasmine-core": "^2.8.0", "karma": "^2.0.0", "karma-chrome-launcher": "^2.0.0", "karma-coverage": "^1.1.1", - "karma-coverage-istanbul-reporter": "^1.3.3", "karma-jasmine": "^1.1.1", - "karma-jasmine-html-reporter": "^0.2.2", + "karma-mocha-reporter": "^2.2.5", + "karma-remap-coverage": "^0.1.4", "karma-remap-istanbul": "^0.6.0", + "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^2.0.9", + "optimize-js-plugin": "^0.0.4", + "raw-loader": "^0.5.1", + "rimraf": "~2.6.2", + "sass-loader": "^6.0.6", + "source-map-loader": "^0.2.3", + "style-loader": "^0.19.1", + "to-string-loader": "^1.1.5", + "ts-loader": "^3.2.0", "tslint": "^5.8.0", "typescript": "~2.3.2", - "webpack": "^3.10.0" + "webpack": "^3.10.0", + "webpack-dll-bundles-plugin": "^1.0.0-beta.5", + "webpack-merge": "^4.1.1" } } diff --git a/walkoff/client/spec-bundle.js b/walkoff/client/spec-bundle.js new file mode 100644 index 000000000..16496eefd --- /dev/null +++ b/walkoff/client/spec-bundle.js @@ -0,0 +1,59 @@ +/** + * @author: @AngularClass + */ + +/* + * When testing with webpack and ES6, we have to do some extra + * things to get testing to work right. Because we are gonna write tests + * in ES6 too, we have to compile those as well. That's handled in + * karma.conf.js with the karma-webpack plugin. This is the entry + * file for webpack test. Just like webpack will create a bundle.js + * file for our client, when we run test, it will compile and bundle them + * all here! Crazy huh. So we need to do some setup + */ +Error.stackTraceLimit = Infinity; + +require('core-js/es6'); +require('core-js/es7/reflect'); + +require('zone.js/dist/zone'); +require('zone.js/dist/long-stack-trace-zone'); +require('zone.js/dist/proxy'); // since zone.js 0.6.15 +require('zone.js/dist/sync-test'); +require('zone.js/dist/jasmine-patch'); // put here since zone.js 0.6.14 +require('zone.js/dist/async-test'); +require('zone.js/dist/fake-async-test'); + +// RxJS +require('rxjs/Rx'); + +var testing = require('@angular/core/testing'); +var browser = require('@angular/platform-browser-dynamic/testing'); + +testing.TestBed.initTestEnvironment( + browser.BrowserDynamicTestingModule, + browser.platformBrowserDynamicTesting() +); + +/* + * Ok, this is kinda crazy. We can use the context method on + * require that webpack created in order to tell webpack + * what files we actually want to require or import. + * Below, context will be a function/object with file names as keys. + * Using that regex we are saying look in ../src then find + * any file that ends with spec.ts and get its path. By passing in true + * we say do this recursively + */ +var testContext = require.context('./src', true, /\.spec\.ts/); + +/* + * get all the files, for each file, call the context function + * that will require the file and load it up here. Context will + * loop and require those spec files here + */ +function requireAll(requireContext) { + return requireContext.keys().map(requireContext); +} + +// requires and returns all modules that match +var modules = requireAll(testContext); \ No newline at end of file diff --git a/walkoff/client/auth/auth.service.ts b/walkoff/client/src/auth/auth.service.ts similarity index 100% rename from walkoff/client/auth/auth.service.ts rename to walkoff/client/src/auth/auth.service.ts diff --git a/walkoff/client/src/cases/cases.component.spec.ts b/walkoff/client/src/cases/cases.component.spec.ts new file mode 100644 index 000000000..7383ae082 --- /dev/null +++ b/walkoff/client/src/cases/cases.component.spec.ts @@ -0,0 +1,209 @@ +import { HttpModule, Http, RequestOptions } from '@angular/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, TestBed, ComponentFixture, fakeAsync, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { } from 'jasmine'; + +import { CasesComponent } from './cases.component'; +import { CasesService } from './cases.service'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ToastyModule } from 'ng2-toasty'; +import { JwtHttp } from 'angular2-jwt-refresh'; +import { GetJwtHttp } from '../jwthttp.factory'; +import { NgxDatatableModule } from '@swimlane/ngx-datatable'; + +import { Case } from '../models/case'; +import { AvailableSubscription } from '../models/availableSubscription'; +import { Playbook } from '../models/playbook/playbook'; + +describe('CasesComponent', () => { + let comp: CasesComponent; + let fixture: ComponentFixture; + let service: CasesService; + + const testCases: Case[] = [ + { + id: 1, + name: 'case 1', + note: 'something', + subscriptions: [ + { + uid: '12345', + events: ['some', 'events', 'go', 'here'], + }, + ], + }, + ]; + + const testAvailableSubscriptions: AvailableSubscription[] = [ + { + events: [ + 'Job Added', + 'Job Error', + 'Job Executed', + 'Job Removed', + 'Scheduler Paused', + 'Scheduler Resumed', + 'Scheduler Shutdown', + 'Scheduler Start', + ], + type: 'controller', + }, + { + events: [], + type: 'playbook', + }, + { + events: [ + 'App Instance Created', + 'Workflow Arguments Invalid', + 'Workflow Arguments Validated', + 'Workflow Execution Start', + 'Workflow Paused', + 'Workflow Resumed', + 'Workflow Shutdown', + ], + type: 'workflow', + }, + { + events: [ + 'Action Execution Error', + 'Action Execution Success', + 'Action Started', + 'Arguments Invalid', + 'Trigger Action Awaiting Data', + 'Trigger Action Not Taken', + 'Trigger Action Taken', + ], + type: 'action', + }, + { + events: [ + 'Branch Not Taken', + 'Branch Taken', + ], + type: 'branch', + }, + { + events: [ + 'Condition Error', + 'Condition Success', + ], + type: 'condition', + }, + { + events: [ + 'Transform Error', + 'Transform Success', + ], + type: 'transform', + }, + ]; + + const testPlaybooks: Playbook[] = [ + { + uid: 'pb-12345', + name: 'test playbook', + workflows: [ + { + uid: 'wf-12345', + name: 'TestWorkflow', + actions: [ + { + uid: 'ac-12345', + name: 'test action', + position: { x: 0, y: 0 }, + app_name: 'TestApp', + action_name: 'TestActon', + arguments: [ + { + name: 'test', + value: 'value', + }, + ], + triggers: [], + }, + ], + branches: [], + start: 'ac-12345', + }, + ], + }, + ]; + + /** + * async beforeEach + */ + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + HttpModule, + NgbModule.forRoot(), + ToastyModule.forRoot(), + NgxDatatableModule, + // FormsModule, + // ReactiveFormsModule, + ], + declarations: [CasesComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [CasesService, { + provide: JwtHttp, + useFactory: GetJwtHttp, + deps: [Http, RequestOptions], + }], + }) + .compileComponents(); + })); + + /** + * Synchronous beforeEach + */ + beforeEach(() => { + fixture = TestBed.createComponent(CasesComponent); + comp = fixture.componentInstance; + service = fixture.debugElement.injector.get(CasesService); + + spyOn(window, 'confirm').and.returnValue(true); + spyOn(service, 'getCases').and.returnValue(Promise.resolve(testCases)); + // spyOn(service, 'getEventsForCase').and.returnValue(Promise.resolve(testCaseEvents)); + spyOn(service, 'getAvailableSubscriptions').and.returnValue(Promise.resolve(testAvailableSubscriptions)); + spyOn(service, 'getPlaybooks').and.returnValue(Promise.resolve(testPlaybooks)); + spyOn(service, 'deleteCase').and.returnValue(Promise.resolve()); + }); + + it('should properly display cases', fakeAsync(() => { + fixture.detectChanges(); + expect(comp.cases).toBeTruthy(); + expect(comp.cases.length).toBe(0); + tick(); + fixture.detectChanges(); + expect(comp.cases.length).toBe(testCases.length); + const els = fixture.debugElement.queryAll(By.css('.casesTable .datatable-body-row')); + expect(els.length).toBe(testCases.length); + })); + + it('should properly remove case from the display on delete', fakeAsync(() => { + fixture.detectChanges(); + tick(); + fixture.detectChanges(); + const originalCount = testCases.length; + expect(comp.cases.length).toBe(originalCount); + let els = fixture.debugElement.queryAll(By.css('.casesTable .datatable-body-row')); + expect(els.length).toBe(originalCount); + + comp.deleteCase(comp.cases[0]); + tick(); + fixture.detectChanges(); + expect(comp.cases.length).toBe(originalCount - 1); + els = fixture.debugElement.queryAll(By.css('.casesTable .datatable-body-row')); + expect(els.length).toBe(originalCount - 1); + })); + + it('should properly make the subscription tree from playbooks', fakeAsync(() => { + fixture.detectChanges(); + expect(comp.subscriptionTree).toEqual({}); + tick(); + fixture.detectChanges(); + expect(comp.subscriptionTree).toEqual(comp.convertPlaybooksToSubscriptionTree(testPlaybooks)); + })); +}); diff --git a/walkoff/client/cases/cases.component.ts b/walkoff/client/src/cases/cases.component.ts similarity index 90% rename from walkoff/client/cases/cases.component.ts rename to walkoff/client/src/cases/cases.component.ts index 8b72b9fb2..2a6bba1bc 100644 --- a/walkoff/client/cases/cases.component.ts +++ b/walkoff/client/src/cases/cases.component.ts @@ -1,4 +1,4 @@ -import { Component, ViewEncapsulation } from '@angular/core'; +import { Component, ViewEncapsulation, OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; import * as _ from 'lodash'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; @@ -14,9 +14,7 @@ import { Case } from '../models/case'; import { CaseEvent } from '../models/caseEvent'; import { AvailableSubscription } from '../models/availableSubscription'; import { Playbook } from '../models/playbook/playbook'; -import { Workflow } from '../models/playbook/workflow'; -import { Action } from '../models/playbook/action'; -import { Branch } from '../models/playbook/branch'; +import { GenericObject } from '../models/genericObject'; /** * Types as the backend calls them for adding a new CaseEvent. @@ -29,14 +27,14 @@ const childrenTypes = ['workflows', 'actions', 'branches', 'conditions', 'transf @Component({ selector: 'cases-component', - templateUrl: 'client/cases/cases.html', + templateUrl: './cases.html', encapsulation: ViewEncapsulation.None, styleUrls: [ - 'client/cases/cases.css', + './cases.css', ], providers: [CasesService], }) -export class CasesComponent { +export class CasesComponent implements OnInit { cases: Case[] = []; availableCases: Select2OptionData[] = []; availableSubscriptions: AvailableSubscription[] = []; @@ -46,11 +44,14 @@ export class CasesComponent { displayCaseEvents: CaseEvent[] = []; eventFilterQuery: FormControl = new FormControl(); caseFilterQuery: FormControl = new FormControl(); - subscriptionTree: any; + subscriptionTree: GenericObject = {}; constructor( private casesService: CasesService, private modalService: NgbModal, private toastyService: ToastyService, private toastyConfig: ToastyConfig) { + } + + ngOnInit(): void { this.toastyConfig.theme = 'bootstrap'; this.caseSelectConfig = { @@ -169,25 +170,22 @@ export class CasesComponent { .catch(e => this.toastyService.error(`Error retrieving subscription tree: ${e.message}`)); } - convertPlaybooksToSubscriptionTree(playbooks: any[]): any { + convertPlaybooksToSubscriptionTree(playbooks: Playbook[]): GenericObject { const self = this; //Top level controller data const tree = { name: 'Controller', uid: 'controller', type: 'controller', children: [] as object[] }; - // Remap the branches to be under actions as they used to be - playbooks.forEach((p: Playbook) => { - p.workflows.forEach((w: Workflow) => { - w.actions.forEach((s: Action) => { - (s as any).branches = []; + playbooks.forEach(p => { + p.workflows.forEach(w => { + w.actions.forEach(a => { + (a as any).branches = []; }); - w.branches.forEach((ns: Branch) => { - const matchingAction = w.actions.find(s => s.uid === ns.destination_uid); - if (matchingAction) { (ns as any).name = matchingAction.name; } - (w.actions.find(s => s.uid === ns.source_uid) as any).branches.push(ns); + w.branches.forEach(b => { + const matchingAction = w.actions.find(s => s.uid === b.destination_uid); + if (matchingAction) { (b as any).name = matchingAction.name; } + (w.actions.find(s => s.uid === b.source_uid) as any).branches.push(b); }); - - delete w.branches; }); }); diff --git a/walkoff/client/cases/cases.css b/walkoff/client/src/cases/cases.css similarity index 100% rename from walkoff/client/cases/cases.css rename to walkoff/client/src/cases/cases.css diff --git a/walkoff/client/cases/cases.html b/walkoff/client/src/cases/cases.html similarity index 93% rename from walkoff/client/cases/cases.html rename to walkoff/client/src/cases/cases.html index bdb8120a0..92f289d5f 100644 --- a/walkoff/client/cases/cases.html +++ b/walkoff/client/src/cases/cases.html @@ -24,7 +24,7 @@

Events

-