Commit f65878c2 by Torkel Ödegaard

ux: working on query troubleshooting

parent 78dbb4dc
......@@ -68,6 +68,7 @@
"grunt-jscs": "3.0.1",
"grunt-sass-lint": "^0.2.2",
"grunt-sync": "^0.6.2",
"json-formatter-js": "^2.2.0",
"karma-sinon": "^1.0.5",
"lodash": "^4.17.2",
"mousetrap": "^1.6.0",
......
// #<{(|
// * Escapes `"` charachters from string
// |)}>#
// function escapeString(str: string): string {
// return str.replace('"', '\"');
// }
//
// #<{(|
// * Determines if a value is an object
// |)}>#
// export function isObject(value: any): boolean {
// var type = typeof value;
// return !!value && (type === 'object');
// }
//
// #<{(|
// * Gets constructor name of an object.
// * From http://stackoverflow.com/a/332429
// *
// |)}>#
// export function getObjectName(object: Object): string {
// if (object === undefined) {
// return '';
// }
// if (object === null) {
// return 'Object';
// }
// if (typeof object === 'object' && !object.constructor) {
// return 'Object';
// }
//
// const funcNameRegex = /function ([^(]*)/;
// const results = (funcNameRegex).exec((object).constructor.toString());
// if (results && results.length > 1) {
// return results[1];
// } else {
// return '';
// }
// }
//
// #<{(|
// * Gets type of an object. Returns "null" for null objects
// |)}>#
// export function getType(object: Object): string {
// if (object === null) { return 'null'; }
// return typeof object;
// }
//
// #<{(|
// * Generates inline preview for a JavaScript object based on a value
// |)}>#
// export function getValuePreview (object: Object, value: string): string {
// var type = getType(object);
//
// if (type === 'null' || type === 'undefined') { return type; }
//
// if (type === 'string') {
// value = '"' + escapeString(value) + '"';
// }
// if (type === 'function'){
//
// // Remove content of the function
// return object.toString()
// .replace(/[\r\n]/g, '')
// .replace(/\{.*\}/, '') + '{…}';
// }
// return value;
// }
//
// #<{(|
// * Generates inline preview for a JavaScript object
// |)}>#
// export function getPreview(object: string): string {
// let value = '';
// if (isObject(object)) {
// value = getObjectName(object);
// if (Array.isArray(object)) {
// value += '[' + object.length + ']';
// }
// } else {
// value = getValuePreview(object, object);
// }
// return value;
// }
//
// #<{(|
// * Generates a prefixed CSS class name
// |)}>#
// export function cssClass(className: string): string {
// return `json-formatter-${className}`;
// }
//
// #<{(|
// * Creates a new DOM element wiht given type and class
// * TODO: move me to helpers
// |)}>#
// export function createElement(type: string, className?: string, content?: Element|string): Element {
// const el = document.createElement(type);
// if (className) {
// el.classList.add(cssClass(className));
// }
// if (content !== undefined) {
// if (content instanceof Node) {
// el.appendChild(content);
// } else {
// el.appendChild(document.createTextNode(String(content)));
// }
// }
// return el;
// }
// import {
// isObject,
// getObjectName,
// getType,
// getValuePreview,
// getPreview,
// cssClass,
// createElement
// } from './helpers';
//
// import './style.less';
//
// const DATE_STRING_REGEX = /(^\d{1,4}[\.|\\/|-]\d{1,2}[\.|\\/|-]\d{1,4})(\s*(?:0?[1-9]:[0-5]|1(?=[012])\d:[0-5])\d\s*[ap]m)?$/;
// const PARTIAL_DATE_REGEX = /\d{2}:\d{2}:\d{2} GMT-\d{4}/;
// const JSON_DATE_REGEX = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/;
//
// // When toggleing, don't animated removal or addition of more than a few items
// const MAX_ANIMATED_TOGGLE_ITEMS = 10;
//
// const requestAnimationFrame = window.requestAnimationFrame || function(cb: ()=>void) { cb(); return 0; };
//
// export interface JSONFormatterConfiguration {
// hoverPreviewEnabled?: boolean;
// hoverPreviewArrayCount?: number;
// hoverPreviewFieldCount?: number;
// animateOpen?: boolean;
// animateClose?: boolean;
// theme?: string;
// };
//
// const _defaultConfig: JSONFormatterConfiguration = {
// hoverPreviewEnabled: false,
// hoverPreviewArrayCount: 100,
// hoverPreviewFieldCount: 5,
// animateOpen: true,
// animateClose: true,
// theme: null
// };
//
//
// #<{(|*
// * @class JSONFormatter
// *
// * JSONFormatter allows you to render JSON objects in HTML with a
// * **collapsible** navigation.
// |)}>#
// export default class JSONFormatter {
//
// // Hold the open state after the toggler is used
// private _isOpen: boolean = null;
//
// // A reference to the element that we render to
// private element: Element;
//
// #<{(|*
// * @param {object} json The JSON object you want to render. It has to be an
// * object or array. Do NOT pass raw JSON string.
// *
// * @param {number} [open=1] his number indicates up to how many levels the
// * rendered tree should expand. Set it to `0` to make the whole tree collapsed
// * or set it to `Infinity` to expand the tree deeply
// *
// * @param {object} [config=defaultConfig] -
// * defaultConfig = {
// * hoverPreviewEnabled: false,
// * hoverPreviewArrayCount: 100,
// * hoverPreviewFieldCount: 5
// * }
// *
// * Available configurations:
// * #####Hover Preview
// * * `hoverPreviewEnabled`: enable preview on hover
// * * `hoverPreviewArrayCount`: number of array items to show in preview Any
// * array larger than this number will be shown as `Array[XXX]` where `XXX`
// * is length of the array.
// * * `hoverPreviewFieldCount`: number of object properties to show for object
// * preview. Any object with more properties that thin number will be
// * truncated.
// *
// * @param {string} [key=undefined] The key that this object in it's parent
// * context
// |)}>#
// constructor(public json: any, private open = 1, private config: JSONFormatterConfiguration = _defaultConfig, private key?: string) {
//
// // Setting default values for config object
// if (this.config.hoverPreviewEnabled === undefined) {
// this.config.hoverPreviewEnabled = _defaultConfig.hoverPreviewEnabled;
// }
// if (this.config.hoverPreviewArrayCount === undefined) {
// this.config.hoverPreviewArrayCount = _defaultConfig.hoverPreviewArrayCount;
// }
// if (this.config.hoverPreviewFieldCount === undefined) {
// this.config.hoverPreviewFieldCount = _defaultConfig.hoverPreviewFieldCount;
// }
// }
//
// #<{(|
// * is formatter open?
// |)}>#
// private get isOpen(): boolean {
// if (this._isOpen !== null) {
// return this._isOpen;
// } else {
// return this.open > 0;
// }
// }
//
// #<{(|
// * set open state (from toggler)
// |)}>#
// private set isOpen(value: boolean) {
// this._isOpen = value;
// }
//
// #<{(|
// * is this a date string?
// |)}>#
// private get isDate(): boolean {
// return (this.type === 'string') &&
// (DATE_STRING_REGEX.test(this.json) ||
// JSON_DATE_REGEX.test(this.json) ||
// PARTIAL_DATE_REGEX.test(this.json));
// }
//
// #<{(|
// * is this a URL string?
// |)}>#
// private get isUrl(): boolean {
// return this.type === 'string' && (this.json.indexOf('http') === 0);
// }
//
// #<{(|
// * is this an array?
// |)}>#
// private get isArray(): boolean {
// return Array.isArray(this.json);
// }
//
// #<{(|
// * is this an object?
// * Note: In this context arrays are object as well
// |)}>#
// private get isObject(): boolean {
// return isObject(this.json);
// }
//
// #<{(|
// * is this an empty object with no properties?
// |)}>#
// private get isEmptyObject(): boolean {
// return !this.keys.length && !this.isArray;
// }
//
// #<{(|
// * is this an empty object or array?
// |)}>#
// private get isEmpty(): boolean {
// return this.isEmptyObject || (this.keys && !this.keys.length && this.isArray);
// }
//
// #<{(|
// * did we recieve a key argument?
// * This means that the formatter was called as a sub formatter of a parent formatter
// |)}>#
// private get hasKey(): boolean {
// return typeof this.key !== 'undefined';
// }
//
// #<{(|
// * if this is an object, get constructor function name
// |)}>#
// private get constructorName(): string {
// return getObjectName(this.json);
// }
//
// #<{(|
// * get type of this value
// * Possible values: all JavaScript primitive types plus "array" and "null"
// |)}>#
// private get type(): string {
// return getType(this.json);
// }
//
// #<{(|
// * get object keys
// * If there is an empty key we pad it wit quotes to make it visible
// |)}>#
// private get keys(): string[] {
// if (this.isObject) {
// return Object.keys(this.json).map((key)=> key ? key : '""');
// } else {
// return [];
// }
// }
//
// #<{(|*
// * Toggles `isOpen` state
// *
// |)}>#
// toggleOpen() {
// this.isOpen = !this.isOpen;
//
// if (this.element) {
// if (this.isOpen) {
// this.appendChildren(this.config.animateOpen);
// } else{
// this.removeChildren(this.config.animateClose);
// }
// this.element.classList.toggle(cssClass('open'));
// }
// }
//
// #<{(|*
// * Open all children up to a certain depth.
// * Allows actions such as expand all/collapse all
// *
// |)}>#
// openAtDepth(depth = 1) {
// if (depth < 0) {
// return;
// }
//
// this.open = depth;
// this.isOpen = (depth !== 0);
//
// if (this.element) {
// this.removeChildren(false);
//
// if (depth === 0) {
// this.element.classList.remove(cssClass('open'));
// } else {
// this.appendChildren(this.config.animateOpen);
// this.element.classList.add(cssClass('open'));
// }
// }
// }
//
// #<{(|*
// * Generates inline preview
// *
// * @returns {string}
// |)}>#
// getInlinepreview() {
// if (this.isArray) {
//
// // if array length is greater then 100 it shows "Array[101]"
// if (this.json.length > this.config.hoverPreviewArrayCount) {
// return `Array[${this.json.length}]`;
// } else {
// return `[${this.json.map(getPreview).join(', ')}]`;
// }
// } else {
//
// const keys = this.keys;
//
// // the first five keys (like Chrome Developer Tool)
// const narrowKeys = keys.slice(0, this.config.hoverPreviewFieldCount);
//
// // json value schematic information
// const kvs = narrowKeys.map(key => `${key}:${getPreview(this.json[key])}`);
//
// // if keys count greater then 5 then show ellipsis
// const ellipsis = keys.length >= this.config.hoverPreviewFieldCount ? '…' : '';
//
// return `{${kvs.join(', ')}${ellipsis}}`;
// }
// }
//
//
// #<{(|*
// * Renders an HTML element and installs event listeners
// *
// * @returns {HTMLDivElement}
// |)}>#
// render(): HTMLDivElement {
//
// // construct the root element and assign it to this.element
// this.element = createElement('div', 'row');
//
// // construct the toggler link
// const togglerLink = createElement('a', 'toggler-link');
//
// // if this is an object we need a wrapper span (toggler)
// if (this.isObject) {
// togglerLink.appendChild(createElement('span', 'toggler'));
// }
//
// // if this is child of a parent formatter we need to append the key
// if (this.hasKey) {
// togglerLink.appendChild(createElement('span', 'key', `${this.key}:`));
// }
//
// // Value for objects and arrays
// if (this.isObject) {
//
// // construct the value holder element
// const value = createElement('span', 'value');
//
// // we need a wrapper span for objects
// const objectWrapperSpan = createElement('span');
//
// // get constructor name and append it to wrapper span
// var constructorName = createElement('span', 'constructor-name', this.constructorName);
// objectWrapperSpan.appendChild(constructorName);
//
// // if it's an array append the array specific elements like brackets and length
// if (this.isArray) {
// const arrayWrapperSpan = createElement('span');
// arrayWrapperSpan.appendChild(createElement('span', 'bracket', '['));
// arrayWrapperSpan.appendChild(createElement('span', 'number', (this.json.length)));
// arrayWrapperSpan.appendChild(createElement('span', 'bracket', ']'));
// objectWrapperSpan.appendChild(arrayWrapperSpan);
// }
//
// // append object wrapper span to toggler link
// value.appendChild(objectWrapperSpan);
// togglerLink.appendChild(value);
//
// // Primitive values
// } else {
//
// // make a value holder element
// const value = this.isUrl ? createElement('a') : createElement('span');
//
// // add type and other type related CSS classes
// value.classList.add(cssClass(this.type));
// if (this.isDate) {
// value.classList.add(cssClass('date'));
// }
// if (this.isUrl) {
// value.classList.add(cssClass('url'));
// value.setAttribute('href', this.json);
// }
//
// // Append value content to value element
// const valuePreview = getValuePreview(this.json, this.json);
// value.appendChild(document.createTextNode(valuePreview));
//
// // append the value element to toggler link
// togglerLink.appendChild(value);
// }
//
// // if hover preview is enabled, append the inline preview element
// if (this.isObject && this.config.hoverPreviewEnabled) {
// const preview = createElement('span', 'preview-text');
// preview.appendChild(document.createTextNode(this.getInlinepreview()));
// togglerLink.appendChild(preview);
// }
//
// // construct a children element
// const children = createElement('div', 'children');
//
// // set CSS classes for children
// if (this.isObject) {
// children.classList.add(cssClass('object'));
// }
// if (this.isArray) {
// children.classList.add(cssClass('array'));
// }
// if (this.isEmpty) {
// children.classList.add(cssClass('empty'));
// }
//
// // set CSS classes for root element
// if (this.config && this.config.theme) {
// this.element.classList.add(cssClass(this.config.theme));
// }
// if (this.isOpen) {
// this.element.classList.add(cssClass('open'));
// }
//
// // append toggler and children elements to root element
// this.element.appendChild(togglerLink);
// this.element.appendChild(children);
//
// // if formatter is set to be open call appendChildren
// if (this.isObject && this.isOpen) {
// this.appendChildren();
// }
//
// // add event listener for toggling
// if (this.isObject) {
// togglerLink.addEventListener('click', this.toggleOpen.bind(this));
// }
//
// return this.element as HTMLDivElement;
// }
//
// #<{(|*
// * Appends all the children to children element
// * Animated option is used when user triggers this via a click
// |)}>#
// appendChildren(animated = false) {
// const children = this.element.querySelector(`div.${cssClass('children')}`);
//
// if (!children || this.isEmpty) { return; }
//
// if (animated) {
// let index = 0;
// const addAChild = ()=> {
// const key = this.keys[index];
// const formatter = new JSONFormatter(this.json[key], this.open - 1, this.config, key);
// children.appendChild(formatter.render());
//
// index += 1;
//
// if (index < this.keys.length) {
// if (index > MAX_ANIMATED_TOGGLE_ITEMS) {
// addAChild();
// } else {
// requestAnimationFrame(addAChild);
// }
// }
// };
//
// requestAnimationFrame(addAChild);
//
// } else {
// this.keys.forEach(key => {
// const formatter = new JSONFormatter(this.json[key], this.open - 1, this.config, key);
// children.appendChild(formatter.render());
// });
// }
// }
//
// #<{(|*
// * Removes all the children from children element
// * Animated option is used when user triggers this via a click
// |)}>#
// removeChildren(animated = false) {
// const childrenElement = this.element.querySelector(`div.${cssClass('children')}`) as HTMLDivElement;
//
// if (animated) {
// let childrenRemoved = 0;
// const removeAChild = ()=> {
// if (childrenElement && childrenElement.children.length) {
// childrenElement.removeChild(childrenElement.children[0]);
// childrenRemoved += 1;
// if (childrenRemoved > MAX_ANIMATED_TOGGLE_ITEMS) {
// removeAChild();
// } else {
// requestAnimationFrame(removeAChild);
// }
// }
// };
// requestAnimationFrame(removeAChild);
// } else {
// if (childrenElement) {
// childrenElement.innerHTML = '';
// }
// }
// }
// }
///<reference path="../../headers/common.d.ts" />
import coreModule from 'app/core/core_module';
import JsonFormatter from 'json-formatter-js';
const template = `
<div class="response-viewer">
<div class="response-viewer-json"></div>
</div>
`;
export function responseViewer() {
return {
restrict: 'E',
template: template,
scope: {response: "="},
link: function(scope, elem) {
var jsonElem = elem.find('.response-viewer-json');
scope.$watch("response", newVal => {
if (!newVal) {
elem.empty();
return;
}
if (scope.response.headers) {
delete scope.response.headers;
}
if (scope.response.data) {
scope.response.response = scope.response.data;
delete scope.response.data;
}
if (scope.response.config) {
scope.response.request = scope.response.config;
delete scope.response.config;
delete scope.response.request.transformRequest;
delete scope.response.request.transformResponse;
delete scope.response.request.paramSerializer;
delete scope.response.request.jsonpCallbackParam;
delete scope.response.request.headers;
delete scope.response.request.requestId;
delete scope.response.request.inspect;
delete scope.response.request.retry;
delete scope.response.request.timeout;
}
const formatter = new JsonFormatter(scope.response, 2, {
theme: 'dark',
});
const html = formatter.render();
jsonElem.html(html);
});
}
};
}
coreModule.directive('responseViewer', responseViewer);
......@@ -45,7 +45,7 @@ import {assignModelProperties} from './utils/model_utils';
import {contextSrv} from './services/context_srv';
import {KeybindingSrv} from './services/keybindingSrv';
import {helpModal} from './components/help/help';
import {responseViewer} from './components/response_viewer';
export {
arrayJoin,
......@@ -69,4 +69,5 @@ export {
contextSrv,
KeybindingSrv,
helpModal,
responseViewer,
};
......@@ -8,16 +8,8 @@ var module = angular.module('grafana.directives');
var template = `
<div class="gf-form-group" ng-if="ctrl.lastError">
<div class="gf-form">
<pre class="gf-form-pre alert alert-error">{{ctrl.lastError}}</pre>
</div>
</div>
<div class="gf-form-group" ng-if="ctrl.showResponse">
<div class="gf-form">
<pre class="gf-form-pre alert alert-info">{{ctrl.lastResponse}}</pre>
</div>
<response-viewer response="ctrl.responseData" />
</div>
<div class="gf-form-group">
......@@ -49,9 +41,9 @@ var template = `
</div>
<div class="gf-form gf-form--offset-1">
<button class="btn btn-secondary gf-form-btn" ng-click="ctrl.toggleShowResponse()" ng-show="ctrl.lastResponse">
<i class="fa fa-info"></i>&nbsp;
Show Response
<button class="btn btn-inverse gf-form-btn" ng-click="ctrl.toggleShowResponse()" ng-show="ctrl.responseData">
<i class="fa fa-binoculars"></i>&nbsp;
Request & Response
</button>
</div>
......@@ -68,7 +60,7 @@ export class MetricsDsSelectorCtrl {
datasources: any[];
current: any;
lastResponse: any;
lastError: any;
responseData: any;
showResponse: boolean;
/** @ngInject */
......@@ -95,9 +87,8 @@ export class MetricsDsSelectorCtrl {
}
onRequestResponse(data) {
console.log(data);
this.lastResponse = JSON.stringify(data, null, 2);
this.lastError = null;
this.responseData = data;
this.showResponse = true;
}
toggleShowResponse() {
......@@ -105,8 +96,9 @@ export class MetricsDsSelectorCtrl {
}
onRequestError(err) {
console.log(err);
this.lastError = JSON.stringify(err, null, 2);
this.responseData = err;
this.responseData.isError = true;
this.showResponse = true;
}
getOptions(includeBuiltin) {
......@@ -122,8 +114,7 @@ export class MetricsDsSelectorCtrl {
if (ds) {
this.current = ds;
this.panelCtrl.setDatasource(ds);
this.lastError = null;
this.lastResponse = null;
this.responseData = null;
}
}
......
......@@ -72,3 +72,8 @@ declare module 'd3' {
var d3: any;
export default d3;
}
declare module 'json-formatter-js' {
var JSONFormatter: any;
export default JSONFormatter;
}
......@@ -32,7 +32,8 @@ System.config({
"jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow",
"jquery.flot.gauge": "vendor/flot/jquery.flot.gauge",
"d3": "vendor/d3/d3.js",
"jquery.flot.dashes": "vendor/flot/jquery.flot.dashes"
"jquery.flot.dashes": "vendor/flot/jquery.flot.dashes",
"json-formatter-js": "vendor/npm/json-formatter-js/dist/json-formatter"
},
packages: {
......
......@@ -75,6 +75,7 @@
@import "components/jsontree";
@import "components/edit_sidemenu.scss";
@import "components/row.scss";
@import "components/response_viewer.scss";
// PAGES
@import "pages/login";
......
.response-viewer {
background: $card-background;
box-shadow: $card-shadow;
padding: 1rem;
border-radius: 4px;
}
......@@ -40,7 +40,8 @@
"jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow",
"jquery.flot.gauge": "vendor/flot/jquery.flot.gauge",
"d3": "vendor/d3/d3.js",
"jquery.flot.dashes": "vendor/flot/jquery.flot.dashes"
"jquery.flot.dashes": "vendor/flot/jquery.flot.dashes",
"json-formatter-js": "vendor/npm/json-formatter-js/dist/json-formatter"
},
packages: {
......
......@@ -34,6 +34,7 @@ module.exports = function(config) {
'remarkable/dist/*',
'virtual-scroll/**/*',
'mousetrap/**/*',
'json-formatter-js/dist/*.js',
],
dest: '<%= srcDir %>/vendor/npm'
}
......
......@@ -1663,9 +1663,9 @@ glob@7.0.5:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@~7.0.0:
version "7.0.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a"
glob@^7.0.0, glob@^7.1.1, glob@~7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
......@@ -1674,9 +1674,9 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@~7.0.0:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.1.1, glob@~7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
glob@^7.0.3, glob@^7.0.5, glob@~7.0.0:
version "7.0.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a"
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
......@@ -2561,6 +2561,10 @@ jshint@~2.9.4:
shelljs "0.3.x"
strip-json-comments "1.0.x"
json-formatter-js@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/json-formatter-js/-/json-formatter-js-2.2.0.tgz#1ed987223ef2f1d945304597faae78b580a8212b"
json-schema@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
......@@ -3807,11 +3811,11 @@ resolve-pkg@^0.1.0:
dependencies:
resolve-from "^2.0.0"
resolve@1.1.x, resolve@^1.1.6, resolve@~1.1.0:
resolve@1.1.x, resolve@~1.1.0:
version "1.1.7"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
resolve@^1.3.2:
resolve@^1.1.6, resolve@^1.3.2:
version "1.3.3"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5"
dependencies:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment