diff --git a/app/js/controllers/console.js b/app/js/controllers/console.js new file mode 100644 index 0000000..71370cb --- /dev/null +++ b/app/js/controllers/console.js @@ -0,0 +1,20 @@ +'use strict'; + +var controllersModule = require('./_index'); + +var codemirror = require('codemirror'); + +/** + * @ngInject + */ +function ConsoleController($scope, $location, $stateParams, datasetService) { + var vm = this; + vm.artifactName = $stateParams.artifactName; + vm.show = $location.search().show; + + datasetService.artifact(vm.artifactName, 'console').then(function(response) { + vm.data = response.data; + }); +} + +controllersModule.controller('ConsoleController', ConsoleController); diff --git a/app/js/directives/codemirror-console.js b/app/js/directives/codemirror-console.js new file mode 100644 index 0000000..a46fdc4 --- /dev/null +++ b/app/js/directives/codemirror-console.js @@ -0,0 +1,130 @@ +'use strict'; + +var CodeMirror = require('codemirror'); +require('codemirror/addon/mode/simple'); +require('codemirror/addon/fold/foldcode'); +require('codemirror/addon/fold/foldgutter'); + +var directivesModule = require('./_index.js'); + +CodeMirror.defineSimpleMode('console', { + start: [ + { + token: 'comment', + regex: /[\d\-]+ [\d\:\.]+ \|/, + sol: true + }, { + token: 'keyword', + regex: /\[[a-z\-]+\] \$ .*/ + } + ] +}); + +/** + * @ngInject + */ +function codemirrorConsole($compile, $window, datasetService, summaryService) { + var instance = null; + var element = null; + var headers = new Map(); + + var rangeFinder = function(cm, pos) { + if (!headers.has(pos.line)) { + return null; + } + + var foundSelf = false; + var foldEnd = null; + headers.forEach(function(name, lineNo) { + if (!foundSelf && lineNo === pos.line) { + foundSelf = true; + return; + } + + if (foundSelf && foldEnd === null) { + foldEnd = lineNo - 1; + } + }); + + if (foldEnd === null) { + foldEnd = cm.lastLine(); + } + + return { + from: CodeMirror.Pos(pos.line, cm.getLine(pos.line).length), + to: CodeMirror.Pos(foldEnd, cm.getLine(foldEnd).length) + }; + }; + + var link = function(scope, el, attrs, ctrl) { + instance = CodeMirror(el[0], { + lineNumbers: true, + readOnly: true, + value: 'test test test', + mode: 'console', + theme: 'neat', + foldGutter: { + rangeFinder: rangeFinder + }, + gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"] + }); + + element = el.find('div'); + + var updateHeight = function() { + var rect = element[0].getBoundingClientRect(); + element[0].style.height = ($window.innerHeight - rect.top) + 'px'; + }; + + scope.$on('windowResize', updateHeight); + updateHeight(); + }; + + /** + * @ngInject + */ + var controller = function($scope) { + $scope.$watch('data', function(data) { + if (!data) { + return; + } + + var lines = []; + var currentLine = 0; + headers.clear(); + data.scripts.forEach(function(script) { + headers.set(currentLine, script.name); + + script.lines.forEach(function(line) { + lines.push(line.date + ' | ' + line.line); + currentLine++; + }); + }); + + instance.setValue(lines.join('\n')); + + var doc = instance.getDoc(); + headers.forEach(function(text, lineNumber) { + var element = angular.element('
' + text + '
'); + element.addClass('console-script-header'); + + doc.addLineWidget(lineNumber, element[0], { + above: true, + noHScroll: true + }); + }); + }); + }; + + return { + restrict: 'EA', + scope: { + 'data': '=', + 'show': '=' + }, + link: link, + controller: controller + }; +} + +directivesModule.directive('codemirrorConsole', codemirrorConsole); diff --git a/app/js/directives/console-summary.js b/app/js/directives/console-summary.js new file mode 100644 index 0000000..ae74750 --- /dev/null +++ b/app/js/directives/console-summary.js @@ -0,0 +1,31 @@ +'use strict'; + +var directivesModule = require('./_index.js'); + +/** + * @ngInject + */ +function consoleSummary() { + + /** + * @ngInject + */ + var controller = function($scope, $attrs, datasetService) { + $scope.$watch('artifactName', function(artifactName) { + datasetService.artifact(artifactName, 'console').then(function(response) { + $scope.console = response.data; + }); + }); + }; + + return { + restrict: 'EA', + scope: { + 'artifactName': '=' + }, + controller: controller, + templateUrl: 'directives/console-summary.html' + }; +} + +directivesModule.directive('consoleSummary', consoleSummary); diff --git a/app/js/filters/bootstrap-filters.js b/app/js/filters/bootstrap-filters.js index b72b3fe..5fcf18b 100644 --- a/app/js/filters/bootstrap-filters.js +++ b/app/js/filters/bootstrap-filters.js @@ -2,16 +2,22 @@ var filtersModule = require('./_index.js'); -var contextClass = function(test, type) { +var statusClass = function(status, type) { var clazz; - if (test.status === 'success') { - clazz = 'success'; - } else if (test.status === 'skip') { - clazz = 'info'; - } else if (test.status === 'fail') { - clazz = 'danger'; - } else { + if (!status) { clazz = 'default'; + } else { + status = status.toLowerCase(); + + if (status === 'success') { + clazz = 'success'; + } else if (status === 'skip') { + clazz = 'info'; + } else if (status === 'fail' || status === 'failure') { + clazz = 'danger'; + } else { + clazz = 'default'; + } } if (type) { @@ -21,4 +27,9 @@ var contextClass = function(test, type) { } }; +var contextClass = function(test, type) { + return statusClass(test.status, type); +}; + filtersModule.filter('contextClass', function() { return contextClass; }); +filtersModule.filter('statusClass', function() { return statusClass; }); diff --git a/app/js/on_config.js b/app/js/on_config.js index 28405a1..b2b42a2 100644 --- a/app/js/on_config.js +++ b/app/js/on_config.js @@ -29,6 +29,14 @@ function OnConfig($stateProvider, $locationProvider, $urlRouterProvider) { title: 'Test Details' }); + $stateProvider.state('console', { + url: '/{artifactName}/console?show', + controller: 'ConsoleController', + controllerAs: 'console', + templateUrl: 'console.html', + title: 'Console' + }); + $urlRouterProvider.otherwise('/'); } diff --git a/app/styles/_codemirror.scss b/app/styles/_codemirror.scss new file mode 100644 index 0000000..1cf66a9 --- /dev/null +++ b/app/styles/_codemirror.scss @@ -0,0 +1,338 @@ +/* BASICS */ + +.CodeMirror { + /* Set height, width, borders, and global font properties here */ + font-family: monospace; + height: 300px; + color: black; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers {} +.CodeMirror-linenumber { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: #999; + white-space: nowrap; +} + +.CodeMirror-guttermarker { color: black; } +.CodeMirror-guttermarker-subtle { color: #999; } + +/* CURSOR */ + +.CodeMirror-cursor { + border-left: 1px solid black; + border-right: none; + width: 0; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.cm-fat-cursor .CodeMirror-cursor { + width: auto; + border: 0; + background: #7e7; +} +.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} + +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; + background-color: #7e7; +} +@-moz-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@-webkit-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} + +/* Can style cursor different in overwrite (non-insert) mode */ +.CodeMirror-overwrite .CodeMirror-cursor {} + +.cm-tab { display: inline-block; text-decoration: inherit; } + +.CodeMirror-ruler { + border-left: 1px solid #ccc; + position: absolute; +} + +/* DEFAULT THEME */ + +.cm-s-default .cm-header {color: blue;} +.cm-s-default .cm-quote {color: #090;} +.cm-negative {color: #d44;} +.cm-positive {color: #292;} +.cm-header, .cm-strong {font-weight: bold;} +.cm-em {font-style: italic;} +.cm-link {text-decoration: underline;} +.cm-strikethrough {text-decoration: line-through;} + +.cm-s-default .cm-keyword {color: #708;} +.cm-s-default .cm-atom {color: #219;} +.cm-s-default .cm-number {color: #164;} +.cm-s-default .cm-def {color: #00f;} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator {} +.cm-s-default .cm-variable-2 {color: #05a;} +.cm-s-default .cm-variable-3 {color: #085;} +.cm-s-default .cm-comment {color: #a50;} +.cm-s-default .cm-string {color: #a11;} +.cm-s-default .cm-string-2 {color: #f50;} +.cm-s-default .cm-meta {color: #555;} +.cm-s-default .cm-qualifier {color: #555;} +.cm-s-default .cm-builtin {color: #30a;} +.cm-s-default .cm-bracket {color: #997;} +.cm-s-default .cm-tag {color: #170;} +.cm-s-default .cm-attribute {color: #00c;} +.cm-s-default .cm-hr {color: #999;} +.cm-s-default .cm-link {color: #00c;} + +.cm-s-default .cm-error {color: #f00;} +.cm-invalidchar {color: #f00;} + +.CodeMirror-composing { border-bottom: 2px solid; } + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } +.CodeMirror-activeline-background {background: #e8f2ff;} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + position: relative; + overflow: hidden; + background: white; +} + +.CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; +} +.CodeMirror-sizer { + position: relative; + border-right: 30px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actual scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} +.CodeMirror-vscrollbar { + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; left: 0; top: 0; + min-height: 100%; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + display: inline-block; + vertical-align: top; + margin-bottom: -30px; + /* Hack to make IE7 behave */ + *zoom:1; + *display:inline; +} +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + background: none !important; + border: none !important; +} +.CodeMirror-gutter-background { + position: absolute; + top: 0; bottom: 0; + z-index: 4; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; +} +.CodeMirror-gutter-wrapper { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; + -webkit-font-variant-ligatures: none; + font-variant-ligatures: none; +} +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + overflow: auto; +} + +.CodeMirror-widget {} + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} + +.CodeMirror-cursor { position: absolute; } +.CodeMirror-measure pre { position: static; } + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +div.CodeMirror-dragcursors { + visibility: visible; +} + +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } +.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } +.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } + +.cm-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* IE7 hack to prevent it from returning funny offsetTops on the spans */ +.CodeMirror span { *vertical-align: text-bottom; } + +/* Used to force a border model for a node */ +.cm-force-border { padding-right: .1px; } + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { content: ''; } + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { background: none; } diff --git a/app/styles/_foldgutter.scss b/app/styles/_foldgutter.scss new file mode 100644 index 0000000..ad19ae2 --- /dev/null +++ b/app/styles/_foldgutter.scss @@ -0,0 +1,20 @@ +.CodeMirror-foldmarker { + color: blue; + text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px; + font-family: arial; + line-height: .3; + cursor: pointer; +} +.CodeMirror-foldgutter { + width: .7em; +} +.CodeMirror-foldgutter-open, +.CodeMirror-foldgutter-folded { + cursor: pointer; +} +.CodeMirror-foldgutter-open:after { + content: "\25BE"; +} +.CodeMirror-foldgutter-folded:after { + content: "\25B8"; +} diff --git a/app/styles/_neat.scss b/app/styles/_neat.scss new file mode 100644 index 0000000..4267b1a --- /dev/null +++ b/app/styles/_neat.scss @@ -0,0 +1,12 @@ +.cm-s-neat span.cm-comment { color: #a86; } +.cm-s-neat span.cm-keyword { line-height: 1em; font-weight: bold; color: blue; } +.cm-s-neat span.cm-string { color: #a22; } +.cm-s-neat span.cm-builtin { line-height: 1em; font-weight: bold; color: #077; } +.cm-s-neat span.cm-special { line-height: 1em; font-weight: bold; color: #0aa; } +.cm-s-neat span.cm-variable { color: black; } +.cm-s-neat span.cm-number, .cm-s-neat span.cm-atom { color: #3a3; } +.cm-s-neat span.cm-meta { color: #555; } +.cm-s-neat span.cm-link { color: #3a3; } + +.cm-s-neat .CodeMirror-activeline-background { background: #e8f2ff; } +.cm-s-neat .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; } diff --git a/app/styles/directives/_codemirror-console.scss b/app/styles/directives/_codemirror-console.scss new file mode 100644 index 0000000..f74fb91 --- /dev/null +++ b/app/styles/directives/_codemirror-console.scss @@ -0,0 +1,9 @@ +codemirror-console { + font-size: 12px; + + .CodeMirror-linewidget .console-script-header { + font-weight: bold; + text-align: center; + background-color: lightgray; + } +} diff --git a/app/styles/main.scss b/app/styles/main.scss index 07e7d42..90cab6d 100644 --- a/app/styles/main.scss +++ b/app/styles/main.scss @@ -5,8 +5,13 @@ @import 'sb-admin-2'; @import 'nprogress'; +@import 'codemirror'; +@import 'foldgutter'; +@import 'neat'; + @import 'directives/_test-details-search.scss'; @import 'directives/_timeline-details.scss'; @import 'directives/_timeline-search.scss'; @import 'directives/_timeline-viewport.scss'; @import 'directives/_timeline-overview.scss'; +@import 'directives/_codemirror-console.scss'; diff --git a/app/views/console.html b/app/views/console.html new file mode 100644 index 0000000..66c69da --- /dev/null +++ b/app/views/console.html @@ -0,0 +1,16 @@ +
+
+

+ Console: {{ console.artifactName }} +

+
+
+ +
+
+
+ +
+
+
diff --git a/app/views/directives/console-summary.html b/app/views/directives/console-summary.html new file mode 100644 index 0000000..58b2408 --- /dev/null +++ b/app/views/directives/console-summary.html @@ -0,0 +1,45 @@ +
+
+

{{artifactName}}

+
+
+
+
+ + {{console.status}} + +
+ status +
+
+ + {{console.scripts.length}} of + {{console.scripts.length + console.remaining.length}} + + + {{console.scripts.length}} of + {{console.scripts.length + console.remaining.length}} + +
+ scripts completed +
+
+
+
+
+ + {{console.scripts[console.scripts.length - 2].name}} + +
+ last script run +
+
+
+ +
diff --git a/package.json b/package.json index ee306c5..f166ee9 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "browserify-ngannotate": "2.0.0", "bulk-require": "^0.2.1", "bulkify": "1.1.1", + "codemirror": "5.14.2", "d3": "^3.5.6", "del": "2.2.0", "eslint": "~1.10.3",