Compare commits

...

12 Commits

Author SHA1 Message Date
Alex Khomenko
0b109a1637 Forgot password: Fix styling (#26002)
(cherry picked from commit 9e47114c45)
2020-07-02 13:07:33 +03:00
Arve Knudsen
6a1f05d7ec CircleCI: Upgrade build pipeline tool (#26006)
Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
(cherry picked from commit 3e9e2db384)
2020-07-02 13:07:33 +03:00
Ryan McKinley
1576b16219 Monaco: check suggestions against current word (#25992)
* trigger on current word

* proper index

* test suggestsions

* test suggestsions

* fix test

(cherry picked from commit 085b2f3dbf)
2020-07-02 13:07:33 +03:00
Sofia Papagiannaki
44ba5482f1 Release v7.1.0-beta2 2020-07-02 13:07:33 +03:00
Ryan McKinley
972e07bd2e Panel Loading: spin clockwise, not counter clockwise (#25998)
* spin clockwise

* spin clockwise

(cherry picked from commit 90a5a85eb1)
2020-07-02 13:07:33 +03:00
Sebastian Widmer
22211e5bdd Loki: Allow aliasing Loki queries in dashboard (#25706)
* Loki: Add Legend field to query editor

* Loki: Basic test for legend field

* Loki: Mention legend is only for metric queries

* Loki: Fix absolute timerange never updating

(cherry picked from commit 5789f80e14)
2020-07-02 13:07:33 +03:00
Ryan McKinley
d95c494d9d Value Mappings: remove unused operator property from interface (#25989)
(cherry picked from commit 73e82af4df)
2020-07-02 13:07:33 +03:00
Ivana Huckova
6e3a9d7927 Fix href to datasources for NoDataSourceCallToAction in Explore (#25991)
(cherry picked from commit c9751707c5)
2020-07-02 13:07:33 +03:00
Dan Cech
4a68ba7b23 provide license token directly via plugin environment (#25987)
* provide license token directly via plugin environment

(cherry picked from commit b5ca2381bc)
2020-07-02 13:07:33 +03:00
Sofia Papagiannaki
74ca7121eb Fix build-in plugins failing to load in windows (#25982)
(cherry picked from commit bcaa42fbb3)
2020-07-02 13:07:33 +03:00
Marcus Andersson
4d1ea72426 release 7.1.0-beta1 2020-07-01 12:13:45 +02:00
Marcus Andersson
11385c6cfe bumped version to beta1. 2020-07-01 12:11:50 +02:00
33 changed files with 446 additions and 139 deletions

View File

@@ -56,7 +56,7 @@ commands:
- run: - run:
name: "Install Grafana build pipeline tool" name: "Install Grafana build pipeline tool"
command: | command: |
VERSION=0.4.17 VERSION=0.4.18
curl -fLO https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v${VERSION}/grabpl curl -fLO https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v${VERSION}/grabpl
chmod +x grabpl chmod +x grabpl
mv grabpl /tmp mv grabpl /tmp

View File

@@ -2,5 +2,5 @@
"npmClient": "yarn", "npmClient": "yarn",
"useWorkspaces": true, "useWorkspaces": true,
"packages": ["packages/*"], "packages": ["packages/*"],
"version": "7.1.0-pre.0" "version": "7.1.0-beta.2"
} }

View File

@@ -3,7 +3,7 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"private": true, "private": true,
"name": "grafana", "name": "grafana",
"version": "7.1.0-pre", "version": "7.1.0-beta2",
"repository": "github:grafana/grafana", "repository": "github:grafana/grafana",
"scripts": { "scripts": {
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js", "api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs", "author": "Grafana Labs",
"license": "Apache-2.0", "license": "Apache-2.0",
"name": "@grafana/data", "name": "@grafana/data",
"version": "7.1.0-pre.0", "version": "7.1.0-beta.2",
"description": "Grafana Data Library", "description": "Grafana Data Library",
"keywords": [ "keywords": [
"typescript" "typescript"

View File

@@ -152,8 +152,8 @@ describe('Format value', () => {
it('should return formatted value if there are no matching value mappings', () => { it('should return formatted value if there are no matching value mappings', () => {
const valueMappings: ValueMapping[] = [ const valueMappings: ValueMapping[] = [
{ id: 0, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, { id: 0, text: 'elva', type: MappingType.ValueToText, value: '11' },
{ id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' }, { id: 1, text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' },
]; ];
const value = '10'; const value = '10';
const instance = getDisplayProcessorFromConfig({ decimals: 1, mappings: valueMappings }); 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', () => { it('should return mapped value if there are matching value mappings', () => {
const valueMappings: ValueMapping[] = [ const valueMappings: ValueMapping[] = [
{ id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, { id: 0, text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
{ id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, { id: 1, text: 'elva', type: MappingType.ValueToText, value: '11' },
]; ];
const value = '11'; const value = '11';
const instance = getDisplayProcessorFromConfig({ decimals: 1, mappings: valueMappings }); 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', () => { it('should return mapped value and leave numeric value in tact if value mapping maps to empty string', () => {
const valueMappings: ValueMapping[] = [ const valueMappings: ValueMapping[] = [{ id: 1, text: '', type: MappingType.ValueToText, value: '1' }];
{ id: 1, operator: '', text: '', type: MappingType.ValueToText, value: '1' },
];
const value = '1'; const value = '1';
const instance = getDisplayProcessorFromConfig({ decimals: 1, mappings: valueMappings }); const instance = getDisplayProcessorFromConfig({ decimals: 1, mappings: valueMappings });

View File

@@ -4,9 +4,8 @@ export enum MappingType {
} }
interface BaseMap { interface BaseMap {
id: number; id: number; // this could/should just be the array index
operator: string; text: string; // the final display value
text: string;
type: MappingType; type: MappingType;
} }

View File

@@ -11,8 +11,8 @@ describe('Format value with value mappings', () => {
it('should return undefined with no matching valuemappings', () => { it('should return undefined with no matching valuemappings', () => {
const valueMappings: ValueMapping[] = [ const valueMappings: ValueMapping[] = [
{ id: 0, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, { id: 0, text: 'elva', type: MappingType.ValueToText, value: '11' },
{ id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' }, { id: 1, text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' },
]; ];
const value = '10'; const value = '10';
@@ -21,8 +21,8 @@ describe('Format value with value mappings', () => {
it('should return first matching mapping with lowest id', () => { it('should return first matching mapping with lowest id', () => {
const valueMappings: ValueMapping[] = [ const valueMappings: ValueMapping[] = [
{ id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, { id: 0, text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
{ id: 1, operator: '', text: 'tio', type: MappingType.ValueToText, value: '10' }, { id: 1, text: 'tio', type: MappingType.ValueToText, value: '10' },
]; ];
const 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', () => { it('should return if value is null and value to text mapping value is null', () => {
const valueMappings: ValueMapping[] = [ const valueMappings: ValueMapping[] = [
{ id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, { id: 0, text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
{ id: 1, operator: '', text: '<NULL>', type: MappingType.ValueToText, value: 'null' }, { id: 1, text: '<NULL>', type: MappingType.ValueToText, value: 'null' },
]; ];
const 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', () => { it('should return if value is null and range to text mapping from and to is null', () => {
const valueMappings: ValueMapping[] = [ const valueMappings: ValueMapping[] = [
{ id: 0, operator: '', text: '<NULL>', type: MappingType.RangeToText, from: 'null', to: 'null' }, { id: 0, text: '<NULL>', type: MappingType.RangeToText, from: 'null', to: 'null' },
{ id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, { id: 1, text: 'elva', type: MappingType.ValueToText, value: '11' },
]; ];
const value = null; const value = null;
@@ -51,8 +51,8 @@ describe('Format value with value mappings', () => {
it('should return rangeToText mapping where value equals to', () => { it('should return rangeToText mapping where value equals to', () => {
const valueMappings: ValueMapping[] = [ const valueMappings: ValueMapping[] = [
{ id: 0, operator: '', text: '1-10', type: MappingType.RangeToText, from: '1', to: '10' }, { id: 0, text: '1-10', type: MappingType.RangeToText, from: '1', to: '10' },
{ id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, { id: 1, text: 'elva', type: MappingType.ValueToText, value: '11' },
]; ];
const value = '10'; const value = '10';
@@ -61,8 +61,8 @@ describe('Format value with value mappings', () => {
it('should return rangeToText mapping where value equals from', () => { it('should return rangeToText mapping where value equals from', () => {
const valueMappings: ValueMapping[] = [ const valueMappings: ValueMapping[] = [
{ id: 0, operator: '', text: '10-20', type: MappingType.RangeToText, from: '10', to: '20' }, { id: 0, text: '10-20', type: MappingType.RangeToText, from: '10', to: '20' },
{ id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, { id: 1, text: 'elva', type: MappingType.ValueToText, value: '11' },
]; ];
const value = '10'; 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', () => { it('should return rangeToText mapping where value is between from and to', () => {
const valueMappings: ValueMapping[] = [ const valueMappings: ValueMapping[] = [
{ id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, { id: 0, text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
{ id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, { id: 1, text: 'elva', type: MappingType.ValueToText, value: '11' },
]; ];
const value = '10'; const value = '10';
@@ -81,8 +81,8 @@ describe('Format value with value mappings', () => {
it('should map value text to mapping', () => { it('should map value text to mapping', () => {
const valueMappings: ValueMapping[] = [ const valueMappings: ValueMapping[] = [
{ id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, { id: 0, text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
{ id: 1, operator: '', text: 'ELVA', type: MappingType.ValueToText, value: 'elva' }, { id: 1, text: 'ELVA', type: MappingType.ValueToText, value: 'elva' },
]; ];
const value = 'elva'; const value = 'elva';

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs", "author": "Grafana Labs",
"license": "Apache-2.0", "license": "Apache-2.0",
"name": "@grafana/e2e-selectors", "name": "@grafana/e2e-selectors",
"version": "7.1.0-pre.0", "version": "7.1.0-beta.2",
"description": "Grafana End-to-End Test Selectors Library", "description": "Grafana End-to-End Test Selectors Library",
"keywords": [ "keywords": [
"cli", "cli",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs", "author": "Grafana Labs",
"license": "Apache-2.0", "license": "Apache-2.0",
"name": "@grafana/e2e", "name": "@grafana/e2e",
"version": "7.1.0-pre.0", "version": "7.1.0-beta.2",
"description": "Grafana End-to-End Test Library", "description": "Grafana End-to-End Test Library",
"keywords": [ "keywords": [
"cli", "cli",
@@ -45,7 +45,7 @@
"types": "src/index.ts", "types": "src/index.ts",
"dependencies": { "dependencies": {
"@cypress/webpack-preprocessor": "4.1.3", "@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", "@grafana/tsconfig": "^1.0.0-rc1",
"@mochajs/json-file-reporter": "^1.2.0", "@mochajs/json-file-reporter": "^1.2.0",
"blink-diff": "1.0.13", "blink-diff": "1.0.13",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs", "author": "Grafana Labs",
"license": "Apache-2.0", "license": "Apache-2.0",
"name": "@grafana/runtime", "name": "@grafana/runtime",
"version": "7.1.0-pre.0", "version": "7.1.0-beta.2",
"description": "Grafana Runtime Library", "description": "Grafana Runtime Library",
"keywords": [ "keywords": [
"grafana", "grafana",
@@ -23,8 +23,8 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@grafana/data": "7.1.0-pre.0", "@grafana/data": "7.1.0-beta.2",
"@grafana/ui": "7.1.0-pre.0", "@grafana/ui": "7.1.0-beta.2",
"systemjs": "0.20.19", "systemjs": "0.20.19",
"systemjs-plugin-css": "0.1.37" "systemjs-plugin-css": "0.1.37"
}, },
@@ -32,9 +32,9 @@
"@grafana/tsconfig": "^1.0.0-rc1", "@grafana/tsconfig": "^1.0.0-rc1",
"@rollup/plugin-commonjs": "11.0.2", "@rollup/plugin-commonjs": "11.0.2",
"@rollup/plugin-node-resolve": "7.1.1", "@rollup/plugin-node-resolve": "7.1.1",
"@types/jest": "23.3.14",
"@types/rollup-plugin-visualizer": "2.6.0", "@types/rollup-plugin-visualizer": "2.6.0",
"@types/systemjs": "^0.20.6", "@types/systemjs": "^0.20.6",
"@types/jest": "23.3.14",
"lodash": "4.17.15", "lodash": "4.17.15",
"pretty-format": "25.1.0", "pretty-format": "25.1.0",
"rollup": "2.0.6", "rollup": "2.0.6",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs", "author": "Grafana Labs",
"license": "Apache-2.0", "license": "Apache-2.0",
"name": "@grafana/toolkit", "name": "@grafana/toolkit",
"version": "7.1.0-pre.0", "version": "7.1.0-beta.2",
"description": "Grafana Toolkit", "description": "Grafana Toolkit",
"keywords": [ "keywords": [
"grafana", "grafana",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs", "author": "Grafana Labs",
"license": "Apache-2.0", "license": "Apache-2.0",
"name": "@grafana/ui", "name": "@grafana/ui",
"version": "7.1.0-pre.0", "version": "7.1.0-beta.2",
"description": "Grafana Components Library", "description": "Grafana Components Library",
"keywords": [ "keywords": [
"grafana", "grafana",
@@ -28,8 +28,8 @@
}, },
"dependencies": { "dependencies": {
"@emotion/core": "^10.0.27", "@emotion/core": "^10.0.27",
"@grafana/data": "7.1.0-pre.0", "@grafana/data": "7.1.0-beta.2",
"@grafana/e2e-selectors": "7.1.0-pre.0", "@grafana/e2e-selectors": "7.1.0-beta.2",
"@grafana/slate-react": "0.22.9-grafana", "@grafana/slate-react": "0.22.9-grafana",
"@grafana/tsconfig": "^1.0.0-rc1", "@grafana/tsconfig": "^1.0.0-rc1",
"@iconscout/react-unicons": "^1.0.0", "@iconscout/react-unicons": "^1.0.0",
@@ -47,9 +47,8 @@
"immutable": "3.8.2", "immutable": "3.8.2",
"jquery": "3.5.1", "jquery": "3.5.1",
"lodash": "4.17.15", "lodash": "4.17.15",
"monaco-editor": "0.20.0",
"react-monaco-editor": "0.36.0",
"moment": "2.24.0", "moment": "2.24.0",
"monaco-editor": "0.20.0",
"papaparse": "4.6.3", "papaparse": "4.6.3",
"rc-cascader": "1.0.1", "rc-cascader": "1.0.1",
"rc-drawer": "3.1.3", "rc-drawer": "3.1.3",
@@ -63,6 +62,7 @@
"react-dom": "16.12.0", "react-dom": "16.12.0",
"react-highlight-words": "0.16.0", "react-highlight-words": "0.16.0",
"react-hook-form": "5.1.3", "react-hook-form": "5.1.3",
"react-monaco-editor": "0.36.0",
"react-popper": "1.3.3", "react-popper": "1.3.3",
"react-storybook-addon-props-combinations": "1.1.0", "react-storybook-addon-props-combinations": "1.1.0",
"react-table": "7.0.0", "react-table": "7.0.0",

View File

@@ -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');
});
});

View File

@@ -2,6 +2,33 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import { CodeEditorSuggestionItem, CodeEditorSuggestionItemKind, CodeEditorSuggestionProvider } from './types'; 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( function getCompletionItems(
prefix: string, prefix: string,
suggestions: CodeEditorSuggestionItem[], suggestions: CodeEditorSuggestionItem[],
@@ -53,51 +80,39 @@ export function registerSuggestions(
triggerCharacters: ['$'], triggerCharacters: ['$'],
provideCompletionItems: (model, position, context) => { provideCompletionItems: (model, position, context) => {
if (context.triggerCharacter === '$') {
const range = { const range = {
startLineNumber: position.lineNumber, startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber, endLineNumber: position.lineNumber,
startColumn: position.column - 1, startColumn: position.column,
endColumn: position.column, endColumn: position.column,
}; };
// Simple check if this was triggered by pressing `$`
if (context.triggerCharacter === '$') {
range.startColumn = position.column - 1;
return { return {
suggestions: getCompletionItems('$', getSuggestions(), range), suggestions: getCompletionItems('$', getSuggestions(), range),
}; };
} }
// find out if we are completing a property in the 'dependencies' object. // Find the replacement region
const lineText = model.getValueInRange({ const currentLine = model.getValueInRange({
startLineNumber: position.lineNumber, startLineNumber: position.lineNumber,
startColumn: 1, startColumn: 1,
endLineNumber: position.lineNumber, endLineNumber: position.lineNumber,
endColumn: position.column, endColumn: position.column,
}); });
const idx = lineText.lastIndexOf('$'); const { index, prefix } = findInsertIndex(currentLine);
if (idx >= 0) { range.startColumn = index + 1;
const range = {
startLineNumber: position.lineNumber, const suggestions = getCompletionItems(prefix, getSuggestions(), range);
endLineNumber: position.lineNumber, if (suggestions.length) {
startColumn: idx, // the last $ we found // NOTE, this will replace any language provided suggestions
endColumn: position.column, return { suggestions };
};
return {
suggestions: getCompletionItems(lineText.substr(idx), getSuggestions(), range),
};
} }
// Empty line that asked for suggestion // Default language suggestions
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);
return undefined; return undefined;
}, },
}); });

View File

@@ -12,8 +12,8 @@ const setup = (spy?: any, propOverrides?: object) => {
} }
}, },
valueMappings: [ valueMappings: [
{ id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' }, { id: 1, type: MappingType.ValueToText, value: '20', text: 'Ok' },
{ id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' }, { 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"]'); const remove = wrapper.find('button[aria-label="ValueMappingsEditor remove button"]');
remove.at(0).simulate('click'); remove.at(0).simulate('click');
expect(onChangeSpy).toBeCalledWith([ expect(onChangeSpy).toBeCalledWith([{ id: 2, type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' }]);
{ id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
]);
}); });
it('should remove mapping at index 1', () => { 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"]'); const remove = wrapper.find('button[aria-label="ValueMappingsEditor remove button"]');
remove.at(1).simulate('click'); remove.at(1).simulate('click');
expect(onChangeSpy).toBeCalledWith([ expect(onChangeSpy).toBeCalledWith([{ id: 1, type: MappingType.ValueToText, value: '20', text: 'Ok' }]);
{ id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' },
]);
}); });
}); });
@@ -62,9 +58,9 @@ describe('Next id to add', () => {
add.at(0).simulate('click'); add.at(0).simulate('click');
expect(onChangeSpy).toBeCalledWith([ expect(onChangeSpy).toBeCalledWith([
{ id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' }, { id: 1, type: MappingType.ValueToText, value: '20', text: 'Ok' },
{ id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' }, { id: 2, type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
{ id: 3, operator: '', type: MappingType.ValueToText, from: '', to: '', text: '' }, { id: 3, type: MappingType.ValueToText, from: '', to: '', text: '' },
]); ]);
}); });
@@ -73,8 +69,6 @@ describe('Next id to add', () => {
const wrapper = setup(onChangeSpy, { valueMappings: [] }); const wrapper = setup(onChangeSpy, { valueMappings: [] });
const add = wrapper.find('*[aria-label="ValueMappingsEditor add mapping button"]'); const add = wrapper.find('*[aria-label="ValueMappingsEditor add mapping button"]');
add.at(0).simulate('click'); add.at(0).simulate('click');
expect(onChangeSpy).toBeCalledWith([ expect(onChangeSpy).toBeCalledWith([{ id: 0, type: MappingType.ValueToText, from: '', to: '', text: '' }]);
{ id: 0, operator: '', type: MappingType.ValueToText, from: '', to: '', text: '' },
]);
}); });
}); });

View File

@@ -15,7 +15,6 @@ export const ValueMappingsEditor: React.FC<Props> = ({ valueMappings, onChange,
type: MappingType.ValueToText, type: MappingType.ValueToText,
from: '', from: '',
to: '', to: '',
operator: '',
text: '', text: '',
}; };
const id = update && update.length > 0 ? Math.max(...update.map(v => v.id)) + 1 : 0; const id = update && update.length > 0 ? Math.max(...update.map(v => v.id)) + 1 : 0;

View File

@@ -155,6 +155,7 @@ export const getStandardFieldConfigs = () => {
id: 'mappings', id: 'mappings',
path: 'mappings', path: 'mappings',
name: 'Value mappings', name: 'Value mappings',
description: 'Modify the display text based on input value',
editor: standardEditorsRegistry.get('mappings').editor as any, editor: standardEditorsRegistry.get('mappings').editor as any,
override: standardEditorsRegistry.get('mappings').editor as any, override: standardEditorsRegistry.get('mappings').editor as any,

View File

@@ -1,6 +1,6 @@
{ {
"name": "@jaegertracing/jaeger-ui-components", "name": "@jaegertracing/jaeger-ui-components",
"version": "7.1.0-pre.0", "version": "7.1.0-beta.2",
"main": "src/index.ts", "main": "src/index.ts",
"types": "src/index.ts", "types": "src/index.ts",
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -14,7 +14,7 @@
"typescript": "3.9.3" "typescript": "3.9.3"
}, },
"dependencies": { "dependencies": {
"@grafana/data": "7.1.0-pre.0", "@grafana/data": "7.1.0-beta.2",
"@types/classnames": "^2.2.7", "@types/classnames": "^2.2.7",
"@types/deep-freeze": "^0.1.1", "@types/deep-freeze": "^0.1.1",
"@types/hoist-non-react-statics": "^3.3.1", "@types/hoist-non-react-statics": "^3.3.1",

View File

@@ -16,4 +16,6 @@ type Licensing interface {
LicenseURL(user *SignedInUser) string LicenseURL(user *SignedInUser) string
StateInfo() string StateInfo() string
TokenRaw() string
} }

View File

@@ -100,7 +100,11 @@ func (m *manager) Register(pluginID string, factory PluginFactoryFunc) error {
} }
if m.License.HasLicense() { 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) env := pluginSettings.ToEnv("GF_PLUGIN", hostEnv)

View File

@@ -251,6 +251,7 @@ func TestManager(t *testing.T) {
t.Run("Plugin registration scenario when Grafana is licensed", func(t *testing.T) { t.Run("Plugin registration scenario when Grafana is licensed", func(t *testing.T) {
ctx.license.edition = "Enterprise" ctx.license.edition = "Enterprise"
ctx.license.hasLicense = true ctx.license.hasLicense = true
ctx.license.tokenRaw = "testtoken"
ctx.cfg.BuildVersion = "7.0.0" ctx.cfg.BuildVersion = "7.0.0"
ctx.cfg.EnterpriseLicensePath = "/license.txt" ctx.cfg.EnterpriseLicensePath = "/license.txt"
@@ -258,8 +259,8 @@ func TestManager(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
t.Run("Should provide expected host environment variables", func(t *testing.T) { t.Run("Should provide expected host environment variables", func(t *testing.T) {
require.Len(t, ctx.env, 3) require.Len(t, ctx.env, 4)
require.EqualValues(t, []string{"GF_VERSION=7.0.0", "GF_EDITION=Enterprise", "GF_ENTERPRISE_LICENSE_PATH=/license.txt"}, ctx.env) 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 { type testLicensingService struct {
edition string edition string
hasLicense bool hasLicense bool
tokenRaw string
} }
func (t *testLicensingService) HasLicense() bool { func (t *testLicensingService) HasLicense() bool {
@@ -408,3 +410,7 @@ func (t *testLicensingService) LicenseURL(user *models.SignedInUser) string {
func (t *testLicensingService) HasValidLicense() bool { func (t *testLicensingService) HasValidLicense() bool {
return false return false
} }
func (t *testLicensingService) TokenRaw() string {
return t.tokenRaw
}

View File

@@ -3,6 +3,7 @@ package plugins
import ( import (
"net/url" "net/url"
"path" "path"
"path/filepath"
"strings" "strings"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@@ -63,7 +64,9 @@ func (fp *FrontendPluginBase) handleModuleDefaults() {
// Previously there was an assumption that the plugin directory // Previously there was an assumption that the plugin directory
// should be public/app/plugins/<plugin type>/<plugin id> // 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 // 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.Module = path.Join("app/plugins", fp.Type, currentDir, "module")
fp.BaseUrl = path.Join("public/app/plugins", fp.Type, currentDir) fp.BaseUrl = path.Join("public/app/plugins", fp.Type, currentDir)
} }

View File

@@ -56,3 +56,7 @@ func (l *OSSLicensingService) Init() error {
func (*OSSLicensingService) HasValidLicense() bool { func (*OSSLicensingService) HasValidLicense() bool {
return false return false
} }
func (*OSSLicensingService) TokenRaw() string {
return ""
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "@grafana-plugins/input-datasource", "name": "@grafana-plugins/input-datasource",
"version": "7.1.0-pre.0", "version": "7.1.0-beta.2",
"description": "Input Datasource", "description": "Input Datasource",
"private": true, "private": true,
"repository": { "repository": {
@@ -16,9 +16,9 @@
"author": "Grafana Labs", "author": "Grafana Labs",
"license": "Apache-2.0", "license": "Apache-2.0",
"devDependencies": { "devDependencies": {
"@grafana/data": "7.1.0-pre.0", "@grafana/data": "7.1.0-beta.2",
"@grafana/toolkit": "7.1.0-pre.0", "@grafana/toolkit": "7.1.0-beta.2",
"@grafana/ui": "7.1.0-pre.0" "@grafana/ui": "7.1.0-beta.2"
}, },
"volta": { "volta": {
"node": "12.16.2" "node": "12.16.2"

View File

@@ -45,7 +45,7 @@ export const ForgottenPassword: FC = () => {
<Legend>Reset password</Legend> <Legend>Reset password</Legend>
<Field <Field
label="User" 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} invalid={!!errors.userOrEmail}
error={errors?.userOrEmail?.message} error={errors?.userOrEmail?.message}
> >

View File

@@ -49,10 +49,11 @@ export const getLoginStyles = (theme: GrafanaTheme) => {
min-height: 100vh; min-height: 100vh;
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
background-color: ${theme.palette.black};
min-width: 100%; min-width: 100%;
margin-left: 0; margin-left: 0;
background-color: $black;
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
`, `,
@@ -76,7 +77,7 @@ export const getLoginStyles = (theme: GrafanaTheme) => {
text-align: center; text-align: center;
`, `,
mainTitle: css` mainTitle: css`
font-size: '32px'; font-size: 32px;
`, `,
subTitle: css` subTitle: css`
font-size: ${theme.typography.size.md}; font-size: ${theme.typography.size.md};

View File

@@ -95,7 +95,7 @@ export class PanelHeader extends Component<Props, State> {
return ( return (
<div className="panel-loading" onClick={this.onCancelQuery}> <div className="panel-loading" onClick={this.onCancelQuery}>
<Tooltip content="Cancel query"> <Tooltip content="Cancel query">
<Icon className="panel-loading__spinner spin-counter-clock" name="sync" /> <Icon className="panel-loading__spinner spin-clockwise" name="sync" />
</Tooltip> </Tooltip>
</div> </div>
); );

View File

@@ -23,7 +23,7 @@ export const NoDataSourceCallToAction = () => {
); );
const ctaElement = ( const ctaElement = (
<LinkButton size="lg" href="/datasources/new" icon="database"> <LinkButton size="lg" href="datasources/new" icon="database">
Add data source Add data source
</LinkButton> </LinkButton>
); );

View File

@@ -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();
});
});

View File

@@ -1,45 +1,105 @@
// Libraries // Libraries
import React, { memo } from 'react'; import React, { PureComponent } from 'react';
// Types // Types
import { AbsoluteTimeRange, QueryEditorProps } from '@grafana/data'; import { AbsoluteTimeRange, QueryEditorProps, PanelData } from '@grafana/data';
import { InlineFormLabel } from '@grafana/ui';
import { LokiDatasource } from '../datasource'; import { LokiDatasource } from '../datasource';
import { LokiQuery } from '../types'; import { LokiQuery } from '../types';
import { LokiQueryField } from './LokiQueryField'; import { LokiQueryField } from './LokiQueryField';
type Props = QueryEditorProps<LokiDatasource, LokiQuery>; type Props = QueryEditorProps<LokiDatasource, LokiQuery>;
export const LokiQueryEditor = memo(function LokiQueryEditor(props: Props) { interface State {
const { query, data, datasource, onChange, onRunQuery } = props; legendFormat: string;
}
let absolute: AbsoluteTimeRange; export class LokiQueryEditor extends PureComponent<Props, State> {
// Query target to be modified and used for queries
query: LokiQuery;
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,
};
}
calcAbsoluteRange = (data: PanelData): AbsoluteTimeRange => {
if (data && data.request) { if (data && data.request) {
const { range } = data.request; const { range } = data.request;
absolute = { return {
from: range.from.valueOf(), from: range.from.valueOf(),
to: range.to.valueOf(), to: range.to.valueOf(),
}; };
} else { }
absolute = {
return {
from: Date.now() - 10000, from: Date.now() - 10000,
to: Date.now(), 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 ( return (
<div> <div>
<LokiQueryField <LokiQueryField
datasource={datasource} datasource={datasource}
query={query} query={query}
onChange={onChange} onChange={this.onFieldChange}
onRunQuery={onRunQuery} onRunQuery={this.onRunQuery}
history={[]} history={[]}
data={data} data={data}
absoluteRange={absolute} 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>
</div>
); );
}); }
}
export default LokiQueryEditor; export default LokiQueryEditor;

View File

@@ -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>
`;

View File

@@ -143,8 +143,10 @@ function createUid(ts: string, labelsString: string, line: string): string {
} }
function lokiMatrixToTimeSeries(matrixResult: LokiMatrixResult, options: TransformerOptions): TimeSeries { function lokiMatrixToTimeSeries(matrixResult: LokiMatrixResult, options: TransformerOptions): TimeSeries {
const name = createMetricLabel(matrixResult.metric, options);
return { return {
target: createMetricLabel(matrixResult.metric, options), target: name,
title: name,
datapoints: lokiPointsToTimeseriesPoints(matrixResult.values, options), datapoints: lokiPointsToTimeseriesPoints(matrixResult.values, options),
tags: matrixResult.metric, tags: matrixResult.metric,
meta: options.meta, meta: options.meta,

View File

@@ -35,15 +35,15 @@
} }
} }
@keyframes spin-counter-clock { @keyframes spin-clockwise {
0% { 0% {
transform: rotate(359deg); transform: rotate(0deg) scaleX(-1); // scaleX flips the `sync` icon so arrows point the correct way
} }
100% { 100% {
transform: rotate(0deg); transform: rotate(359deg) scaleX(-1);
} }
} }
.spin-counter-clock { .spin-clockwise {
animation: spin-counter-clock 3s infinite linear; animation: spin-clockwise 3s infinite linear;
} }