diff --git a/' b/' new file mode 100644 index 00000000000..2e342921a63 --- /dev/null +++ b/' @@ -0,0 +1,200 @@ + +/** Created by: Alex Wendland (me@alexwendland.com), 2014-08-06 + * + * angular-json-tree + * + * Directive for creating a tree-view out of a JS Object. Only loads + * sub-nodes on demand in order to improve performance of rendering large + * objects. + * + * Attributes: + * - object (Object, 2-way): JS object to build the tree from + * - start-expanded (Boolean, 1-way, ?=true): should the tree default to expanded + * + * Usage: + * // In the controller + * scope.someObject = { + * test: 'hello', + * array: [1,1,2,3,5,8] + * }; + * // In the html + * + * + * Dependencies: + * - utils (json-tree.js) + * - ajsRecursiveDirectiveHelper (json-tree.js) + * + * Test: json-tree-test.js + */ + +import angular from 'angular'; +import coreModule from 'app/core/core_module'; + +var utils = { + /* See link for possible type values to check against. + * http://stackoverflow.com/questions/4622952/json-object-containing-array + * + * Value Class Type + * ------------------------------------- + * "foo" String string + * new String("foo") String object + * 1.2 Number number + * new Number(1.2) Number object + * true Boolean boolean + * new Boolean(true) Boolean object + * new Date() Date object + * new Error() Error object + * [1,2,3] Array object + * new Array(1, 2, 3) Array object + * new Function("") Function function + * /abc/g RegExp object (function in Nitro/V8) + * new RegExp("meow") RegExp object (function in Nitro/V8) + * {} Object object + * new Object() Object object + */ + is: function is(obj, clazz) { + return Object.prototype.toString.call(obj).slice(8, -1) === clazz; + }, + + // See above for possible values + whatClass: function whatClass(obj) { + return Object.prototype.toString.call(obj).slice(8, -1); + }, + + // Iterate over an objects keyset + forKeys: function forKeys(obj, f) { + for (var key in obj) { + if (obj.hasOwnProperty(key) && typeof obj[key] !== 'function') { + if (f(key, obj[key])) { + break; + } + } + } + } +}; + +coreModule.directive('jsonTree', [function jsonTreeDirective() { + return { + restrict: 'E', + scope: { + object: '=', + startExpanded: '=', + rootName: '@', + }, + template: '' + }; +}]); + +coreModule.directive('jsonNode', ['ajsRecursiveDirectiveHelper', function jsonNodeDirective(ajsRecursiveDirectiveHelper) { + return { + restrict: 'E', + scope: { + key: '=', + value: '=', + startExpanded: '=' + }, + compile: function jsonNodeDirectiveCompile(elem) { + return ajsRecursiveDirectiveHelper.compile(elem, this); + }, + template: ' {{key}}' + + ' {{value}}' + + ' ' + + ' {preview}}' + + ' ', + pre: function jsonNodeDirectiveLink(scope, elem, attrs) { + // Set value's type as Class for CSS styling + elem.addClass(utils.whatClass(scope.value).toLowerCase()); + // If the value is an Array or Object, use expandable view type + if (utils.is(scope.value, 'Object') || utils.is(scope.value, 'Array')) { + scope.isExpandable = true; + // Add expandable class for CSS usage + elem.addClass('expandable'); + // Setup preview text + var isArray = utils.is(scope.value, 'Array'); + scope.preview = isArray ? '[ ' : '{ '; + utils.forKeys(scope.value, function jsonNodeDirectiveLinkForKeys(key, value) { + if (isArray) { + scope.preview += value + ', '; + } else { + scope.preview += key + ': ' + value + ', '; + } + }); + scope.preview = scope.preview.substring(0, scope.preview.length - (scope.preview.length > 2 ? 2 : 0)) + (isArray ? ' ]' : ' }'); + // If directive initially has isExpanded set, also set shouldRender to true + if (scope.startExpanded) { + scope.shouldRender = true; + elem.addClass('expanded'); + } + // Setup isExpanded state handling + scope.isExpanded = scope.startExpanded ? scope.startExpanded() : false; + scope.toggleExpanded = function jsonNodeDirectiveToggleExpanded() { + scope.isExpanded = !scope.isExpanded; + if (scope.isExpanded) { + elem.addClass('expanded'); + } else { + elem.removeClass('expanded'); + } + // For delaying subnode render until requested + scope.shouldRender = true; + }; + } else { + scope.isExpandable = false; + // Add expandable class for CSS usage + elem.addClass('not-expandable'); + } + } + }; +}]); + +/** Added by: Alex Wendland (me@alexwendland.com), 2014-08-09 + * Source: http://stackoverflow.com/questions/14430655/recursion-in-angular-directives + * + * Used to allow for recursion within directives + */ +coreModule.factory('ajsRecursiveDirectiveHelper', ['$compile', function RecursiveDirectiveHelper($compile) { + return { + /** + * Manually compiles the element, fixing the recursion loop. + * @param element + * @param [link] A post-link function, or an object with function(s) registered via pre and post properties. + * @returns An object containing the linking functions. + */ + compile: function RecursiveDirectiveHelperCompile(element, link) { + // Normalize the link parameter + if (angular.isFunction(link)) { + link = { + post: link + }; + } + + // Break the recursion loop by removing the contents + var contents = element.contents().remove(); + var compiledContents; + return { + pre: (link && link.pre) ? link.pre : null, + /** + * Compiles and re-adds the contents + */ + post: function RecursiveDirectiveHelperCompilePost(scope, element) { + // Compile the contents + if (!compiledContents) { + compiledContents = $compile(contents); + } + // Re-add the compiled contents to the element + compiledContents(scope, function (clone) { + element.append(clone); + }); + + // Call the post-linking function, if any + if (link && link.post) { + link.post.apply(null, arguments); + } + } + }; + } + }; +}]); diff --git a/pkg/api/dtos/alerting.go b/pkg/api/dtos/alerting.go index 579813e3c0e..2276d87e545 100644 --- a/pkg/api/dtos/alerting.go +++ b/pkg/api/dtos/alerting.go @@ -42,8 +42,8 @@ type AlertTestCommand struct { type AlertTestResult struct { Triggered bool `json:"triggerd"` Timing string `json:"timing"` - Error string `json:"error"` - Logs []*AlertTestResultLog `json:"logs"` + Error string `json:"error,omitempty"` + Logs []*AlertTestResultLog `json:"logs,omitempty"` } type AlertTestResultLog struct { diff --git a/pkg/tsdb/models.go b/pkg/tsdb/models.go index 29e0ff2cd32..2954c630b68 100644 --- a/pkg/tsdb/models.go +++ b/pkg/tsdb/models.go @@ -46,8 +46,8 @@ type QueryResult struct { } type TimeSeries struct { - Name string - Points [][2]float64 + Name string `json:"name"` + Points [][2]float64 `json:"points"` } type TimeSeriesSlice []*TimeSeries diff --git a/public/app/core/components/jsontree/jsontree.ts b/public/app/core/components/jsontree/jsontree.ts new file mode 100644 index 00000000000..14f6012ce12 --- /dev/null +++ b/public/app/core/components/jsontree/jsontree.ts @@ -0,0 +1,200 @@ + +/** Created by: Alex Wendland (me@alexwendland.com), 2014-08-06 + * + * angular-json-tree + * + * Directive for creating a tree-view out of a JS Object. Only loads + * sub-nodes on demand in order to improve performance of rendering large + * objects. + * + * Attributes: + * - object (Object, 2-way): JS object to build the tree from + * - start-expanded (Boolean, 1-way, ?=true): should the tree default to expanded + * + * Usage: + * // In the controller + * scope.someObject = { + * test: 'hello', + * array: [1,1,2,3,5,8] + * }; + * // In the html + * + * + * Dependencies: + * - utils (json-tree.js) + * - ajsRecursiveDirectiveHelper (json-tree.js) + * + * Test: json-tree-test.js + */ + +import angular from 'angular'; +import coreModule from 'app/core/core_module'; + +var utils = { + /* See link for possible type values to check against. + * http://stackoverflow.com/questions/4622952/json-object-containing-array + * + * Value Class Type + * ------------------------------------- + * "foo" String string + * new String("foo") String object + * 1.2 Number number + * new Number(1.2) Number object + * true Boolean boolean + * new Boolean(true) Boolean object + * new Date() Date object + * new Error() Error object + * [1,2,3] Array object + * new Array(1, 2, 3) Array object + * new Function("") Function function + * /abc/g RegExp object (function in Nitro/V8) + * new RegExp("meow") RegExp object (function in Nitro/V8) + * {} Object object + * new Object() Object object + */ + is: function is(obj, clazz) { + return Object.prototype.toString.call(obj).slice(8, -1) === clazz; + }, + + // See above for possible values + whatClass: function whatClass(obj) { + return Object.prototype.toString.call(obj).slice(8, -1); + }, + + // Iterate over an objects keyset + forKeys: function forKeys(obj, f) { + for (var key in obj) { + if (obj.hasOwnProperty(key) && typeof obj[key] !== 'function') { + if (f(key, obj[key])) { + break; + } + } + } + } +}; + +coreModule.directive('jsonTree', [function jsonTreeDirective() { + return { + restrict: 'E', + scope: { + object: '=', + startExpanded: '@', + rootName: '@', + }, + template: '' + }; +}]); + +coreModule.directive('jsonNode', ['ajsRecursiveDirectiveHelper', function jsonNodeDirective(ajsRecursiveDirectiveHelper) { + return { + restrict: 'E', + scope: { + key: '=', + value: '=', + startExpanded: '@' + }, + compile: function jsonNodeDirectiveCompile(elem) { + return ajsRecursiveDirectiveHelper.compile(elem, this); + }, + template: ' {{key}}' + + ' {{value}}' + + ' ' + + ' {{preview}}' + + ' ', + pre: function jsonNodeDirectiveLink(scope, elem, attrs) { + // Set value's type as Class for CSS styling + elem.addClass(utils.whatClass(scope.value).toLowerCase()); + // If the value is an Array or Object, use expandable view type + if (utils.is(scope.value, 'Object') || utils.is(scope.value, 'Array')) { + scope.isExpandable = true; + // Add expandable class for CSS usage + elem.addClass('expandable'); + // Setup preview text + var isArray = utils.is(scope.value, 'Array'); + scope.preview = isArray ? '[ ' : '{ '; + utils.forKeys(scope.value, function jsonNodeDirectiveLinkForKeys(key, value) { + if (isArray) { + scope.preview += value + ', '; + } else { + scope.preview += key + ': ' + value + ', '; + } + }); + scope.preview = scope.preview.substring(0, scope.preview.length - (scope.preview.length > 2 ? 2 : 0)) + (isArray ? ' ]' : ' }'); + // If directive initially has isExpanded set, also set shouldRender to true + if (scope.startExpanded) { + scope.shouldRender = true; + elem.addClass('expanded'); + } + // Setup isExpanded state handling + scope.isExpanded = scope.startExpanded; + scope.toggleExpanded = function jsonNodeDirectiveToggleExpanded() { + scope.isExpanded = !scope.isExpanded; + if (scope.isExpanded) { + elem.addClass('expanded'); + } else { + elem.removeClass('expanded'); + } + // For delaying subnode render until requested + scope.shouldRender = true; + }; + } else { + scope.isExpandable = false; + // Add expandable class for CSS usage + elem.addClass('not-expandable'); + } + } + }; +}]); + +/** Added by: Alex Wendland (me@alexwendland.com), 2014-08-09 + * Source: http://stackoverflow.com/questions/14430655/recursion-in-angular-directives + * + * Used to allow for recursion within directives + */ +coreModule.factory('ajsRecursiveDirectiveHelper', ['$compile', function RecursiveDirectiveHelper($compile) { + return { + /** + * Manually compiles the element, fixing the recursion loop. + * @param element + * @param [link] A post-link function, or an object with function(s) registered via pre and post properties. + * @returns An object containing the linking functions. + */ + compile: function RecursiveDirectiveHelperCompile(element, link) { + // Normalize the link parameter + if (angular.isFunction(link)) { + link = { + post: link + }; + } + + // Break the recursion loop by removing the contents + var contents = element.contents().remove(); + var compiledContents; + return { + pre: (link && link.pre) ? link.pre : null, + /** + * Compiles and re-adds the contents + */ + post: function RecursiveDirectiveHelperCompilePost(scope, element) { + // Compile the contents + if (!compiledContents) { + compiledContents = $compile(contents); + } + // Re-add the compiled contents to the element + compiledContents(scope, function (clone) { + element.append(clone); + }); + + // Call the post-linking function, if any + if (link && link.post) { + link.post.apply(null, arguments); + } + } + }; + } + }; +}]); diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 4dae139b66d..1174e267f4e 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -19,6 +19,7 @@ import "./directives/rebuild_on_change"; import "./directives/give_focus"; import './jquery_extended'; import './partials'; +import './components/jsontree/jsontree'; import {grafanaAppDirective} from './components/grafana_app'; import {sideMenuDirective} from './components/sidemenu/sidemenu'; diff --git a/public/app/plugins/panel/graph/alert_tab_ctrl.ts b/public/app/plugins/panel/graph/alert_tab_ctrl.ts index 95b5cd267f8..559e9c6b78e 100644 --- a/public/app/plugins/panel/graph/alert_tab_ctrl.ts +++ b/public/app/plugins/panel/graph/alert_tab_ctrl.ts @@ -150,7 +150,7 @@ export class AlertTabCtrl { }; return this.backendSrv.post('/api/alerts/test', payload).then(res => { - this.testResult = angular.toJson(res, true); + this.testResult = res; this.testing = false; }); } diff --git a/public/app/plugins/panel/graph/partials/tab_alerting.html b/public/app/plugins/panel/graph/partials/tab_alerting.html index f42b0021872..c5b85f5de59 100644 --- a/public/app/plugins/panel/graph/partials/tab_alerting.html +++ b/public/app/plugins/panel/graph/partials/tab_alerting.html @@ -132,9 +132,7 @@
-
-{{ctrl.testResult}}
-  
+
diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index 194d7a5487c..77a88efcf7a 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -71,6 +71,7 @@ @import "components/query_editor"; @import "components/tabbed_view"; @import "components/query_part"; +@import "components/jsontree"; // PAGES @import "pages/login"; diff --git a/public/sass/components/_jsontree.scss b/public/sass/components/_jsontree.scss new file mode 100644 index 00000000000..668382180f7 --- /dev/null +++ b/public/sass/components/_jsontree.scss @@ -0,0 +1,61 @@ +/* Structure */ +json-tree { + .json-tree-key { + vertical-align: middle; + } + .expandable { + position: relative; + &::before { + pointer-events: none; + } + &::before, & > .key { + cursor: pointer; + } + } + .json-tree-branch-preview { + display: inline-block; + vertical-align: middle; + } +} + +/* Looks */ +json-tree { + ul { + padding-left: $spacer; + } + li, ul { + list-style: none; + } + li { + line-height: 1.3rem; + } + .json-tree-key { + color: $variable; + padding: 5px 10px 5px 15px; + &::after { + content: ':'; + } + } + json-node.expandable { + &::before { + content: '\25b6'; + position: absolute; + left: 0px; + font-size: 10px; + transition: transform .1s ease; + } + &.expanded::before { + transform: rotate(90deg); + } + } + .json-tree-leaf-value, .json-tree-branch-preview { + word-break: break-all; + } + .json-tree-branch-preview { + overflow: hidden; + font-style: italic; + max-width: 40%; + height: 1.5em; + opacity: .7; + } +}