diff --git a/public/app/core/components/collapse_box.ts b/public/app/core/components/collapse_box.ts
new file mode 100644
index 00000000000..05fac27904a
--- /dev/null
+++ b/public/app/core/components/collapse_box.ts
@@ -0,0 +1,58 @@
+///
+
+import coreModule from 'app/core/core_module';
+
+const template = `
+
+`;
+
+export class CollapseBoxCtrl {
+ isOpen: boolean;
+ onOpen: () => void;
+
+ /** @ngInject **/
+ constructor() {
+ this.isOpen = false;
+ }
+
+ toggle() {
+ this.isOpen = !this.isOpen;
+ if (this.isOpen) {
+ this.onOpen();
+ }
+ }
+}
+
+export function collapseBox() {
+ return {
+ restrict: 'E',
+ template: template,
+ controller: CollapseBoxCtrl,
+ bindToController: true,
+ controllerAs: 'ctrl',
+ scope: {
+ "title": "@",
+ "isOpen": "=?",
+ "onOpen": "&"
+ },
+ transclude: {
+ 'actions': '?collapseBoxActions',
+ 'body': 'collapseBoxBody',
+ },
+ link: function(scope, elem, attrs) {
+ }
+ };
+}
+
+coreModule.directive('collapseBox', collapseBox);
diff --git a/public/app/core/components/json_explorer/json_explorer.ts b/public/app/core/components/json_explorer/json_explorer.ts
index 3f460968b77..4b57268c663 100644
--- a/public/app/core/components/json_explorer/json_explorer.ts
+++ b/public/app/core/components/json_explorer/json_explorer.ts
@@ -45,7 +45,7 @@ const _defaultConfig: JsonExplorerConfig = {
* JsonExplorer allows you to render JSON objects in HTML with a
* **collapsible** navigation.
*/
-export default class JsonExplorer {
+export class JsonExplorer {
// Hold the open state after the toggler is used
private _isOpen: boolean = null;
@@ -273,7 +273,7 @@ export default class JsonExplorer {
*
* @returns {HTMLDivElement}
*/
- render(): HTMLDivElement {
+ render(skipRoot = false): HTMLDivElement {
// construct the root element and assign it to this.element
this.element = createElement('div', 'row');
@@ -371,7 +371,9 @@ export default class JsonExplorer {
}
// append toggler and children elements to root element
- this.element.appendChild(togglerLink);
+ if (!skipRoot) {
+ this.element.appendChild(togglerLink);
+ }
this.element.appendChild(children);
// if formatter is set to be open call appendChildren
diff --git a/public/app/core/components/response_viewer.ts b/public/app/core/components/response_viewer.ts
deleted file mode 100644
index fe332f070ec..00000000000
--- a/public/app/core/components/response_viewer.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-///
-
-import coreModule from 'app/core/core_module';
-import JsonExplorer from './json_explorer/json_explorer';
-
-
-const template = `
-
-`;
-
-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 JsonExplorer(scope.response, 2, {
- theme: 'dark',
- });
-
- const html = formatter.render();
- jsonElem.html(html);
- });
-
- }
- };
-}
-
-coreModule.directive('responseViewer', responseViewer);
diff --git a/public/app/core/core.ts b/public/app/core/core.ts
index 4c5fa429677..f6b5046c3fc 100644
--- a/public/app/core/core.ts
+++ b/public/app/core/core.ts
@@ -45,7 +45,8 @@ 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';
+import {collapseBox} from './components/collapse_box';
+import {JsonExplorer} from './components/json_explorer/json_explorer';
export {
arrayJoin,
@@ -69,5 +70,6 @@ export {
contextSrv,
KeybindingSrv,
helpModal,
- responseViewer,
+ collapseBox,
+ JsonExplorer,
};
diff --git a/public/app/features/panel/all.js b/public/app/features/panel/all.js
index 2f978e65345..b4afba4da1b 100644
--- a/public/app/features/panel/all.js
+++ b/public/app/features/panel/all.js
@@ -6,4 +6,5 @@ define([
'./panel_editor_tab',
'./query_editor_row',
'./metrics_ds_selector',
+ './query_troubleshooter',
], function () {});
diff --git a/public/app/features/panel/metrics_ds_selector.ts b/public/app/features/panel/metrics_ds_selector.ts
index 2a9137e6aa5..523268953a0 100644
--- a/public/app/features/panel/metrics_ds_selector.ts
+++ b/public/app/features/panel/metrics_ds_selector.ts
@@ -8,10 +8,6 @@ var module = angular.module('grafana.directives');
var template = `
-
-
-
-
`;
@@ -81,24 +70,8 @@ export class MetricsDsSelectorCtrl {
this.dsSegment = uiSegmentSrv.newSegment({value: this.current.name, selectMode: true});
this.mixedDsSegment = uiSegmentSrv.newSegment({value: 'Add Query', selectMode: true});
-
- appEvents.on('ds-request-response', this.onRequestResponse.bind(this), $scope);
- appEvents.on('ds-request-error', this.onRequestError.bind(this), $scope);
}
- onRequestResponse(data) {
- this.responseData = data;
- }
-
- toggleShowResponse() {
- this.showResponse = !this.showResponse;
- }
-
- onRequestError(err) {
- this.responseData = err;
- this.responseData.isError = true;
- this.showResponse = true;
- }
getOptions(includeBuiltin) {
return Promise.resolve(this.datasources.filter(value => {
diff --git a/public/app/features/panel/query_troubleshooter.ts b/public/app/features/panel/query_troubleshooter.ts
new file mode 100644
index 00000000000..0c9dabb3ac1
--- /dev/null
+++ b/public/app/features/panel/query_troubleshooter.ts
@@ -0,0 +1,108 @@
+///
+
+import _ from 'lodash';
+import appEvents from 'app/core/app_events';
+import {coreModule, JsonExplorer} from 'app/core/core';
+
+const template = `
+
+
+ Copy to clipboard
+
+
+
+
+
+`;
+
+export class QueryTroubleshooterCtrl {
+ responseData: any;
+ showResponse: boolean;
+ panelCtrl: any;
+ renderJsonExplorer: (data) => void;
+
+ /** @ngInject **/
+ constructor($scope, private $timeout) {
+ appEvents.on('ds-request-response', this.onRequestResponse.bind(this), $scope);
+ appEvents.on('ds-request-error', this.onRequestError.bind(this), $scope);
+ }
+
+ onRequestResponse(data) {
+ this.responseData = data;
+ }
+
+ toggleShowResponse() {
+ this.showResponse = !this.showResponse;
+ }
+
+ onRequestError(err) {
+ this.responseData = err;
+ this.responseData.isError = true;
+ this.showResponse = true;
+ }
+
+ onOpen() {
+ if (!this.responseData) {
+ console.log('no data');
+ return;
+ }
+
+ var data = this.responseData;
+ if (data.headers) {
+ delete data.headers;
+ }
+
+ if (data.config) {
+ data.request = data.config;
+ delete data.config;
+ delete data.request.transformRequest;
+ delete data.request.transformResponse;
+ delete data.request.paramSerializer;
+ delete data.request.jsonpCallbackParam;
+ delete data.request.headers;
+ delete data.request.requestId;
+ delete data.request.inspect;
+ delete data.request.retry;
+ delete data.request.timeout;
+ }
+
+ if (data.data) {
+ data.response = data.data;
+
+ delete data.data;
+ delete data.status;
+ delete data.statusText;
+ delete data.$$config;
+ }
+
+ this.$timeout(_.partial(this.renderJsonExplorer, data), 10);
+ }
+}
+
+export function queryTroubleshooter() {
+ return {
+ restrict: 'E',
+ template: template,
+ controller: QueryTroubleshooterCtrl,
+ bindToController: true,
+ controllerAs: 'ctrl',
+ scope: {
+ panelCtrl: "="
+ },
+ link: function(scope, elem, attrs, ctrl) {
+
+ ctrl.renderJsonExplorer = function(data) {
+ var jsonElem = elem.find('.query-troubleshooter-json');
+
+ const formatter = new JsonExplorer(data, 2, {
+ theme: 'dark',
+ });
+
+ const html = formatter.render(true);
+ jsonElem.html(html);
+ };
+ }
+ };
+}
+
+coreModule.directive('queryTroubleshooter', queryTroubleshooter);
diff --git a/public/app/partials/metrics.html b/public/app/partials/metrics.html
index 39c471eabd1..d3c6e6bcfc4 100644
--- a/public/app/partials/metrics.html
+++ b/public/app/partials/metrics.html
@@ -1,4 +1,6 @@
+
+
-
+
diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss
index c8f82ad845a..63ff5c6fe02 100644
--- a/public/sass/_grafana.scss
+++ b/public/sass/_grafana.scss
@@ -75,8 +75,8 @@
@import "components/jsontree";
@import "components/edit_sidemenu.scss";
@import "components/row.scss";
-@import "components/response_viewer.scss";
@import "components/json_explorer.scss";
+@import "components/collapse_box.scss";
// PAGES
@import "pages/login";
diff --git a/public/sass/components/_collapse_box.scss b/public/sass/components/_collapse_box.scss
new file mode 100644
index 00000000000..61abebaf634
--- /dev/null
+++ b/public/sass/components/_collapse_box.scss
@@ -0,0 +1,29 @@
+.collapse-box {
+ margin-bottom: $spacer;
+}
+
+.collapse-box__header {
+ display: flex;
+ flex-direction: row;
+ padding: $input-padding-y $input-padding-x;
+ margin-right: $gf-form-margin;
+ background-color: $input-bg;
+ font-size: $font-size-sm;
+ margin-right: $gf-form-margin;
+
+ border: $input-btn-border-width solid transparent;
+ @include border-radius($label-border-radius-sm);
+}
+
+.collapse-box__header-title {
+ flex-grow: 1;
+}
+
+.collapse-box__body {
+ padding: $input-padding-y*2 $input-padding-x;
+ background-color: $input-label-bg;
+ display: block;
+ margin-right: $gf-form-margin;
+ border: $input-btn-border-width solid transparent;
+ @include border-radius($label-border-radius-sm);
+}
diff --git a/public/sass/components/_json_explorer.scss b/public/sass/components/_json_explorer.scss
index 3c4c19eceb7..d372c332176 100644
--- a/public/sass/components/_json_explorer.scss
+++ b/public/sass/components/_json_explorer.scss
@@ -36,7 +36,7 @@
.json-formatter-string {
color: $string-color;
- white-space: pre;
+ white-space: normal;
word-wrap: break-word;
}
.json-formatter-number { color: $number-color; }
diff --git a/public/sass/components/_response_viewer.scss b/public/sass/components/_response_viewer.scss
deleted file mode 100644
index f554f98e382..00000000000
--- a/public/sass/components/_response_viewer.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-.response-viewer {
- background: $card-background;
- box-shadow: $card-shadow;
- padding: 1rem;
- border-radius: 4px;
-}