mirror of
https://github.com/grafana/grafana.git
synced 2025-12-22 20:54:34 +08:00
Compare commits
12 Commits
docs/add-t
...
v7.1.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b109a1637 | ||
|
|
6a1f05d7ec | ||
|
|
1576b16219 | ||
|
|
44ba5482f1 | ||
|
|
972e07bd2e | ||
|
|
22211e5bdd | ||
|
|
d95c494d9d | ||
|
|
6e3a9d7927 | ||
|
|
4a68ba7b23 | ||
|
|
74ca7121eb | ||
|
|
4d1ea72426 | ||
|
|
11385c6cfe |
@@ -56,7 +56,7 @@ commands:
|
||||
- run:
|
||||
name: "Install Grafana build pipeline tool"
|
||||
command: |
|
||||
VERSION=0.4.17
|
||||
VERSION=0.4.18
|
||||
curl -fLO https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v${VERSION}/grabpl
|
||||
chmod +x grabpl
|
||||
mv grabpl /tmp
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"packages": ["packages/*"],
|
||||
"version": "7.1.0-pre.0"
|
||||
"version": "7.1.0-beta.2"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"name": "grafana",
|
||||
"version": "7.1.0-pre",
|
||||
"version": "7.1.0-beta2",
|
||||
"repository": "github:grafana/grafana",
|
||||
"scripts": {
|
||||
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/data",
|
||||
"version": "7.1.0-pre.0",
|
||||
"version": "7.1.0-beta.2",
|
||||
"description": "Grafana Data Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
|
||||
@@ -152,8 +152,8 @@ describe('Format value', () => {
|
||||
|
||||
it('should return formatted value if there are no matching value mappings', () => {
|
||||
const valueMappings: ValueMapping[] = [
|
||||
{ id: 0, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
|
||||
{ id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' },
|
||||
{ id: 0, text: 'elva', type: MappingType.ValueToText, value: '11' },
|
||||
{ id: 1, text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' },
|
||||
];
|
||||
const value = '10';
|
||||
const instance = getDisplayProcessorFromConfig({ decimals: 1, mappings: valueMappings });
|
||||
@@ -186,8 +186,8 @@ describe('Format value', () => {
|
||||
|
||||
it('should return mapped value if there are matching value mappings', () => {
|
||||
const valueMappings: ValueMapping[] = [
|
||||
{ id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
|
||||
{ id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
|
||||
{ id: 0, text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
|
||||
{ id: 1, text: 'elva', type: MappingType.ValueToText, value: '11' },
|
||||
];
|
||||
const value = '11';
|
||||
const instance = getDisplayProcessorFromConfig({ decimals: 1, mappings: valueMappings });
|
||||
@@ -196,9 +196,7 @@ describe('Format value', () => {
|
||||
});
|
||||
|
||||
it('should return mapped value and leave numeric value in tact if value mapping maps to empty string', () => {
|
||||
const valueMappings: ValueMapping[] = [
|
||||
{ id: 1, operator: '', text: '', type: MappingType.ValueToText, value: '1' },
|
||||
];
|
||||
const valueMappings: ValueMapping[] = [{ id: 1, text: '', type: MappingType.ValueToText, value: '1' }];
|
||||
const value = '1';
|
||||
const instance = getDisplayProcessorFromConfig({ decimals: 1, mappings: valueMappings });
|
||||
|
||||
|
||||
@@ -4,9 +4,8 @@ export enum MappingType {
|
||||
}
|
||||
|
||||
interface BaseMap {
|
||||
id: number;
|
||||
operator: string;
|
||||
text: string;
|
||||
id: number; // this could/should just be the array index
|
||||
text: string; // the final display value
|
||||
type: MappingType;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ describe('Format value with value mappings', () => {
|
||||
|
||||
it('should return undefined with no matching valuemappings', () => {
|
||||
const valueMappings: ValueMapping[] = [
|
||||
{ id: 0, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
|
||||
{ id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' },
|
||||
{ id: 0, text: 'elva', type: MappingType.ValueToText, value: '11' },
|
||||
{ id: 1, text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' },
|
||||
];
|
||||
const value = '10';
|
||||
|
||||
@@ -21,8 +21,8 @@ describe('Format value with value mappings', () => {
|
||||
|
||||
it('should return first matching mapping with lowest id', () => {
|
||||
const valueMappings: ValueMapping[] = [
|
||||
{ id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
|
||||
{ id: 1, operator: '', text: 'tio', type: MappingType.ValueToText, value: '10' },
|
||||
{ id: 0, text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
|
||||
{ id: 1, text: 'tio', type: MappingType.ValueToText, value: '10' },
|
||||
];
|
||||
const value = '10';
|
||||
|
||||
@@ -31,8 +31,8 @@ describe('Format value with value mappings', () => {
|
||||
|
||||
it('should return if value is null and value to text mapping value is null', () => {
|
||||
const valueMappings: ValueMapping[] = [
|
||||
{ id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
|
||||
{ id: 1, operator: '', text: '<NULL>', type: MappingType.ValueToText, value: 'null' },
|
||||
{ id: 0, text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
|
||||
{ id: 1, text: '<NULL>', type: MappingType.ValueToText, value: 'null' },
|
||||
];
|
||||
const value = null;
|
||||
|
||||
@@ -41,8 +41,8 @@ describe('Format value with value mappings', () => {
|
||||
|
||||
it('should return if value is null and range to text mapping from and to is null', () => {
|
||||
const valueMappings: ValueMapping[] = [
|
||||
{ id: 0, operator: '', text: '<NULL>', type: MappingType.RangeToText, from: 'null', to: 'null' },
|
||||
{ id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
|
||||
{ id: 0, text: '<NULL>', type: MappingType.RangeToText, from: 'null', to: 'null' },
|
||||
{ id: 1, text: 'elva', type: MappingType.ValueToText, value: '11' },
|
||||
];
|
||||
const value = null;
|
||||
|
||||
@@ -51,8 +51,8 @@ describe('Format value with value mappings', () => {
|
||||
|
||||
it('should return rangeToText mapping where value equals to', () => {
|
||||
const valueMappings: ValueMapping[] = [
|
||||
{ id: 0, operator: '', text: '1-10', type: MappingType.RangeToText, from: '1', to: '10' },
|
||||
{ id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
|
||||
{ id: 0, text: '1-10', type: MappingType.RangeToText, from: '1', to: '10' },
|
||||
{ id: 1, text: 'elva', type: MappingType.ValueToText, value: '11' },
|
||||
];
|
||||
const value = '10';
|
||||
|
||||
@@ -61,8 +61,8 @@ describe('Format value with value mappings', () => {
|
||||
|
||||
it('should return rangeToText mapping where value equals from', () => {
|
||||
const valueMappings: ValueMapping[] = [
|
||||
{ id: 0, operator: '', text: '10-20', type: MappingType.RangeToText, from: '10', to: '20' },
|
||||
{ id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
|
||||
{ id: 0, text: '10-20', type: MappingType.RangeToText, from: '10', to: '20' },
|
||||
{ id: 1, text: 'elva', type: MappingType.ValueToText, value: '11' },
|
||||
];
|
||||
const value = '10';
|
||||
|
||||
@@ -71,8 +71,8 @@ describe('Format value with value mappings', () => {
|
||||
|
||||
it('should return rangeToText mapping where value is between from and to', () => {
|
||||
const valueMappings: ValueMapping[] = [
|
||||
{ id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
|
||||
{ id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
|
||||
{ id: 0, text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
|
||||
{ id: 1, text: 'elva', type: MappingType.ValueToText, value: '11' },
|
||||
];
|
||||
const value = '10';
|
||||
|
||||
@@ -81,8 +81,8 @@ describe('Format value with value mappings', () => {
|
||||
|
||||
it('should map value text to mapping', () => {
|
||||
const valueMappings: ValueMapping[] = [
|
||||
{ id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
|
||||
{ id: 1, operator: '', text: 'ELVA', type: MappingType.ValueToText, value: 'elva' },
|
||||
{ id: 0, text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
|
||||
{ id: 1, text: 'ELVA', type: MappingType.ValueToText, value: 'elva' },
|
||||
];
|
||||
|
||||
const value = 'elva';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e-selectors",
|
||||
"version": "7.1.0-pre.0",
|
||||
"version": "7.1.0-beta.2",
|
||||
"description": "Grafana End-to-End Test Selectors Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e",
|
||||
"version": "7.1.0-pre.0",
|
||||
"version": "7.1.0-beta.2",
|
||||
"description": "Grafana End-to-End Test Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
@@ -45,7 +45,7 @@
|
||||
"types": "src/index.ts",
|
||||
"dependencies": {
|
||||
"@cypress/webpack-preprocessor": "4.1.3",
|
||||
"@grafana/e2e-selectors": "7.1.0-pre.0",
|
||||
"@grafana/e2e-selectors": "7.1.0-beta.2",
|
||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||
"@mochajs/json-file-reporter": "^1.2.0",
|
||||
"blink-diff": "1.0.13",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/runtime",
|
||||
"version": "7.1.0-pre.0",
|
||||
"version": "7.1.0-beta.2",
|
||||
"description": "Grafana Runtime Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -23,8 +23,8 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "7.1.0-pre.0",
|
||||
"@grafana/ui": "7.1.0-pre.0",
|
||||
"@grafana/data": "7.1.0-beta.2",
|
||||
"@grafana/ui": "7.1.0-beta.2",
|
||||
"systemjs": "0.20.19",
|
||||
"systemjs-plugin-css": "0.1.37"
|
||||
},
|
||||
@@ -32,9 +32,9 @@
|
||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||
"@rollup/plugin-commonjs": "11.0.2",
|
||||
"@rollup/plugin-node-resolve": "7.1.1",
|
||||
"@types/jest": "23.3.14",
|
||||
"@types/rollup-plugin-visualizer": "2.6.0",
|
||||
"@types/systemjs": "^0.20.6",
|
||||
"@types/jest": "23.3.14",
|
||||
"lodash": "4.17.15",
|
||||
"pretty-format": "25.1.0",
|
||||
"rollup": "2.0.6",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/toolkit",
|
||||
"version": "7.1.0-pre.0",
|
||||
"version": "7.1.0-beta.2",
|
||||
"description": "Grafana Toolkit",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/ui",
|
||||
"version": "7.1.0-pre.0",
|
||||
"version": "7.1.0-beta.2",
|
||||
"description": "Grafana Components Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -28,8 +28,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/core": "^10.0.27",
|
||||
"@grafana/data": "7.1.0-pre.0",
|
||||
"@grafana/e2e-selectors": "7.1.0-pre.0",
|
||||
"@grafana/data": "7.1.0-beta.2",
|
||||
"@grafana/e2e-selectors": "7.1.0-beta.2",
|
||||
"@grafana/slate-react": "0.22.9-grafana",
|
||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||
"@iconscout/react-unicons": "^1.0.0",
|
||||
@@ -47,9 +47,8 @@
|
||||
"immutable": "3.8.2",
|
||||
"jquery": "3.5.1",
|
||||
"lodash": "4.17.15",
|
||||
"monaco-editor": "0.20.0",
|
||||
"react-monaco-editor": "0.36.0",
|
||||
"moment": "2.24.0",
|
||||
"monaco-editor": "0.20.0",
|
||||
"papaparse": "4.6.3",
|
||||
"rc-cascader": "1.0.1",
|
||||
"rc-drawer": "3.1.3",
|
||||
@@ -63,6 +62,7 @@
|
||||
"react-dom": "16.12.0",
|
||||
"react-highlight-words": "0.16.0",
|
||||
"react-hook-form": "5.1.3",
|
||||
"react-monaco-editor": "0.36.0",
|
||||
"react-popper": "1.3.3",
|
||||
"react-storybook-addon-props-combinations": "1.1.0",
|
||||
"react-table": "7.0.0",
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { findInsertIndex } from './suggestions';
|
||||
|
||||
describe('Check suggestion index', () => {
|
||||
it('find last $ sign', () => {
|
||||
const line = ' hello $123';
|
||||
const { index, prefix } = findInsertIndex(line);
|
||||
expect(index).toEqual(line.indexOf('$'));
|
||||
expect(prefix).toEqual('$123');
|
||||
});
|
||||
|
||||
it('insert into empty line', () => {
|
||||
const line = '';
|
||||
const { index, prefix } = findInsertIndex(line);
|
||||
expect(index).toEqual(0);
|
||||
expect(prefix).toEqual('');
|
||||
});
|
||||
|
||||
it('insert new word', () => {
|
||||
const line = 'this is a new ';
|
||||
const { index, prefix } = findInsertIndex(line);
|
||||
expect(index).toEqual(line.length);
|
||||
expect(prefix).toEqual('');
|
||||
});
|
||||
|
||||
it('complte a simple word', () => {
|
||||
const line = 'SELECT * FROM tab';
|
||||
const { index, prefix } = findInsertIndex(line);
|
||||
expect(index).toEqual(line.lastIndexOf(' ') + 1);
|
||||
expect(prefix).toEqual('tab');
|
||||
});
|
||||
|
||||
it('complete a quoted word', () => {
|
||||
const line = 'SELECT "hello", "wo';
|
||||
const { index, prefix } = findInsertIndex(line);
|
||||
expect(index).toEqual(line.lastIndexOf('"') + 1);
|
||||
expect(prefix).toEqual('wo');
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,33 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
|
||||
import { CodeEditorSuggestionItem, CodeEditorSuggestionItemKind, CodeEditorSuggestionProvider } from './types';
|
||||
|
||||
/**
|
||||
* @internal -- only exported for tests
|
||||
*/
|
||||
export function findInsertIndex(line: string): { index: number; prefix: string } {
|
||||
for (let i = line.length - 1; i > 0; i--) {
|
||||
const ch = line.charAt(i);
|
||||
if (ch === '$') {
|
||||
return {
|
||||
index: i,
|
||||
prefix: line.substring(i),
|
||||
};
|
||||
}
|
||||
|
||||
// Keep these seperators
|
||||
if (ch === ' ' || ch === '\t' || ch === '"' || ch === "'") {
|
||||
return {
|
||||
index: i + 1,
|
||||
prefix: line.substring(i + 1),
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
index: 0,
|
||||
prefix: line,
|
||||
};
|
||||
}
|
||||
|
||||
function getCompletionItems(
|
||||
prefix: string,
|
||||
suggestions: CodeEditorSuggestionItem[],
|
||||
@@ -53,51 +80,39 @@ export function registerSuggestions(
|
||||
triggerCharacters: ['$'],
|
||||
|
||||
provideCompletionItems: (model, position, context) => {
|
||||
const range = {
|
||||
startLineNumber: position.lineNumber,
|
||||
endLineNumber: position.lineNumber,
|
||||
startColumn: position.column,
|
||||
endColumn: position.column,
|
||||
};
|
||||
|
||||
// Simple check if this was triggered by pressing `$`
|
||||
if (context.triggerCharacter === '$') {
|
||||
const range = {
|
||||
startLineNumber: position.lineNumber,
|
||||
endLineNumber: position.lineNumber,
|
||||
startColumn: position.column - 1,
|
||||
endColumn: position.column,
|
||||
};
|
||||
range.startColumn = position.column - 1;
|
||||
return {
|
||||
suggestions: getCompletionItems('$', getSuggestions(), range),
|
||||
};
|
||||
}
|
||||
|
||||
// find out if we are completing a property in the 'dependencies' object.
|
||||
const lineText = model.getValueInRange({
|
||||
// Find the replacement region
|
||||
const currentLine = model.getValueInRange({
|
||||
startLineNumber: position.lineNumber,
|
||||
startColumn: 1,
|
||||
endLineNumber: position.lineNumber,
|
||||
endColumn: position.column,
|
||||
});
|
||||
|
||||
const idx = lineText.lastIndexOf('$');
|
||||
if (idx >= 0) {
|
||||
const range = {
|
||||
startLineNumber: position.lineNumber,
|
||||
endLineNumber: position.lineNumber,
|
||||
startColumn: idx, // the last $ we found
|
||||
endColumn: position.column,
|
||||
};
|
||||
return {
|
||||
suggestions: getCompletionItems(lineText.substr(idx), getSuggestions(), range),
|
||||
};
|
||||
const { index, prefix } = findInsertIndex(currentLine);
|
||||
range.startColumn = index + 1;
|
||||
|
||||
const suggestions = getCompletionItems(prefix, getSuggestions(), range);
|
||||
if (suggestions.length) {
|
||||
// NOTE, this will replace any language provided suggestions
|
||||
return { suggestions };
|
||||
}
|
||||
|
||||
// Empty line that asked for suggestion
|
||||
if (lineText.trim().length < 1) {
|
||||
return {
|
||||
suggestions: getCompletionItems('', getSuggestions(), {
|
||||
startLineNumber: position.lineNumber,
|
||||
endLineNumber: position.lineNumber,
|
||||
startColumn: position.column,
|
||||
endColumn: position.column,
|
||||
}),
|
||||
};
|
||||
}
|
||||
// console.log('complete?', lineText, context);
|
||||
// Default language suggestions
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -12,8 +12,8 @@ const setup = (spy?: any, propOverrides?: object) => {
|
||||
}
|
||||
},
|
||||
valueMappings: [
|
||||
{ id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' },
|
||||
{ id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
|
||||
{ id: 1, type: MappingType.ValueToText, value: '20', text: 'Ok' },
|
||||
{ id: 2, type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -35,9 +35,7 @@ describe('On remove mapping', () => {
|
||||
const remove = wrapper.find('button[aria-label="ValueMappingsEditor remove button"]');
|
||||
remove.at(0).simulate('click');
|
||||
|
||||
expect(onChangeSpy).toBeCalledWith([
|
||||
{ id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
|
||||
]);
|
||||
expect(onChangeSpy).toBeCalledWith([{ id: 2, type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' }]);
|
||||
});
|
||||
|
||||
it('should remove mapping at index 1', () => {
|
||||
@@ -47,9 +45,7 @@ describe('On remove mapping', () => {
|
||||
const remove = wrapper.find('button[aria-label="ValueMappingsEditor remove button"]');
|
||||
remove.at(1).simulate('click');
|
||||
|
||||
expect(onChangeSpy).toBeCalledWith([
|
||||
{ id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' },
|
||||
]);
|
||||
expect(onChangeSpy).toBeCalledWith([{ id: 1, type: MappingType.ValueToText, value: '20', text: 'Ok' }]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,9 +58,9 @@ describe('Next id to add', () => {
|
||||
add.at(0).simulate('click');
|
||||
|
||||
expect(onChangeSpy).toBeCalledWith([
|
||||
{ id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' },
|
||||
{ id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
|
||||
{ id: 3, operator: '', type: MappingType.ValueToText, from: '', to: '', text: '' },
|
||||
{ id: 1, type: MappingType.ValueToText, value: '20', text: 'Ok' },
|
||||
{ id: 2, type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
|
||||
{ id: 3, type: MappingType.ValueToText, from: '', to: '', text: '' },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -73,8 +69,6 @@ describe('Next id to add', () => {
|
||||
const wrapper = setup(onChangeSpy, { valueMappings: [] });
|
||||
const add = wrapper.find('*[aria-label="ValueMappingsEditor add mapping button"]');
|
||||
add.at(0).simulate('click');
|
||||
expect(onChangeSpy).toBeCalledWith([
|
||||
{ id: 0, operator: '', type: MappingType.ValueToText, from: '', to: '', text: '' },
|
||||
]);
|
||||
expect(onChangeSpy).toBeCalledWith([{ id: 0, type: MappingType.ValueToText, from: '', to: '', text: '' }]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,6 @@ export const ValueMappingsEditor: React.FC<Props> = ({ valueMappings, onChange,
|
||||
type: MappingType.ValueToText,
|
||||
from: '',
|
||||
to: '',
|
||||
operator: '',
|
||||
text: '',
|
||||
};
|
||||
const id = update && update.length > 0 ? Math.max(...update.map(v => v.id)) + 1 : 0;
|
||||
|
||||
@@ -155,6 +155,7 @@ export const getStandardFieldConfigs = () => {
|
||||
id: 'mappings',
|
||||
path: 'mappings',
|
||||
name: 'Value mappings',
|
||||
description: 'Modify the display text based on input value',
|
||||
|
||||
editor: standardEditorsRegistry.get('mappings').editor as any,
|
||||
override: standardEditorsRegistry.get('mappings').editor as any,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jaegertracing/jaeger-ui-components",
|
||||
"version": "7.1.0-pre.0",
|
||||
"version": "7.1.0-beta.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -14,7 +14,7 @@
|
||||
"typescript": "3.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "7.1.0-pre.0",
|
||||
"@grafana/data": "7.1.0-beta.2",
|
||||
"@types/classnames": "^2.2.7",
|
||||
"@types/deep-freeze": "^0.1.1",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
|
||||
@@ -16,4 +16,6 @@ type Licensing interface {
|
||||
LicenseURL(user *SignedInUser) string
|
||||
|
||||
StateInfo() string
|
||||
|
||||
TokenRaw() string
|
||||
}
|
||||
|
||||
@@ -100,7 +100,11 @@ func (m *manager) Register(pluginID string, factory PluginFactoryFunc) error {
|
||||
}
|
||||
|
||||
if m.License.HasLicense() {
|
||||
hostEnv = append(hostEnv, fmt.Sprintf("GF_ENTERPRISE_LICENSE_PATH=%s", m.Cfg.EnterpriseLicensePath))
|
||||
hostEnv = append(
|
||||
hostEnv,
|
||||
fmt.Sprintf("GF_ENTERPRISE_LICENSE_PATH=%s", m.Cfg.EnterpriseLicensePath),
|
||||
fmt.Sprintf("GF_ENTERPRISE_LICENSE_TEXT=%s", m.License.TokenRaw()),
|
||||
)
|
||||
}
|
||||
|
||||
env := pluginSettings.ToEnv("GF_PLUGIN", hostEnv)
|
||||
|
||||
@@ -251,6 +251,7 @@ func TestManager(t *testing.T) {
|
||||
t.Run("Plugin registration scenario when Grafana is licensed", func(t *testing.T) {
|
||||
ctx.license.edition = "Enterprise"
|
||||
ctx.license.hasLicense = true
|
||||
ctx.license.tokenRaw = "testtoken"
|
||||
ctx.cfg.BuildVersion = "7.0.0"
|
||||
ctx.cfg.EnterpriseLicensePath = "/license.txt"
|
||||
|
||||
@@ -258,8 +259,8 @@ func TestManager(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Should provide expected host environment variables", func(t *testing.T) {
|
||||
require.Len(t, ctx.env, 3)
|
||||
require.EqualValues(t, []string{"GF_VERSION=7.0.0", "GF_EDITION=Enterprise", "GF_ENTERPRISE_LICENSE_PATH=/license.txt"}, ctx.env)
|
||||
require.Len(t, ctx.env, 4)
|
||||
require.EqualValues(t, []string{"GF_VERSION=7.0.0", "GF_EDITION=Enterprise", "GF_ENTERPRISE_LICENSE_PATH=/license.txt", "GF_ENTERPRISE_LICENSE_TEXT=testtoken"}, ctx.env)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -383,6 +384,7 @@ func (tp *testPlugin) CallResource(ctx context.Context, req *backend.CallResourc
|
||||
type testLicensingService struct {
|
||||
edition string
|
||||
hasLicense bool
|
||||
tokenRaw string
|
||||
}
|
||||
|
||||
func (t *testLicensingService) HasLicense() bool {
|
||||
@@ -408,3 +410,7 @@ func (t *testLicensingService) LicenseURL(user *models.SignedInUser) string {
|
||||
func (t *testLicensingService) HasValidLicense() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *testLicensingService) TokenRaw() string {
|
||||
return t.tokenRaw
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package plugins
|
||||
import (
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -63,7 +64,9 @@ func (fp *FrontendPluginBase) handleModuleDefaults() {
|
||||
// Previously there was an assumption that the plugin directory
|
||||
// should be public/app/plugins/<plugin type>/<plugin id>
|
||||
// However this can be an issue if the plugin directory should be renamed to something else
|
||||
currentDir := path.Base(fp.PluginDir)
|
||||
currentDir := filepath.Base(fp.PluginDir)
|
||||
// use path package for the following statements
|
||||
// because these are not file paths
|
||||
fp.Module = path.Join("app/plugins", fp.Type, currentDir, "module")
|
||||
fp.BaseUrl = path.Join("public/app/plugins", fp.Type, currentDir)
|
||||
}
|
||||
|
||||
@@ -56,3 +56,7 @@ func (l *OSSLicensingService) Init() error {
|
||||
func (*OSSLicensingService) HasValidLicense() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*OSSLicensingService) TokenRaw() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@grafana-plugins/input-datasource",
|
||||
"version": "7.1.0-pre.0",
|
||||
"version": "7.1.0-beta.2",
|
||||
"description": "Input Datasource",
|
||||
"private": true,
|
||||
"repository": {
|
||||
@@ -16,9 +16,9 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@grafana/data": "7.1.0-pre.0",
|
||||
"@grafana/toolkit": "7.1.0-pre.0",
|
||||
"@grafana/ui": "7.1.0-pre.0"
|
||||
"@grafana/data": "7.1.0-beta.2",
|
||||
"@grafana/toolkit": "7.1.0-beta.2",
|
||||
"@grafana/ui": "7.1.0-beta.2"
|
||||
},
|
||||
"volta": {
|
||||
"node": "12.16.2"
|
||||
|
||||
@@ -45,7 +45,7 @@ export const ForgottenPassword: FC = () => {
|
||||
<Legend>Reset password</Legend>
|
||||
<Field
|
||||
label="User"
|
||||
description="Enter your informaton to get a reset link sent to you"
|
||||
description="Enter your information to get a reset link sent to you"
|
||||
invalid={!!errors.userOrEmail}
|
||||
error={errors?.userOrEmail?.message}
|
||||
>
|
||||
|
||||
@@ -49,10 +49,11 @@ export const getLoginStyles = (theme: GrafanaTheme) => {
|
||||
min-height: 100vh;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-color: ${theme.palette.black};
|
||||
min-width: 100%;
|
||||
margin-left: 0;
|
||||
background-color: $black;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`,
|
||||
@@ -76,7 +77,7 @@ export const getLoginStyles = (theme: GrafanaTheme) => {
|
||||
text-align: center;
|
||||
`,
|
||||
mainTitle: css`
|
||||
font-size: '32px';
|
||||
font-size: 32px;
|
||||
`,
|
||||
subTitle: css`
|
||||
font-size: ${theme.typography.size.md};
|
||||
|
||||
@@ -95,7 +95,7 @@ export class PanelHeader extends Component<Props, State> {
|
||||
return (
|
||||
<div className="panel-loading" onClick={this.onCancelQuery}>
|
||||
<Tooltip content="Cancel query">
|
||||
<Icon className="panel-loading__spinner spin-counter-clock" name="sync" />
|
||||
<Icon className="panel-loading__spinner spin-clockwise" name="sync" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -23,7 +23,7 @@ export const NoDataSourceCallToAction = () => {
|
||||
);
|
||||
|
||||
const ctaElement = (
|
||||
<LinkButton size="lg" href="/datasources/new" icon="database">
|
||||
<LinkButton size="lg" href="datasources/new" icon="database">
|
||||
Add data source
|
||||
</LinkButton>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { toUtc } from '@grafana/data';
|
||||
|
||||
import { LokiQueryEditor } from './LokiQueryEditor';
|
||||
import { LokiDatasource } from '../datasource';
|
||||
import { LokiQuery } from '../types';
|
||||
|
||||
const createMockRequestRange = (from: string, to: string) => {
|
||||
return {
|
||||
request: {
|
||||
range: {
|
||||
from: toUtc(from, 'YYYY-MM-DD'),
|
||||
to: toUtc(to, 'YYYY-MM-DD'),
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const setup = (propOverrides?: object) => {
|
||||
const datasourceMock: unknown = {};
|
||||
const datasource: LokiDatasource = datasourceMock as LokiDatasource;
|
||||
const onRunQuery = jest.fn();
|
||||
const onChange = jest.fn();
|
||||
|
||||
const query: LokiQuery = {
|
||||
expr: '',
|
||||
refId: 'A',
|
||||
legendFormat: 'My Legend',
|
||||
};
|
||||
|
||||
const data = createMockRequestRange('2020-01-01', '2020-01-02');
|
||||
|
||||
const props: any = {
|
||||
datasource,
|
||||
onChange,
|
||||
onRunQuery,
|
||||
query,
|
||||
data,
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
|
||||
const wrapper = shallow(<LokiQueryEditor {...props} />);
|
||||
const instance = wrapper.instance() as LokiQueryEditor;
|
||||
|
||||
return {
|
||||
instance,
|
||||
wrapper,
|
||||
};
|
||||
};
|
||||
|
||||
describe('Render LokiQueryEditor with legend', () => {
|
||||
it('should render', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should update absolute timerange', () => {
|
||||
const { wrapper } = setup();
|
||||
wrapper.setProps({
|
||||
data: createMockRequestRange('2019-01-01', '2020-01-02'),
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,45 +1,105 @@
|
||||
// Libraries
|
||||
import React, { memo } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Types
|
||||
import { AbsoluteTimeRange, QueryEditorProps } from '@grafana/data';
|
||||
import { AbsoluteTimeRange, QueryEditorProps, PanelData } from '@grafana/data';
|
||||
import { InlineFormLabel } from '@grafana/ui';
|
||||
import { LokiDatasource } from '../datasource';
|
||||
import { LokiQuery } from '../types';
|
||||
import { LokiQueryField } from './LokiQueryField';
|
||||
|
||||
type Props = QueryEditorProps<LokiDatasource, LokiQuery>;
|
||||
|
||||
export const LokiQueryEditor = memo(function LokiQueryEditor(props: Props) {
|
||||
const { query, data, datasource, onChange, onRunQuery } = props;
|
||||
interface State {
|
||||
legendFormat: string;
|
||||
}
|
||||
|
||||
let absolute: AbsoluteTimeRange;
|
||||
export class LokiQueryEditor extends PureComponent<Props, State> {
|
||||
// Query target to be modified and used for queries
|
||||
query: LokiQuery;
|
||||
|
||||
if (data && data.request) {
|
||||
const { range } = data.request;
|
||||
absolute = {
|
||||
from: range.from.valueOf(),
|
||||
to: range.to.valueOf(),
|
||||
};
|
||||
} else {
|
||||
absolute = {
|
||||
from: Date.now() - 10000,
|
||||
to: Date.now(),
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
// Use default query to prevent undefined input values
|
||||
const defaultQuery: Partial<LokiQuery> = { expr: '', legendFormat: '' };
|
||||
const query = Object.assign({}, defaultQuery, props.query);
|
||||
this.query = query;
|
||||
// Query target properties that are fully controlled inputs
|
||||
this.state = {
|
||||
// Fully controlled text inputs
|
||||
legendFormat: query.legendFormat,
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LokiQueryField
|
||||
datasource={datasource}
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
onRunQuery={onRunQuery}
|
||||
history={[]}
|
||||
data={data}
|
||||
absoluteRange={absolute}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
calcAbsoluteRange = (data: PanelData): AbsoluteTimeRange => {
|
||||
if (data && data.request) {
|
||||
const { range } = data.request;
|
||||
return {
|
||||
from: range.from.valueOf(),
|
||||
to: range.to.valueOf(),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
from: Date.now() - 10000,
|
||||
to: Date.now(),
|
||||
};
|
||||
};
|
||||
|
||||
onFieldChange = (query: LokiQuery, override?: any) => {
|
||||
this.query.expr = query.expr;
|
||||
};
|
||||
|
||||
onLegendChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
const legendFormat = e.currentTarget.value;
|
||||
this.query.legendFormat = legendFormat;
|
||||
this.setState({ legendFormat });
|
||||
};
|
||||
|
||||
onRunQuery = () => {
|
||||
const { query } = this;
|
||||
this.props.onChange(query);
|
||||
this.props.onRunQuery();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { datasource, query, data } = this.props;
|
||||
const { legendFormat } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LokiQueryField
|
||||
datasource={datasource}
|
||||
query={query}
|
||||
onChange={this.onFieldChange}
|
||||
onRunQuery={this.onRunQuery}
|
||||
history={[]}
|
||||
data={data}
|
||||
absoluteRange={this.calcAbsoluteRange(data)}
|
||||
/>
|
||||
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel
|
||||
width={7}
|
||||
tooltip="Controls the name of the time series, using name or pattern. For example
|
||||
{{hostname}} will be replaced with label value for the label hostname. The legend only applies to metric queries."
|
||||
>
|
||||
Legend
|
||||
</InlineFormLabel>
|
||||
<input
|
||||
type="text"
|
||||
className="gf-form-input"
|
||||
placeholder="legend format"
|
||||
value={legendFormat}
|
||||
onChange={this.onLegendChange}
|
||||
onBlur={this.onRunQuery}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LokiQueryEditor;
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Render LokiQueryEditor with legend should render 1`] = `
|
||||
<div>
|
||||
<Component
|
||||
absoluteRange={
|
||||
Object {
|
||||
"from": 1577836800000,
|
||||
"to": 1577923200000,
|
||||
}
|
||||
}
|
||||
data={
|
||||
Object {
|
||||
"request": Object {
|
||||
"range": Object {
|
||||
"from": "2020-01-01T00:00:00.000Z",
|
||||
"to": "2020-01-02T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
datasource={Object {}}
|
||||
history={Array []}
|
||||
onChange={[Function]}
|
||||
onRunQuery={[Function]}
|
||||
query={
|
||||
Object {
|
||||
"expr": "",
|
||||
"legendFormat": "My Legend",
|
||||
"refId": "A",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
tooltip="Controls the name of the time series, using name or pattern. For example
|
||||
{{hostname}} will be replaced with label value for the label hostname. The legend only applies to metric queries."
|
||||
width={7}
|
||||
>
|
||||
Legend
|
||||
</Component>
|
||||
<input
|
||||
className="gf-form-input"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
placeholder="legend format"
|
||||
type="text"
|
||||
value="My Legend"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Render LokiQueryEditor with legend should update absolute timerange 1`] = `
|
||||
<div>
|
||||
<Component
|
||||
absoluteRange={
|
||||
Object {
|
||||
"from": 1546300800000,
|
||||
"to": 1577923200000,
|
||||
}
|
||||
}
|
||||
data={
|
||||
Object {
|
||||
"request": Object {
|
||||
"range": Object {
|
||||
"from": "2019-01-01T00:00:00.000Z",
|
||||
"to": "2020-01-02T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
datasource={Object {}}
|
||||
history={Array []}
|
||||
onChange={[Function]}
|
||||
onRunQuery={[Function]}
|
||||
query={
|
||||
Object {
|
||||
"expr": "",
|
||||
"legendFormat": "My Legend",
|
||||
"refId": "A",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
tooltip="Controls the name of the time series, using name or pattern. For example
|
||||
{{hostname}} will be replaced with label value for the label hostname. The legend only applies to metric queries."
|
||||
width={7}
|
||||
>
|
||||
Legend
|
||||
</Component>
|
||||
<input
|
||||
className="gf-form-input"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
placeholder="legend format"
|
||||
type="text"
|
||||
value="My Legend"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -143,8 +143,10 @@ function createUid(ts: string, labelsString: string, line: string): string {
|
||||
}
|
||||
|
||||
function lokiMatrixToTimeSeries(matrixResult: LokiMatrixResult, options: TransformerOptions): TimeSeries {
|
||||
const name = createMetricLabel(matrixResult.metric, options);
|
||||
return {
|
||||
target: createMetricLabel(matrixResult.metric, options),
|
||||
target: name,
|
||||
title: name,
|
||||
datapoints: lokiPointsToTimeseriesPoints(matrixResult.values, options),
|
||||
tags: matrixResult.metric,
|
||||
meta: options.meta,
|
||||
|
||||
@@ -35,15 +35,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin-counter-clock {
|
||||
@keyframes spin-clockwise {
|
||||
0% {
|
||||
transform: rotate(359deg);
|
||||
transform: rotate(0deg) scaleX(-1); // scaleX flips the `sync` icon so arrows point the correct way
|
||||
}
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
transform: rotate(359deg) scaleX(-1);
|
||||
}
|
||||
}
|
||||
|
||||
.spin-counter-clock {
|
||||
animation: spin-counter-clock 3s infinite linear;
|
||||
.spin-clockwise {
|
||||
animation: spin-clockwise 3s infinite linear;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user