diff --git a/public/app/plugins/datasource/graphite/FunctionEditor.tsx b/public/app/plugins/datasource/graphite/FunctionEditor.tsx
index d178904640a..ffdbd5dddfe 100644
--- a/public/app/plugins/datasource/graphite/FunctionEditor.tsx
+++ b/public/app/plugins/datasource/graphite/FunctionEditor.tsx
@@ -1,4 +1,4 @@
-import React, { Suspense } from 'react';
+import React from 'react';
import { PopoverController, Popover, ClickOutsideWrapper } from '@grafana/ui';
import { FunctionDescriptor, FunctionEditorControls, FunctionEditorControlsProps } from './FunctionEditorControls';
@@ -9,15 +9,6 @@ interface FunctionEditorProps extends FunctionEditorControlsProps {
interface FunctionEditorState {
showingDescription: boolean;
}
-const FunctionDescription = React.lazy(async () => {
- // @ts-ignore
- const { default: rst2html } = await import(/* webpackChunkName: "rst2html" */ 'rst2html');
- return {
- default(props: { description?: string }) {
- return
;
- },
- };
-});
class FunctionEditor extends React.PureComponent {
private triggerRef = React.createRef();
@@ -31,25 +22,7 @@ class FunctionEditor extends React.PureComponent {
- const {
- onMoveLeft,
- onMoveRight,
- func: {
- def: { name, description },
- },
- } = this.props;
- const { showingDescription } = this.state;
-
- if (showingDescription) {
- return (
-
-
{name}
- Loading description...}>
-
-
-
- );
- }
+ const { onMoveLeft, onMoveRight } = this.props;
return (
{
- this.setState({ showingDescription: true }, () => {
- updatePopperPosition();
- });
- }}
/>
);
};
@@ -83,9 +51,6 @@ class FunctionEditor extends React.PureComponent {
- this.setState({ showingDescription: false });
- }}
renderArrow={({ arrowProps, placement }) => (
)}
@@ -101,9 +66,6 @@ class FunctionEditor extends React.PureComponent {
- this.setState({ showingDescription: false });
- }}
style={{ cursor: 'pointer' }}
>
{this.props.func.def.name}
diff --git a/public/app/plugins/datasource/graphite/FunctionEditorControls.tsx b/public/app/plugins/datasource/graphite/FunctionEditorControls.tsx
index 1a8211b7919..cd9a18b2c78 100644
--- a/public/app/plugins/datasource/graphite/FunctionEditorControls.tsx
+++ b/public/app/plugins/datasource/graphite/FunctionEditorControls.tsx
@@ -1,5 +1,5 @@
-import React from 'react';
-import { Icon } from '@grafana/ui';
+import React, { Suspense } from 'react';
+import { Icon, Tooltip } from '@grafana/ui';
export interface FunctionDescriptor {
text: string;
@@ -20,9 +20,28 @@ export interface FunctionEditorControlsProps {
onRemove: (func: FunctionDescriptor) => void;
}
-const FunctionHelpButton = (props: { description?: string; name: string; onDescriptionShow: () => void }) => {
+const FunctionDescription = React.lazy(async () => {
+ // @ts-ignore
+ const { default: rst2html } = await import(/* webpackChunkName: "rst2html" */ 'rst2html');
+ return {
+ default(props: { description?: string }) {
+ return ;
+ },
+ };
+});
+
+const FunctionHelpButton = (props: { description?: string; name: string }) => {
if (props.description) {
- return ;
+ let tooltip = (
+ Loading description...}>
+
+
+ );
+ return (
+
+
+
+ );
}
return (
@@ -42,10 +61,9 @@ const FunctionHelpButton = (props: { description?: string; name: string; onDescr
export const FunctionEditorControls = (
props: FunctionEditorControlsProps & {
func: FunctionDescriptor;
- onDescriptionShow: () => void;
}
) => {
- const { func, onMoveLeft, onMoveRight, onRemove, onDescriptionShow } = props;
+ const { func, onMoveLeft, onMoveRight, onRemove } = props;
return (
onMoveLeft(func)} />
-
+
onRemove(func)} />
onMoveRight(func)} />
diff --git a/public/app/plugins/datasource/graphite/datasource.test.ts b/public/app/plugins/datasource/graphite/datasource.test.ts
index d3d98337e7b..170f85bea0b 100644
--- a/public/app/plugins/datasource/graphite/datasource.test.ts
+++ b/public/app/plugins/datasource/graphite/datasource.test.ts
@@ -253,6 +253,37 @@ describe('graphiteDatasource', () => {
});
});
+ describe('when fetching Graphite function descriptions', () => {
+ // `"default": Infinity` (invalid JSON) in params passed by Graphite API in 1.1.7
+ const INVALID_JSON =
+ '{"testFunction":{"name":"function","description":"description","module":"graphite.render.functions","group":"Transform","params":[{"name":"param","type":"intOrInf","required":true,"default":Infinity}]}}';
+
+ it('should parse the response with an invalid JSON', async () => {
+ fetchMock.mockImplementation(() => {
+ return of(createFetchResponse(INVALID_JSON));
+ });
+ const funcDefs = await ctx.ds.getFuncDefs();
+ expect(funcDefs).toEqual({
+ testFunction: {
+ category: 'Transform',
+ defaultParams: ['inf'],
+ description: 'description',
+ fake: true,
+ name: 'function',
+ params: [
+ {
+ multiple: false,
+ name: 'param',
+ optional: false,
+ options: undefined,
+ type: 'int_or_infinity',
+ },
+ ],
+ },
+ });
+ });
+ });
+
describe('building graphite params', () => {
it('should return empty array if no targets', () => {
const results = ctx.ds.buildGraphiteParams({
diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts
index 8153fdd7280..24e71a37d97 100644
--- a/public/app/plugins/datasource/graphite/datasource.ts
+++ b/public/app/plugins/datasource/graphite/datasource.ts
@@ -581,7 +581,17 @@ export class GraphiteDatasource extends DataSourceApi {
if (results.status !== 200 || typeof results.data !== 'object') {
- this.funcDefs = gfunc.getFuncDefs(this.graphiteVersion);
+ if (typeof results.data === 'string') {
+ // Fix for a Graphite bug: https://github.com/graphite-project/graphite-web/issues/2609
+ // There is a fix for it https://github.com/graphite-project/graphite-web/pull/2612 but
+ // it was merged to master in July 2020 but it has never been released (the last Graphite
+ // release was 1.1.7 - March 2020). The bug was introduced in Graphite 1.1.7, in versions
+ // 1.1.0 - 1.1.6 /functions endpoint returns a valid JSON
+ const fixedData = JSON.parse(results.data.replace(/"default": ?Infinity/g, '"default": 1e9999'));
+ this.funcDefs = gfunc.parseFuncDefs(fixedData);
+ } else {
+ this.funcDefs = gfunc.getFuncDefs(this.graphiteVersion);
+ }
} else {
this.funcDefs = gfunc.parseFuncDefs(results.data);
}
diff --git a/public/app/plugins/datasource/graphite/gfunc.ts b/public/app/plugins/datasource/graphite/gfunc.ts
index c050d8aa207..399b4c76816 100644
--- a/public/app/plugins/datasource/graphite/gfunc.ts
+++ b/public/app/plugins/datasource/graphite/gfunc.ts
@@ -999,7 +999,7 @@ export class FuncInstance {
}
// param types that should never be quoted
- if (_.includes(['value_or_series', 'boolean', 'int', 'float', 'node'], paramType)) {
+ if (_.includes(['value_or_series', 'boolean', 'int', 'float', 'node', 'int_or_infinity'], paramType)) {
return value;
}
@@ -1155,7 +1155,11 @@ function parseFuncDefs(rawDefs: any) {
};
if (rawParam.default !== undefined) {
- func.defaultParams.push(_.toString(rawParam.default));
+ if (rawParam.default === Infinity) {
+ func.defaultParams.push('inf');
+ } else {
+ func.defaultParams.push(_.toString(rawParam.default));
+ }
} else if (rawParam.suggestions) {
func.defaultParams.push(_.toString(rawParam.suggestions[0]));
} else {
@@ -1179,6 +1183,8 @@ function parseFuncDefs(rawDefs: any) {
param.type = 'int_or_interval';
} else if (rawParam.type === 'seriesList') {
param.type = 'value_or_series';
+ } else if (rawParam.type === 'intOrInf') {
+ param.type = 'int_or_infinity';
}
if (rawParam.options) {
diff --git a/public/sass/components/_query_editor.scss b/public/sass/components/_query_editor.scss
index cf8d2e8ce88..d81f54cbd40 100644
--- a/public/sass/components/_query_editor.scss
+++ b/public/sass/components/_query_editor.scss
@@ -68,6 +68,7 @@ input[type='text'].tight-form-func-param {
.rst-unknown.rst-directive {
font-family: monospace;
margin-bottom: $space-md;
+ word-wrap: break-word;
}
.rst-interpreted_text {