From e67d3df14c5ad323a53652abeb7121e1904688c1 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Sat, 21 Jul 2018 23:31:47 +0200 Subject: [PATCH 001/139] Fix array display from url --- .../specs/variable_srv_init.test.ts | 38 +++++++++++++++---- .../app/features/templating/variable_srv.ts | 4 +- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/public/app/features/templating/specs/variable_srv_init.test.ts b/public/app/features/templating/specs/variable_srv_init.test.ts index 978ad824d69..ab8b20deca2 100644 --- a/public/app/features/templating/specs/variable_srv_init.test.ts +++ b/public/app/features/templating/specs/variable_srv_init.test.ts @@ -76,8 +76,8 @@ describe('VariableSrv init', function(this: any) { { name: 'apps', type: type, - current: { text: 'test', value: 'test' }, - options: [{ text: 'test', value: 'test' }], + current: { text: 'Test', value: 'test' }, + options: [{ text: 'Test', value: 'test' }], }, ]; scenario.urlParams['var-apps'] = 'new'; @@ -160,11 +160,11 @@ describe('VariableSrv init', function(this: any) { name: 'apps', type: 'query', multi: true, - current: { text: 'val1', value: 'val1' }, + current: { text: 'Val1', value: 'val1' }, options: [ - { text: 'val1', value: 'val1' }, - { text: 'val2', value: 'val2' }, - { text: 'val3', value: 'val3', selected: true }, + { text: 'Val1', value: 'val1' }, + { text: 'Val2', value: 'val2' }, + { text: 'Val3', value: 'val3', selected: true }, ], }, ]; @@ -176,7 +176,7 @@ describe('VariableSrv init', function(this: any) { expect(variable.current.value.length).toBe(2); expect(variable.current.value[0]).toBe('val2'); expect(variable.current.value[1]).toBe('val1'); - expect(variable.current.text).toBe('val2 + val1'); + expect(variable.current.text).toBe('Val2 + Val1'); expect(variable.options[0].selected).toBe(true); expect(variable.options[1].selected).toBe(true); }); @@ -187,6 +187,30 @@ describe('VariableSrv init', function(this: any) { }); }); + describeInitScenario( + 'when template variable is present in url multiple times and variables have no text', + scenario => { + scenario.setup(() => { + scenario.variables = [ + { + name: 'apps', + type: 'query', + multi: true, + }, + ]; + scenario.urlParams['var-apps'] = ['val1', 'val2']; + }); + + it('should display concatenated values in text', () => { + const variable = ctx.variableSrv.variables[0]; + expect(variable.current.value.length).toBe(2); + expect(variable.current.value[0]).toBe('val1'); + expect(variable.current.value[1]).toBe('val2'); + expect(variable.current.text).toBe('val1 + val2'); + }); + } + ); + describeInitScenario('when template variable is present in url multiple times using key/values', scenario => { scenario.setup(() => { scenario.variables = [ diff --git a/public/app/features/templating/variable_srv.ts b/public/app/features/templating/variable_srv.ts index 22f8a909440..0530135a5ef 100644 --- a/public/app/features/templating/variable_srv.ts +++ b/public/app/features/templating/variable_srv.ts @@ -236,8 +236,10 @@ export class VariableSrv { setOptionAsCurrent(variable, option) { variable.current = _.cloneDeep(option); - if (_.isArray(variable.current.text)) { + if (_.isArray(variable.current.text) && variable.current.text.length > 0) { variable.current.text = variable.current.text.join(' + '); + } else if (_.isArray(variable.current.value) && variable.current.value[0] !== '$__all') { + variable.current.text = variable.current.value.join(' + '); } this.selectOptionsForCurrentValue(variable); From 4ed0a3d29a8a783dfc9d816e8446d1a5182712cc Mon Sep 17 00:00:00 2001 From: Victor Cinaglia Date: Sun, 18 Nov 2018 12:22:16 -0500 Subject: [PATCH 002/139] Retain decimal precision when exporting CSV Using `Number.prototype.toLocaleString()` has the unexpected behavior of truncating anything exceeding 3 decimal digits on floats. Additionally, it introduces inconsistencies (comma vs period separators) which could make processing the output CSV harder than it could be. The proposed solution here is to simply let numbers be cast automatically via string concatenation. Fixes #13929 --- public/app/core/specs/file_export.test.ts | 2 ++ public/app/core/utils/file_export.ts | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/app/core/specs/file_export.test.ts b/public/app/core/specs/file_export.test.ts index 52ec4ccea19..98f5a5be742 100644 --- a/public/app/core/specs/file_export.test.ts +++ b/public/app/core/specs/file_export.test.ts @@ -73,6 +73,7 @@ describe('file_export', () => { ], rows: [ [123, 'some_string', 1.234, true], + [1000, 'some_string', 1.234567891, true], [0o765, 'some string with " in the middle', 1e-2, false], [0o765, 'some string with "" in the middle', 1e-2, false], [0o765, 'some string with """ in the middle', 1e-2, false], @@ -89,6 +90,7 @@ describe('file_export', () => { const expectedText = '"integer_value";"string_value";"float_value";"boolean_value"\r\n' + '123;"some_string";1.234;true\r\n' + + '1000;"some_string";1.234567891;true\r\n' + '501;"some string with "" in the middle";0.01;false\r\n' + '501;"some string with """" in the middle";0.01;false\r\n' + '501;"some string with """""" in the middle";0.01;false\r\n' + diff --git a/public/app/core/utils/file_export.ts b/public/app/core/utils/file_export.ts index 4fbdea0f953..1f999da72a5 100644 --- a/public/app/core/utils/file_export.ts +++ b/public/app/core/utils/file_export.ts @@ -41,10 +41,8 @@ function formatSpecialHeader(useExcelHeader) { function formatRow(row, addEndRowDelimiter = true) { let text = ''; for (let i = 0; i < row.length; i += 1) { - if (isBoolean(row[i]) || isNullOrUndefined(row[i])) { + if (isBoolean(row[i]) || isNumber(row[i]) || isNullOrUndefined(row[i])) { text += row[i]; - } else if (isNumber(row[i])) { - text += row[i].toLocaleString(); } else { text += `${QUOTE}${csvEscaped(htmlUnescaped(htmlDecoded(row[i])))}${QUOTE}`; } From b2ef85702062d2c4b1b9be3b397f851ef476bc2e Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Wed, 5 Dec 2018 12:59:56 +0100 Subject: [PATCH 003/139] README.md: Fix small typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 269c525e983..63375f5e245 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ Choose this option to build on platforms other than linux/amd64 and/or not have The resulting image will be tagged as `grafana/grafana:dev` -Notice: If you are using Docker for MacOS, be sure to let limit of Memory bigger than 2 GiB (at docker -> Perferences -> Advanced), otherwize you may faild at `grunt build` +Notice: If you are using Docker for MacOS, be sure to let limit of Memory bigger than 2 GiB (at docker -> Preferences -> Advanced), otherwize you may faild at `grunt build` ### Dev config From 0f7484333262840e3f655adb1806c43dd97e25ea Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Wed, 5 Dec 2018 13:02:58 +0100 Subject: [PATCH 004/139] public/app/core/*: Fix some misspell issues --- .../core/components/code_editor/code_editor.ts | 4 ++-- .../core/components/json_explorer/helpers.ts | 2 +- .../components/json_explorer/json_explorer.ts | 2 +- .../sidemenu/BottomNavLinks.test.tsx | 2 +- .../__snapshots__/BottomNavLinks.test.tsx.snap | 2 +- public/app/core/services/backend_srv.ts | 4 ++-- public/app/core/services/timer.ts | 2 +- public/app/core/table_model.ts | 2 +- public/app/core/utils/kbn.ts | 18 +++++++++--------- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/public/app/core/components/code_editor/code_editor.ts b/public/app/core/components/code_editor/code_editor.ts index 50ff55f3083..a2e45d70074 100644 --- a/public/app/core/components/code_editor/code_editor.ts +++ b/public/app/core/components/code_editor/code_editor.ts @@ -50,7 +50,7 @@ const DEFAULT_THEME_LIGHT = 'ace/theme/textmate'; const DEFAULT_MODE = 'text'; const DEFAULT_MAX_LINES = 10; const DEFAULT_TAB_SIZE = 2; -const DEFAULT_BEHAVIOURS = true; +const DEFAULT_BEHAVIORS = true; const DEFAULT_SNIPPETS = true; const editorTemplate = `
`; @@ -61,7 +61,7 @@ function link(scope, elem, attrs) { const maxLines = attrs.maxLines || DEFAULT_MAX_LINES; const showGutter = attrs.showGutter !== undefined; const tabSize = attrs.tabSize || DEFAULT_TAB_SIZE; - const behavioursEnabled = attrs.behavioursEnabled ? attrs.behavioursEnabled === 'true' : DEFAULT_BEHAVIOURS; + const behavioursEnabled = attrs.behavioursEnabled ? attrs.behavioursEnabled === 'true' : DEFAULT_BEHAVIORS; const snippetsEnabled = attrs.snippetsEnabled ? attrs.snippetsEnabled === 'true' : DEFAULT_SNIPPETS; // Initialize editor diff --git a/public/app/core/components/json_explorer/helpers.ts b/public/app/core/components/json_explorer/helpers.ts index c039d818281..65e7502a810 100644 --- a/public/app/core/components/json_explorer/helpers.ts +++ b/public/app/core/components/json_explorer/helpers.ts @@ -1,5 +1,5 @@ // Based on work https://github.com/mohsen1/json-formatter-js -// Licence MIT, Copyright (c) 2015 Mohsen Azimi +// License MIT, Copyright (c) 2015 Mohsen Azimi /* * Escapes `"` characters from string diff --git a/public/app/core/components/json_explorer/json_explorer.ts b/public/app/core/components/json_explorer/json_explorer.ts index 9a344d3195b..228154f9884 100644 --- a/public/app/core/components/json_explorer/json_explorer.ts +++ b/public/app/core/components/json_explorer/json_explorer.ts @@ -1,5 +1,5 @@ // Based on work https://github.com/mohsen1/json-formatter-js -// Licence MIT, Copyright (c) 2015 Mohsen Azimi +// License MIT, Copyright (c) 2015 Mohsen Azimi import { isObject, getObjectName, getType, getValuePreview, cssClass, createElement } from './helpers'; diff --git a/public/app/core/components/sidemenu/BottomNavLinks.test.tsx b/public/app/core/components/sidemenu/BottomNavLinks.test.tsx index 8eaed4ca264..b52e5311dc5 100644 --- a/public/app/core/components/sidemenu/BottomNavLinks.test.tsx +++ b/public/app/core/components/sidemenu/BottomNavLinks.test.tsx @@ -36,7 +36,7 @@ describe('Render', () => { expect(wrapper).toMatchSnapshot(); }); - it('should render organisation switcher', () => { + it('should render organization switcher', () => { const wrapper = setup({ link: { showOrgSwitcher: true, diff --git a/public/app/core/components/sidemenu/__snapshots__/BottomNavLinks.test.tsx.snap b/public/app/core/components/sidemenu/__snapshots__/BottomNavLinks.test.tsx.snap index f3181b617ad..ae8c9c753aa 100644 --- a/public/app/core/components/sidemenu/__snapshots__/BottomNavLinks.test.tsx.snap +++ b/public/app/core/components/sidemenu/__snapshots__/BottomNavLinks.test.tsx.snap @@ -73,7 +73,7 @@ exports[`Render should render component 1`] = ` `; -exports[`Render should render organisation switcher 1`] = ` +exports[`Render should render organization switcher 1`] = `
diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts index 144567efeb9..854169ad4b0 100644 --- a/public/app/core/services/backend_srv.ts +++ b/public/app/core/services/backend_srv.ts @@ -5,7 +5,7 @@ import { DashboardModel } from 'app/features/dashboard/dashboard_model'; export class BackendSrv { private inFlightRequests = {}; - private HTTP_REQUEST_CANCELLED = -1; + private HTTP_REQUEST_CANCELED = -1; private noBackendCache: boolean; /** @ngInject */ @@ -178,7 +178,7 @@ export class BackendSrv { return response; }) .catch(err => { - if (err.status === this.HTTP_REQUEST_CANCELLED) { + if (err.status === this.HTTP_REQUEST_CANCELED) { throw { err, cancelled: true }; } diff --git a/public/app/core/services/timer.ts b/public/app/core/services/timer.ts index 8052b3f2e2c..8234b6288d4 100644 --- a/public/app/core/services/timer.ts +++ b/public/app/core/services/timer.ts @@ -2,7 +2,7 @@ import _ from 'lodash'; import coreModule from 'app/core/core_module'; // This service really just tracks a list of $timeout promises to give us a -// method for cancelling them all when we need to +// method for canceling them all when we need to export class Timer { timers = []; diff --git a/public/app/core/table_model.ts b/public/app/core/table_model.ts index 91a7cd0c1fb..20f165e4fee 100644 --- a/public/app/core/table_model.ts +++ b/public/app/core/table_model.ts @@ -40,7 +40,7 @@ export default class TableModel { this.rows.sort((a, b) => { a = a[options.col]; b = b[options.col]; - // Sort null or undefined seperately from comparable values + // Sort null or undefined separately from comparable values return +(a == null) - +(b == null) || +(a > b) || -(a < b); }); diff --git a/public/app/core/utils/kbn.ts b/public/app/core/utils/kbn.ts index e0b98cb803c..81e37d55666 100644 --- a/public/app/core/utils/kbn.ts +++ b/public/app/core/utils/kbn.ts @@ -1084,7 +1084,7 @@ kbn.getUnitFormats = () => { { text: 'Watt (W)', value: 'watt' }, { text: 'Kilowatt (kW)', value: 'kwatt' }, { text: 'Milliwatt (mW)', value: 'mwatt' }, - { text: 'Watt per square metre (W/m²)', value: 'Wm2' }, + { text: 'Watt per square meter (W/m²)', value: 'Wm2' }, { text: 'Volt-ampere (VA)', value: 'voltamp' }, { text: 'Kilovolt-ampere (kVA)', value: 'kvoltamp' }, { text: 'Volt-ampere reactive (var)', value: 'voltampreact' }, @@ -1181,14 +1181,14 @@ kbn.getUnitFormats = () => { submenu: [ { text: 'parts-per-million (ppm)', value: 'ppm' }, { text: 'parts-per-billion (ppb)', value: 'conppb' }, - { text: 'nanogram per cubic metre (ng/m³)', value: 'conngm3' }, - { text: 'nanogram per normal cubic metre (ng/Nm³)', value: 'conngNm3' }, - { text: 'microgram per cubic metre (μg/m³)', value: 'conμgm3' }, - { text: 'microgram per normal cubic metre (μg/Nm³)', value: 'conμgNm3' }, - { text: 'milligram per cubic metre (mg/m³)', value: 'conmgm3' }, - { text: 'milligram per normal cubic metre (mg/Nm³)', value: 'conmgNm3' }, - { text: 'gram per cubic metre (g/m³)', value: 'congm3' }, - { text: 'gram per normal cubic metre (g/Nm³)', value: 'congNm3' }, + { text: 'nanogram per cubic meter (ng/m³)', value: 'conngm3' }, + { text: 'nanogram per normal cubic meter (ng/Nm³)', value: 'conngNm3' }, + { text: 'microgram per cubic meter (μg/m³)', value: 'conμgm3' }, + { text: 'microgram per normal cubic meter (μg/Nm³)', value: 'conμgNm3' }, + { text: 'milligram per cubic meter (mg/m³)', value: 'conmgm3' }, + { text: 'milligram per normal cubic meter (mg/Nm³)', value: 'conmgNm3' }, + { text: 'gram per cubic meter (g/m³)', value: 'congm3' }, + { text: 'gram per normal cubic meter (g/Nm³)', value: 'congNm3' }, ], }, ]; From ab9f65a4cfee3ebdfcd69690bb99f1622f46f8c0 Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Wed, 5 Dec 2018 13:08:00 +0100 Subject: [PATCH 005/139] public/app/features/*: Fix some misspell issues --- public/app/features/dashboard/state/actions.ts | 2 +- public/app/features/explore/Explore.tsx | 2 +- public/app/features/explore/PlaceholdersBuffer.ts | 2 +- public/app/features/folders/state/actions.ts | 2 +- public/app/features/org/state/actions.ts | 12 ++++++------ public/app/features/panel/metrics_panel_ctrl.ts | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/public/app/features/dashboard/state/actions.ts b/public/app/features/dashboard/state/actions.ts index bc35ff31ff0..4dcf0a925b7 100644 --- a/public/app/features/dashboard/state/actions.ts +++ b/public/app/features/dashboard/state/actions.ts @@ -67,7 +67,7 @@ export function updateDashboardPermission( const updated = toUpdateItem(item); - // if this is the item we want to update, update it's permisssion + // if this is the item we want to update, update it's permission if (itemToUpdate === item) { updated.permission = level; } diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index d2588a8ec0b..1ec27b1458d 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -481,7 +481,7 @@ export class Explore extends React.PureComponent { } else { // Modify query only at index nextQueries = initialQueries.map((query, i) => { - // Synchronise all queries with local query cache to ensure consistency + // Synchronize all queries with local query cache to ensure consistency // TODO still needed? return i === index ? { diff --git a/public/app/features/explore/PlaceholdersBuffer.ts b/public/app/features/explore/PlaceholdersBuffer.ts index 9a0db18ef04..461331daab4 100644 --- a/public/app/features/explore/PlaceholdersBuffer.ts +++ b/public/app/features/explore/PlaceholdersBuffer.ts @@ -88,7 +88,7 @@ export default class PlaceholdersBuffer { orders.push({ index: parts.length - 1, order }); textOffset += part.length + match.length; } - // Ensures string serialisation still works if no placeholders were parsed + // Ensures string serialization still works if no placeholders were parsed // and also accounts for the remainder of text with placeholders parts.push(text.slice(textOffset)); return { diff --git a/public/app/features/folders/state/actions.ts b/public/app/features/folders/state/actions.ts index cd02915e586..a7adc71e2d8 100644 --- a/public/app/features/folders/state/actions.ts +++ b/public/app/features/folders/state/actions.ts @@ -112,7 +112,7 @@ export function updateFolderPermission(itemToUpdate: DashboardAcl, level: Permis const updated = toUpdateItem(item); - // if this is the item we want to update, update it's permisssion + // if this is the item we want to update, update it's permission if (itemToUpdate === item) { updated.permission = level; } diff --git a/public/app/features/org/state/actions.ts b/public/app/features/org/state/actions.ts index aeec8297ea6..52793698a45 100644 --- a/public/app/features/org/state/actions.ts +++ b/public/app/features/org/state/actions.ts @@ -5,7 +5,7 @@ import { getBackendSrv } from 'app/core/services/backend_srv'; type ThunkResult = ThunkAction; export enum ActionTypes { - LoadOrganization = 'LOAD_ORGANISATION', + LoadOrganization = 'LOAD_ORGANIZATION', SetOrganizationName = 'SET_ORGANIZATION_NAME', } @@ -19,9 +19,9 @@ interface SetOrganizationNameAction { payload: string; } -const organisationLoaded = (organisation: Organization) => ({ +const organizationLoaded = (organization: Organization) => ({ type: ActionTypes.LoadOrganization, - payload: organisation, + payload: organization, }); export const setOrganizationName = (orgName: string) => ({ @@ -33,10 +33,10 @@ export type Action = LoadOrganizationAction | SetOrganizationNameAction; export function loadOrganization(): ThunkResult { return async dispatch => { - const organisationResponse = await getBackendSrv().get('/api/org'); - dispatch(organisationLoaded(organisationResponse)); + const organizationResponse = await getBackendSrv().get('/api/org'); + dispatch(organizationLoaded(organizationResponse)); - return organisationResponse; + return organizationResponse; }; } diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index e517c48bb59..1d9aff32489 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -99,7 +99,7 @@ class MetricsPanelCtrl extends PanelCtrl { .then(this.issueQueries.bind(this)) .then(this.handleQueryResult.bind(this)) .catch(err => { - // if cancelled keep loading set to true + // if canceled keep loading set to true if (err.cancelled) { console.log('Panel request cancelled', err); return; From 400db64db513a53d8048844020aa4955e46d41bc Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Wed, 5 Dec 2018 13:09:27 +0100 Subject: [PATCH 006/139] public/sass/*: Fix misspell issue --- public/sass/components/_panel_gettingstarted.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/sass/components/_panel_gettingstarted.scss b/public/sass/components/_panel_gettingstarted.scss index 1fb3eda1834..f46c4569589 100644 --- a/public/sass/components/_panel_gettingstarted.scss +++ b/public/sass/components/_panel_gettingstarted.scss @@ -1,4 +1,4 @@ -// Colours +// Colors $progress-color-dark: $panel-bg !default; $progress-color: $panel-bg !default; $progress-color-light: $panel-bg !default; From 37bb8840f097522349656ea98d65e9c526269e73 Mon Sep 17 00:00:00 2001 From: Mario Trangoni Date: Wed, 5 Dec 2018 13:13:29 +0100 Subject: [PATCH 007/139] public/app/plugins/*: Fix some misspell issues --- .../app/plugins/datasource/logging/result_transformer.test.ts | 4 ++-- public/app/plugins/panel/graph/graph.ts | 2 +- public/app/plugins/panel/graph/jquery.flot.events.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/datasource/logging/result_transformer.test.ts b/public/app/plugins/datasource/logging/result_transformer.test.ts index 182292261a8..c23a370ab81 100644 --- a/public/app/plugins/datasource/logging/result_transformer.test.ts +++ b/public/app/plugins/datasource/logging/result_transformer.test.ts @@ -35,7 +35,7 @@ describe('getLoglevel()', () => { }); describe('parseLabels()', () => { - it('returns no labels on emtpy labels string', () => { + it('returns no labels on empty labels string', () => { expect(parseLabels('')).toEqual({}); expect(parseLabels('{}')).toEqual({}); }); @@ -46,7 +46,7 @@ describe('parseLabels()', () => { }); describe('formatLabels()', () => { - it('returns no labels on emtpy label set', () => { + it('returns no labels on empty label set', () => { expect(formatLabels({})).toEqual(''); expect(formatLabels({}, 'foo')).toEqual('foo'); }); diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index ff248d68201..86a6fd1dfd2 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -737,7 +737,7 @@ class GraphElement { if (min && max && ticks) { const range = max - min; const secPerTick = range / ticks / 1000; - // Need have 10 milisecond margin on the day range + // Need have 10 millisecond margin on the day range // As sometimes last 24 hour dashboard evaluates to more than 86400000 const oneDay = 86400010; const oneYear = 31536000000; diff --git a/public/app/plugins/panel/graph/jquery.flot.events.ts b/public/app/plugins/panel/graph/jquery.flot.events.ts index ed2b2dab92a..a5d7f658ccf 100644 --- a/public/app/plugins/panel/graph/jquery.flot.events.ts +++ b/public/app/plugins/panel/graph/jquery.flot.events.ts @@ -54,7 +54,7 @@ export function createEditPopover(element, event, plot) { const eventManager = plot.getOptions().events.manager; if (eventManager.editorOpen) { // update marker element to attach to (needed in case of legend on the right - // when there is a double render pass and the inital marker element is removed) + // when there is a double render pass and the initial marker element is removed) markerElementToAttachTo = element; return; } From 6b9200a606748bbb6391fbbf0a5c8e8847ac2226 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Sun, 9 Dec 2018 18:44:59 +0100 Subject: [PATCH 008/139] Explore: Split logging query into selector and search - add second query field to logging datasource query component - second field manages search term, first field manages stream selectors - improved logging cheat sheet to get started --- public/app/features/explore/QueryField.tsx | 11 ++++- .../loki/components/LokiCheatSheet.tsx | 27 +++++++---- .../loki/components/LokiQueryField.tsx | 48 +++++++++++++++++-- .../plugins/datasource/loki/query_utils.ts | 1 + public/sass/components/_slate_editor.scss | 6 ++- 5 files changed, 79 insertions(+), 14 deletions(-) diff --git a/public/app/features/explore/QueryField.tsx b/public/app/features/explore/QueryField.tsx index d5cba981951..24b8b8f5b16 100644 --- a/public/app/features/explore/QueryField.tsx +++ b/public/app/features/explore/QueryField.tsx @@ -4,6 +4,7 @@ import ReactDOM from 'react-dom'; import { Change, Value } from 'slate'; import { Editor } from 'slate-react'; import Plain from 'slate-plain-serializer'; +import classnames from 'classnames'; import { CompletionItem, CompletionItemGroup, TypeaheadOutput } from 'app/types/explore'; @@ -30,6 +31,7 @@ function hasSuggestions(suggestions: CompletionItemGroup[]): boolean { export interface QueryFieldProps { additionalPlugins?: any[]; cleanText?: (text: string) => string; + disabled?: boolean; initialQuery: string | null; onBlur?: () => void; onFocus?: () => void; @@ -78,7 +80,7 @@ export class QueryField extends React.PureComponent p); + this.plugins = [ClearPlugin(), NewlinePlugin(), ...(props.additionalPlugins || [])].filter(p => p); this.state = { suggestions: [], @@ -440,12 +442,17 @@ export class QueryField extends React.PureComponent +
{this.renderMenu()} ( {CHEAT_SHEET_ITEMS.map(item => (
{item.title}
-
props.onClickExample({ refId: '1', expr: item.expression })} - > - {item.expression} -
+ {item.expression && ( +
props.onClickExample({ refId: '1', expr: item.expression })} + > + {item.expression} +
+ )}
{item.label}
))} diff --git a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx index 005706bb8d1..137033bf6d7 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx @@ -12,6 +12,7 @@ import BracesPlugin from 'app/features/explore/slate-plugins/braces'; import RunnerPlugin from 'app/features/explore/slate-plugins/runner'; import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField'; import { DataQuery } from 'app/types'; +import { parseQuery, formatQuery } from '../query_utils'; const PRISM_SYNTAX = 'promql'; @@ -67,7 +68,10 @@ interface LokiQueryFieldState { class LokiQueryField extends React.PureComponent { plugins: any[]; + pluginsSearch: any[]; languageProvider: any; + modifiedSearch: string; + modifiedQuery: string; constructor(props: LokiQueryFieldProps, context) { super(props, context); @@ -85,6 +89,8 @@ class LokiQueryField extends React.PureComponent { + const firstModified = this.modifiedQuery === undefined; + this.modifiedQuery = value; // Send text change to parent const { initialQuery, onQueryChange } = this.props; if (onQueryChange) { + const search = this.modifiedSearch || parseQuery(initialQuery.expr).regexp; + const expr = formatQuery(value, search); const query = { ...initialQuery, - expr: value, + expr, + }; + onQueryChange(query, override); + } + // Enable the search field if we have a selector query + if (firstModified) { + this.forceUpdate(); + } + }; + + onChangeSearch = (value: string, override?: boolean) => { + this.modifiedSearch = value; + // Send text change to parent + const { initialQuery, onQueryChange } = this.props; + if (onQueryChange) { + const selector = this.modifiedQuery || parseQuery(initialQuery.expr).query; + const expr = formatQuery(selector, value); + const query = { + ...initialQuery, + expr, }; onQueryChange(query, override); } @@ -190,6 +219,9 @@ class LokiQueryField extends React.PureComponent @@ -204,11 +236,11 @@ class LokiQueryField extends React.PureComponent @@ -224,6 +256,16 @@ class LokiQueryField extends React.PureComponent ) : null}
+
+ +
); } diff --git a/public/app/plugins/datasource/loki/query_utils.ts b/public/app/plugins/datasource/loki/query_utils.ts index 795d1e2ceeb..4f246ea8e28 100644 --- a/public/app/plugins/datasource/loki/query_utils.ts +++ b/public/app/plugins/datasource/loki/query_utils.ts @@ -1,5 +1,6 @@ const selectorRegexp = /(?:^|\s){[^{]*}/g; export function parseQuery(input: string) { + input = input || ''; const match = input.match(selectorRegexp); let query = ''; let regexp = input; diff --git a/public/sass/components/_slate_editor.scss b/public/sass/components/_slate_editor.scss index 25b20f180ef..714b8c487c2 100644 --- a/public/sass/components/_slate_editor.scss +++ b/public/sass/components/_slate_editor.scss @@ -5,7 +5,7 @@ word-break: break-word; } -.slate-query-field-wrapper { +.slate-query-field__wrapper { position: relative; display: inline-block; padding: 6px 7px 4px; @@ -20,6 +20,10 @@ transition: all 0.3s; } +.slate-query-field__wrapper--disabled { + background-color: inherit; +} + .slate-typeahead { .typeahead { position: absolute; From 9293ff06cd9ac8a984a3184b7e0efa0f09ecd2b7 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Sun, 9 Dec 2018 18:53:03 +0100 Subject: [PATCH 009/139] Enable search also after editing --- .../app/plugins/datasource/loki/components/LokiQueryField.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx index 137033bf6d7..b63cdc3cece 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx @@ -140,7 +140,7 @@ class LokiQueryField extends React.PureComponent { - const firstModified = this.modifiedQuery === undefined; + const enableSearchField = !this.modifiedQuery && value; this.modifiedQuery = value; // Send text change to parent const { initialQuery, onQueryChange } = this.props; @@ -154,7 +154,7 @@ class LokiQueryField extends React.PureComponent Date: Sun, 9 Dec 2018 14:39:25 -0800 Subject: [PATCH 010/139] Fix search field styles --- .../plugins/datasource/loki/components/LokiQueryField.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx index b63cdc3cece..f5d42a2e8cf 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx @@ -16,6 +16,11 @@ import { parseQuery, formatQuery } from '../query_utils'; const PRISM_SYNTAX = 'promql'; +const SEARCH_FIELD_STYLES = { + width: '66%', + marginLeft: 3, +}; + export function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadText }: QueryFieldState): string { // Modify suggestion based on context switch (typeaheadContext) { @@ -256,7 +261,7 @@ class LokiQueryField extends React.PureComponent ) : null}
-
+
Date: Mon, 10 Dec 2018 16:25:02 -0500 Subject: [PATCH 011/139] snapshots: Add external_delete_url column --- pkg/services/sqlstore/migrations/dashboard_snapshot_mig.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/services/sqlstore/migrations/dashboard_snapshot_mig.go b/pkg/services/sqlstore/migrations/dashboard_snapshot_mig.go index b880497cd23..be0bc80134c 100644 --- a/pkg/services/sqlstore/migrations/dashboard_snapshot_mig.go +++ b/pkg/services/sqlstore/migrations/dashboard_snapshot_mig.go @@ -60,4 +60,8 @@ func addDashboardSnapshotMigrations(mg *Migrator) { {Name: "external_url", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "dashboard", Type: DB_MediumText, Nullable: false}, })) + + mg.AddMigration("Add column external_delete_url to dashboard_snapshots table", NewAddColumnMigration(snapshotV5, &Column{ + Name: "external_delete_url", Type: DB_NVarchar, Length: 255, Nullable: true, + })) } From 9d6da10e82ff254c8877f8ea2405934493b09541 Mon Sep 17 00:00:00 2001 From: Victor Cinaglia Date: Mon, 10 Dec 2018 16:36:32 -0500 Subject: [PATCH 012/139] snapshots: Move external snapshot creation to backend --- pkg/api/dashboard_snapshot.go | 82 +++++++++++++++++-- pkg/models/dashboard_snapshot.go | 22 +++-- pkg/services/sqlstore/dashboard_snapshot.go | 22 ++--- .../features/dashboard/share_snapshot_ctrl.ts | 33 +------- 4 files changed, 103 insertions(+), 56 deletions(-) diff --git a/pkg/api/dashboard_snapshot.go b/pkg/api/dashboard_snapshot.go index e4e9c9d040f..6c3ee7b69c6 100644 --- a/pkg/api/dashboard_snapshot.go +++ b/pkg/api/dashboard_snapshot.go @@ -1,10 +1,15 @@ package api import ( + "bytes" + "encoding/json" + "fmt" + "net/http" "time" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/metrics" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/guardian" @@ -12,6 +17,11 @@ import ( "github.com/grafana/grafana/pkg/util" ) +var client = &http.Client{ + Timeout: time.Second * 5, + Transport: &http.Transport{Proxy: http.ProxyFromEnvironment}, +} + func GetSharingOptions(c *m.ReqContext) { c.JSON(200, util.DynMap{ "externalSnapshotURL": setting.ExternalSnapshotUrl, @@ -20,26 +30,82 @@ func GetSharingOptions(c *m.ReqContext) { }) } +type CreateExternalSnapshotResponse struct { + Key string `json:"key"` + DeleteKey string `json:"deleteKey"` + Url string `json:"url"` + DeleteUrl string `json:"deleteUrl"` +} + +func createExternalDashboardSnapshot(cmd m.CreateDashboardSnapshotCommand) (*CreateExternalSnapshotResponse, error) { + var createSnapshotResponse CreateExternalSnapshotResponse + message := map[string]interface{}{ + "name": cmd.Name, + "expires": cmd.Expires, + "dashboard": cmd.Dashboard, + } + + messageBytes, err := simplejson.NewFromAny(message).Encode() + if err != nil { + return nil, err + } + + response, err := client.Post(setting.ExternalSnapshotUrl+"/api/snapshots", "application/json", bytes.NewBuffer(messageBytes)) + if response != nil { + defer response.Body.Close() + } + + if err != nil { + return nil, err + } + + if response.StatusCode != 200 { + return nil, fmt.Errorf("Create external snapshot response status code %d", response.StatusCode) + } + + if err := json.NewDecoder(response.Body).Decode(&createSnapshotResponse); err != nil { + return nil, err + } + + return &createSnapshotResponse, nil +} + +// POST /api/snapshots func CreateDashboardSnapshot(c *m.ReqContext, cmd m.CreateDashboardSnapshotCommand) { if cmd.Name == "" { cmd.Name = "Unnamed snapshot" } + var url string + cmd.ExternalUrl = "" + cmd.OrgId = c.OrgId + cmd.UserId = c.UserId + if cmd.External { - // external snapshot ref requires key and delete key - if cmd.Key == "" || cmd.DeleteKey == "" { - c.JsonApiErr(400, "Missing key and delete key for external snapshot", nil) + if !setting.ExternalEnabled { + c.JsonApiErr(403, "External dashboard creation is disabled", nil) + return + } + + response, err := createExternalDashboardSnapshot(cmd) + if err != nil { + c.JsonApiErr(500, "Failed to create external snaphost", err) return } - cmd.OrgId = -1 - cmd.UserId = -1 + url = response.Url + cmd.Key = response.Key + cmd.DeleteKey = response.DeleteKey + cmd.ExternalUrl = response.Url + cmd.ExternalDeleteUrl = response.DeleteUrl + cmd.Dashboard = simplejson.New() + metrics.M_Api_Dashboard_Snapshot_External.Inc() } else { cmd.Key = util.GetRandomString(32) cmd.DeleteKey = util.GetRandomString(32) - cmd.OrgId = c.OrgId - cmd.UserId = c.UserId + url = setting.ToAbsUrl("dashboard/snapshot/" + cmd.Key) + metrics.M_Api_Dashboard_Snapshot_Create.Inc() } @@ -51,7 +117,7 @@ func CreateDashboardSnapshot(c *m.ReqContext, cmd m.CreateDashboardSnapshotComma c.JSON(200, util.DynMap{ "key": cmd.Key, "deleteKey": cmd.DeleteKey, - "url": setting.ToAbsUrl("dashboard/snapshot/" + cmd.Key), + "url": url, "deleteUrl": setting.ToAbsUrl("api/snapshots-delete/" + cmd.DeleteKey), }) } diff --git a/pkg/models/dashboard_snapshot.go b/pkg/models/dashboard_snapshot.go index 3024ba94122..e8db0372758 100644 --- a/pkg/models/dashboard_snapshot.go +++ b/pkg/models/dashboard_snapshot.go @@ -8,14 +8,15 @@ import ( // DashboardSnapshot model type DashboardSnapshot struct { - Id int64 - Name string - Key string - DeleteKey string - OrgId int64 - UserId int64 - External bool - ExternalUrl string + Id int64 + Name string + Key string + DeleteKey string + OrgId int64 + UserId int64 + External bool + ExternalUrl string + ExternalDeleteUrl string Expires time.Time Created time.Time @@ -48,7 +49,10 @@ type CreateDashboardSnapshotCommand struct { Expires int64 `json:"expires"` // these are passed when storing an external snapshot ref - External bool `json:"external"` + External bool `json:"external"` + ExternalUrl string `json:"-"` + ExternalDeleteUrl string `json:"-"` + Key string `json:"key"` DeleteKey string `json:"deleteKey"` diff --git a/pkg/services/sqlstore/dashboard_snapshot.go b/pkg/services/sqlstore/dashboard_snapshot.go index 2e2ea8a4783..d0af676a305 100644 --- a/pkg/services/sqlstore/dashboard_snapshot.go +++ b/pkg/services/sqlstore/dashboard_snapshot.go @@ -47,16 +47,18 @@ func CreateDashboardSnapshot(cmd *m.CreateDashboardSnapshotCommand) error { } snapshot := &m.DashboardSnapshot{ - Name: cmd.Name, - Key: cmd.Key, - DeleteKey: cmd.DeleteKey, - OrgId: cmd.OrgId, - UserId: cmd.UserId, - External: cmd.External, - Dashboard: cmd.Dashboard, - Expires: expires, - Created: time.Now(), - Updated: time.Now(), + Name: cmd.Name, + Key: cmd.Key, + DeleteKey: cmd.DeleteKey, + OrgId: cmd.OrgId, + UserId: cmd.UserId, + External: cmd.External, + ExternalUrl: cmd.ExternalUrl, + ExternalDeleteUrl: cmd.ExternalDeleteUrl, + Dashboard: cmd.Dashboard, + Expires: expires, + Created: time.Now(), + Updated: time.Now(), } _, err := sess.Insert(snapshot) diff --git a/public/app/features/dashboard/share_snapshot_ctrl.ts b/public/app/features/dashboard/share_snapshot_ctrl.ts index ac09d63054d..7dcf0469e77 100644 --- a/public/app/features/dashboard/share_snapshot_ctrl.ts +++ b/public/app/features/dashboard/share_snapshot_ctrl.ts @@ -27,7 +27,6 @@ export class ShareSnapshotCtrl { $scope.init = () => { backendSrv.get('/api/snapshot/shared-options').then(options => { - $scope.externalUrl = options['externalSnapshotURL']; $scope.sharingButtonText = options['externalSnapshotName']; $scope.externalEnabled = options['externalEnabled']; }); @@ -61,30 +60,14 @@ export class ShareSnapshotCtrl { dashboard: dash, name: dash.title, expires: $scope.snapshot.expires, + external: external, }; - const postUrl = external ? $scope.externalUrl + $scope.apiUrl : $scope.apiUrl; - - backendSrv.post(postUrl, cmdData).then( + backendSrv.post($scope.apiUrl, cmdData).then( results => { $scope.loading = false; - - if (external) { - $scope.deleteUrl = results.deleteUrl; - $scope.snapshotUrl = results.url; - $scope.saveExternalSnapshotRef(cmdData, results); - } else { - const url = $location.url(); - let baseUrl = $location.absUrl(); - - if (url !== '/') { - baseUrl = baseUrl.replace(url, '') + '/'; - } - - $scope.snapshotUrl = baseUrl + 'dashboard/snapshot/' + results.key; - $scope.deleteUrl = baseUrl + 'api/snapshots-delete/' + results.deleteKey; - } - + $scope.deleteUrl = results.deleteUrl; + $scope.snapshotUrl = results.url; $scope.step = 2; }, () => { @@ -161,14 +144,6 @@ export class ShareSnapshotCtrl { $scope.step = 3; }); }; - - $scope.saveExternalSnapshotRef = (cmdData, results) => { - // save external in local instance as well - cmdData.external = true; - cmdData.key = results.key; - cmdData.deleteKey = results.deleteKey; - backendSrv.post('/api/snapshots/', cmdData); - }; } } From 411d67cae76f49a9925e3663426c4d1a3dbe00b4 Mon Sep 17 00:00:00 2001 From: Victor Cinaglia Date: Mon, 10 Dec 2018 16:40:26 -0500 Subject: [PATCH 013/139] snapshots: Add support for deleting external snapshots --- pkg/api/dashboard_snapshot.go | 45 ++++++++++ pkg/api/dashboard_snapshot_test.go | 87 +++++++++++++++++++ .../manage-dashboards/SnapshotListCtrl.ts | 8 +- .../partials/snapshot_list.html | 10 ++- 4 files changed, 145 insertions(+), 5 deletions(-) diff --git a/pkg/api/dashboard_snapshot.go b/pkg/api/dashboard_snapshot.go index 6c3ee7b69c6..af818be99b0 100644 --- a/pkg/api/dashboard_snapshot.go +++ b/pkg/api/dashboard_snapshot.go @@ -157,6 +157,37 @@ func GetDashboardSnapshot(c *m.ReqContext) { c.JSON(200, dto) } +func deleteExternalDashboardSnapshot(externalUrl string) error { + response, err := client.Get(externalUrl) + + if response != nil { + defer response.Body.Close() + } + + if err != nil { + return err + } + + if response.StatusCode == 200 { + return nil + } + + // Gracefully ignore "snapshot not found" errors as they could have already + // been removed either via the cleanup script or by request. + if response.StatusCode == 500 { + var respJson map[string]interface{} + if err := json.NewDecoder(response.Body).Decode(&respJson); err != nil { + return err + } + + if respJson["message"] == "Failed to get dashboard snapshot" { + return nil + } + } + + return fmt.Errorf("Unexpected response when deleting external snapshot. Status code: %d", response.StatusCode) +} + // GET /api/snapshots-delete/:deleteKey func DeleteDashboardSnapshotByDeleteKey(c *m.ReqContext) Response { key := c.Params(":deleteKey") @@ -168,6 +199,13 @@ func DeleteDashboardSnapshotByDeleteKey(c *m.ReqContext) Response { return Error(500, "Failed to get dashboard snapshot", err) } + if query.Result.External { + err := deleteExternalDashboardSnapshot(query.Result.ExternalDeleteUrl) + if err != nil { + return Error(500, "Failed to delete external dashboard", err) + } + } + cmd := &m.DeleteDashboardSnapshotCommand{DeleteKey: query.Result.DeleteKey} if err := bus.Dispatch(cmd); err != nil { @@ -204,6 +242,13 @@ func DeleteDashboardSnapshot(c *m.ReqContext) Response { return Error(403, "Access denied to this snapshot", nil) } + if query.Result.External { + err := deleteExternalDashboardSnapshot(query.Result.ExternalDeleteUrl) + if err != nil { + return Error(500, "Failed to delete external dashboard", err) + } + } + cmd := &m.DeleteDashboardSnapshotCommand{DeleteKey: query.Result.DeleteKey} if err := bus.Dispatch(cmd); err != nil { diff --git a/pkg/api/dashboard_snapshot_test.go b/pkg/api/dashboard_snapshot_test.go index e58f2c4712d..a24d0f38d85 100644 --- a/pkg/api/dashboard_snapshot_test.go +++ b/pkg/api/dashboard_snapshot_test.go @@ -1,6 +1,9 @@ package api import ( + "fmt" + "net/http" + "net/http/httptest" "testing" "time" @@ -13,13 +16,17 @@ import ( func TestDashboardSnapshotApiEndpoint(t *testing.T) { Convey("Given a single snapshot", t, func() { + var externalRequest *http.Request jsonModel, _ := simplejson.NewJson([]byte(`{"id":100}`)) mockSnapshotResult := &m.DashboardSnapshot{ Id: 1, + Key: "12345", + DeleteKey: "54321", Dashboard: jsonModel, Expires: time.Now().Add(time.Duration(1000) * time.Second), UserId: 999999, + External: true, } bus.AddHandler("test", func(query *m.GetDashboardSnapshotQuery) error { @@ -45,13 +52,25 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) { return nil }) + setupRemoteServer := func(fn func(http.ResponseWriter, *http.Request)) *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + fn(rw, r) + })) + } + Convey("When user has editor role and is not in the ACL", func() { Convey("Should not be able to delete snapshot", func() { loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", m.ROLE_EDITOR, func(sc *scenarioContext) { + ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) { + externalRequest = req + }) + + mockSnapshotResult.ExternalDeleteUrl = ts.URL sc.handlerFunc = DeleteDashboardSnapshot sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec() So(sc.resp.Code, ShouldEqual, 403) + So(externalRequest, ShouldBeNil) }) }) }) @@ -59,6 +78,12 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) { Convey("When user is anonymous", func() { Convey("Should be able to delete snapshot by deleteKey", func() { anonymousUserScenario("When calling GET on", "GET", "/api/snapshots-delete/12345", "/api/snapshots-delete/:deleteKey", func(sc *scenarioContext) { + ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(200) + externalRequest = req + }) + + mockSnapshotResult.ExternalDeleteUrl = ts.URL sc.handlerFunc = DeleteDashboardSnapshotByDeleteKey sc.fakeReqWithParams("GET", sc.url, map[string]string{"deleteKey": "12345"}).exec() @@ -67,6 +92,10 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) { So(err, ShouldBeNil) So(respJSON.Get("message").MustString(), ShouldStartWith, "Snapshot deleted") + + So(externalRequest.Method, ShouldEqual, http.MethodGet) + So(fmt.Sprintf("http://%s", externalRequest.Host), ShouldEqual, ts.URL) + So(externalRequest.URL.EscapedPath(), ShouldEqual, "/") }) }) }) @@ -79,6 +108,12 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) { Convey("Should be able to delete a snapshot", func() { loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", m.ROLE_EDITOR, func(sc *scenarioContext) { + ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(200) + externalRequest = req + }) + + mockSnapshotResult.ExternalDeleteUrl = ts.URL sc.handlerFunc = DeleteDashboardSnapshot sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec() @@ -87,6 +122,8 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) { So(err, ShouldBeNil) So(respJSON.Get("message").MustString(), ShouldStartWith, "Snapshot deleted") + So(fmt.Sprintf("http://%s", externalRequest.Host), ShouldEqual, ts.URL) + So(externalRequest.URL.EscapedPath(), ShouldEqual, "/") }) }) }) @@ -94,6 +131,7 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) { Convey("When user is editor and is the creator of the snapshot", func() { aclMockResp = []*m.DashboardAclInfoDTO{} mockSnapshotResult.UserId = TestUserID + mockSnapshotResult.External = false Convey("Should be able to delete a snapshot", func() { loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", m.ROLE_EDITOR, func(sc *scenarioContext) { @@ -108,5 +146,54 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) { }) }) }) + + Convey("When deleting an external snapshot", func() { + aclMockResp = []*m.DashboardAclInfoDTO{} + mockSnapshotResult.UserId = TestUserID + + Convey("Should gracefully delete local snapshot when remote snapshot has already been removed", func() { + loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", m.ROLE_EDITOR, func(sc *scenarioContext) { + ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) { + rw.Write([]byte(`{"message":"Failed to get dashboard snapshot"}`)) + rw.WriteHeader(500) + }) + + mockSnapshotResult.ExternalDeleteUrl = ts.URL + sc.handlerFunc = DeleteDashboardSnapshot + sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec() + + So(sc.resp.Code, ShouldEqual, 200) + }) + }) + + Convey("Should fail to delete local snapshot when an unexpected 500 error occurs", func() { + loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", m.ROLE_EDITOR, func(sc *scenarioContext) { + ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(500) + rw.Write([]byte(`{"message":"Unexpected"}`)) + }) + + mockSnapshotResult.ExternalDeleteUrl = ts.URL + sc.handlerFunc = DeleteDashboardSnapshot + sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec() + + So(sc.resp.Code, ShouldEqual, 500) + }) + }) + + Convey("Should fail to delete local snapshot when an unexpected remote error occurs", func() { + loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", m.ROLE_EDITOR, func(sc *scenarioContext) { + ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(404) + }) + + mockSnapshotResult.ExternalDeleteUrl = ts.URL + sc.handlerFunc = DeleteDashboardSnapshot + sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec() + + So(sc.resp.Code, ShouldEqual, 500) + }) + }) + }) }) } diff --git a/public/app/features/manage-dashboards/SnapshotListCtrl.ts b/public/app/features/manage-dashboards/SnapshotListCtrl.ts index 2ff53e7aed5..4d6dc006d47 100644 --- a/public/app/features/manage-dashboards/SnapshotListCtrl.ts +++ b/public/app/features/manage-dashboards/SnapshotListCtrl.ts @@ -5,10 +5,14 @@ export class SnapshotListCtrl { snapshots: any; /** @ngInject */ - constructor(private $rootScope, private backendSrv, navModelSrv) { + constructor(private $rootScope, private backendSrv, navModelSrv, private $location) { this.navModel = navModelSrv.getNav('dashboards', 'snapshots', 0); this.backendSrv.get('/api/dashboard/snapshots').then(result => { - this.snapshots = result; + const baseUrl = this.$location.absUrl().replace($location.url(), ''); + this.snapshots = result.map(snapshot => ({ + ...snapshot, + url: snapshot.externalUrl || `${baseUrl}/dashboard/snapshot/${snapshot.key}`, + })); }); } diff --git a/public/app/features/manage-dashboards/partials/snapshot_list.html b/public/app/features/manage-dashboards/partials/snapshot_list.html index 8775b527ae1..f646194088d 100644 --- a/public/app/features/manage-dashboards/partials/snapshot_list.html +++ b/public/app/features/manage-dashboards/partials/snapshot_list.html @@ -6,17 +6,21 @@ Name Snapshot url + - {{snapshot.name}} + {{snapshot.name}} - dashboard/snapshot/{{snapshot.key}} + {{snapshot.url}} + + + External - + View From 2d296715ece016571224b1d6cdfe6432698317ee Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 14 Dec 2018 02:01:07 +0100 Subject: [PATCH 014/139] Show predefined time ranges as first in timepicker on small screens --- public/sass/components/_timepicker.scss | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/public/sass/components/_timepicker.scss b/public/sass/components/_timepicker.scss index e12835d31c1..b69e0ba99d2 100644 --- a/public/sass/components/_timepicker.scss +++ b/public/sass/components/_timepicker.scss @@ -13,13 +13,18 @@ } .gf-timepicker-dropdown { - position: absolute; - top: $navbarHeight; - right: 0; - padding: 10px 20px; background-color: $page-bg; border-radius: 0 0 0 4px; box-shadow: $search-shadow; + display: flex; + flex-direction: column-reverse; + padding: 10px 20px; + position: absolute; + right: 0; + top: $navbarHeight; + @include media-breakpoint-up(md) { + flex-direction: column; + } } .gf-timepicker-absolute-section { From 17f8be90ae4ddd2f4c8f2b323fb64b73dd277ff5 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 17 Dec 2018 14:25:47 +0100 Subject: [PATCH 015/139] upgrade to golang 1.11.4 --- .circleci/config.yml | 10 +++++----- Dockerfile | 2 +- appveyor.yml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3dd8f800b94..dba6c5f8bd0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,7 +19,7 @@ version: 2 jobs: mysql-integration-test: docker: - - image: circleci/golang:1.11 + - image: circleci/golang:1.11.4 - image: circleci/mysql:5.6-ram environment: MYSQL_ROOT_PASSWORD: rootpass @@ -39,7 +39,7 @@ jobs: postgres-integration-test: docker: - - image: circleci/golang:1.11 + - image: circleci/golang:1.11.4 - image: circleci/postgres:9.3-ram environment: POSTGRES_USER: grafanatest @@ -74,7 +74,7 @@ jobs: gometalinter: docker: - - image: circleci/golang:1.11 + - image: circleci/golang:1.11.4 environment: # we need CGO because of go-sqlite3 CGO_ENABLED: 1 @@ -117,7 +117,7 @@ jobs: test-backend: docker: - - image: circleci/golang:1.11 + - image: circleci/golang:1.11.4 working_directory: /go/src/github.com/grafana/grafana steps: - checkout @@ -175,7 +175,7 @@ jobs: build: docker: - - image: grafana/build-container:1.2.1 + - image: grafana/build-container:1.2.2 working_directory: /go/src/github.com/grafana/grafana steps: - checkout diff --git a/Dockerfile b/Dockerfile index 65260e1a6a8..c3af89b6092 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Golang build container -FROM golang:1.11 +FROM golang:1.11.4 WORKDIR $GOPATH/src/github.com/grafana/grafana diff --git a/appveyor.yml b/appveyor.yml index 4bbd3668e19..5f97784dd38 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ clone_folder: c:\gopath\src\github.com\grafana\grafana environment: nodejs_version: "8" GOPATH: C:\gopath - GOVERSION: 1.11 + GOVERSION: 1.11.4 install: - rmdir c:\go /s /q From cda78973232ad0f0bea0f603c7e07649771fdb6a Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 17 Dec 2018 16:42:02 +0100 Subject: [PATCH 016/139] started with component for generic panel help --- .../core/components/PanelHelp/PanelHelp.tsx | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 public/app/core/components/PanelHelp/PanelHelp.tsx diff --git a/public/app/core/components/PanelHelp/PanelHelp.tsx b/public/app/core/components/PanelHelp/PanelHelp.tsx new file mode 100644 index 00000000000..25340640d4b --- /dev/null +++ b/public/app/core/components/PanelHelp/PanelHelp.tsx @@ -0,0 +1,59 @@ +import React, { PureComponent } from 'react'; +import Remarkable from 'remarkable'; +import { getBackendSrv } from '../../services/backend_srv'; +import { DataSource } from 'app/types'; + +interface Props { + dataSource: DataSource; + type: string; +} + +interface State { + isError: boolean; + isLoading: boolean; + help: any; +} + +export default class PanelHelp extends PureComponent { + componentDidMount(): void { + this.loadHelp(); + } + + loadHelp = () => { + const { dataSource, type } = this.props; + this.setState({ isLoading: true }); + + getBackendSrv() + .get(`/api/plugins/${dataSource.meta.id}/markdown/${type}`) + .then(response => { + const markdown = new Remarkable(); + const helpHtml = markdown.render(response); + + this.setState({ + isError: false, + isLoading: false, + help: helpHtml, + }); + }) + .catch(() => { + this.setState({ + isError: true, + isLoading: false, + }); + }); + }; + + render() { + const { isError, isLoading, help } = this.state; + + if (isLoading) { + return

Loading help...

; + } + + if (isError) { + return

'Error occurred when loading help'

; + } + + return
; + } +} From 65db6a76387d30dc758fcbf9db887af537790427 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 17 Dec 2018 16:54:29 +0100 Subject: [PATCH 017/139] toolbaritems viztab --- public/app/features/dashboard/dashgrid/VisualizationTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 0e31d7cdafc..cdd23fb2ced 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -203,7 +203,7 @@ export class VisualizationTab extends PureComponent { const { isVizPickerOpen, searchQuery } = this.state; return ( - + <> Date: Mon, 17 Dec 2018 23:43:14 -0600 Subject: [PATCH 018/139] Adding CIDR capability to auth_proxy whitelist --- pkg/middleware/auth_proxy.go | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/pkg/middleware/auth_proxy.go b/pkg/middleware/auth_proxy.go index 29bd305b336..fc109ac707f 100644 --- a/pkg/middleware/auth_proxy.go +++ b/pkg/middleware/auth_proxy.go @@ -198,17 +198,31 @@ func checkAuthenticationProxy(remoteAddr string, proxyHeaderValue string) error } proxies := strings.Split(setting.AuthProxyWhitelist, ",") - sourceIP, _, err := net.SplitHostPort(remoteAddr) - if err != nil { - return err + var proxyObjs []*net.IPNet + for _, proxy := range proxies { + proxyObjs = append(proxyObjs, coerceProxyAddress(proxy)) } - // Compare allowed IP addresses to actual address - for _, proxyIP := range proxies { - if sourceIP == strings.TrimSpace(proxyIP) { + sourceIP, _, _ := net.SplitHostPort(remoteAddr) + sourceObj := net.ParseIP(sourceIP) + + for _, proxyObj := range proxyObjs { + if proxyObj.Contains(sourceObj) { return nil } } - return fmt.Errorf("Request for user (%s) from %s is not from the authentication proxy", proxyHeaderValue, sourceIP) } + +func coerceProxyAddress(proxyAddr string) *net.IPNet { + proxyAddr = strings.TrimSpace(proxyAddr) + if !strings.Contains(proxyAddr, "/") { + proxyAddr = strings.Join([]string{proxyAddr, "32"}, "/") + } + + _, network, err := net.ParseCIDR(proxyAddr) + if err != nil { + fmt.Println(err) + } + return network +} From 0cf3e949341cd1997fffb263256f4dacb96df5a6 Mon Sep 17 00:00:00 2001 From: Jonas Hahnfeld Date: Tue, 18 Dec 2018 12:11:21 +0100 Subject: [PATCH 019/139] Add units for Floating Point Operations per Second This is an important metric for computation throughput in the context of High Performance Computing (HPC). I've never heard of the kilo prefix here, nowadays it's mostly measured in MFLOP/s. --- public/app/core/utils/kbn.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/public/app/core/utils/kbn.ts b/public/app/core/utils/kbn.ts index 30fdf2bac6f..a065016a589 100644 --- a/public/app/core/utils/kbn.ts +++ b/public/app/core/utils/kbn.ts @@ -484,6 +484,14 @@ kbn.valueFormats.Mbits = kbn.formatBuilders.decimalSIPrefix('bps', 2); kbn.valueFormats.GBs = kbn.formatBuilders.decimalSIPrefix('Bs', 3); kbn.valueFormats.Gbits = kbn.formatBuilders.decimalSIPrefix('bps', 3); +// Floating Point Operations per Second +kbn.valueFormats.flops = kbn.formatBuilders.decimalSIPrefix('FLOP/s'); +kbn.valueFormats.mflops = kbn.formatBuilders.decimalSIPrefix('FLOP/s', 2); +kbn.valueFormats.gflops = kbn.formatBuilders.decimalSIPrefix('FLOP/s', 3); +kbn.valueFormats.tflops = kbn.formatBuilders.decimalSIPrefix('FLOP/s', 4); +kbn.valueFormats.pflops = kbn.formatBuilders.decimalSIPrefix('FLOP/s', 5); +kbn.valueFormats.eflops = kbn.formatBuilders.decimalSIPrefix('FLOP/s', 6); + // Hash Rate kbn.valueFormats.Hs = kbn.formatBuilders.decimalSIPrefix('H/s'); kbn.valueFormats.KHs = kbn.formatBuilders.decimalSIPrefix('H/s', 1); @@ -1019,6 +1027,17 @@ kbn.getUnitFormats = () => { { text: 'exahashes/sec', value: 'EHs' }, ], }, + { + text: 'computation throughput', + submenu: [ + { text: 'FLOP/s', value: 'flops' }, + { text: 'MFLOP/s', value: 'mflops' }, + { text: 'GFLOP/s', value: 'gflops' }, + { text: 'TFLOP/s', value: 'tflops' }, + { text: 'PFLOP/s', value: 'pflops' }, + { text: 'EFLOP/s', value: 'eflops' }, + ], + }, { text: 'throughput', submenu: [ From 052772ea2ee9dbfb95b8a75c8437ab2a4bd7ed82 Mon Sep 17 00:00:00 2001 From: Tomas Dabasinskas Date: Tue, 18 Dec 2018 13:48:25 +0200 Subject: [PATCH 020/139] Register BrokenAuthHeaderProviders if needed --- pkg/social/social.go | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/pkg/social/social.go b/pkg/social/social.go index 8918507f3b9..da827deaa03 100644 --- a/pkg/social/social.go +++ b/pkg/social/social.go @@ -63,28 +63,34 @@ func NewOAuthService() { for _, name := range allOauthes { sec := setting.Raw.Section("auth." + name) info := &setting.OAuthInfo{ - ClientId: sec.Key("client_id").String(), - ClientSecret: sec.Key("client_secret").String(), - Scopes: util.SplitString(sec.Key("scopes").String()), - AuthUrl: sec.Key("auth_url").String(), - TokenUrl: sec.Key("token_url").String(), - ApiUrl: sec.Key("api_url").String(), - Enabled: sec.Key("enabled").MustBool(), - EmailAttributeName: sec.Key("email_attribute_name").String(), - AllowedDomains: util.SplitString(sec.Key("allowed_domains").String()), - HostedDomain: sec.Key("hosted_domain").String(), - AllowSignup: sec.Key("allow_sign_up").MustBool(), - Name: sec.Key("name").MustString(name), - TlsClientCert: sec.Key("tls_client_cert").String(), - TlsClientKey: sec.Key("tls_client_key").String(), - TlsClientCa: sec.Key("tls_client_ca").String(), - TlsSkipVerify: sec.Key("tls_skip_verify_insecure").MustBool(), + ClientId: sec.Key("client_id").String(), + ClientSecret: sec.Key("client_secret").String(), + Scopes: util.SplitString(sec.Key("scopes").String()), + AuthUrl: sec.Key("auth_url").String(), + TokenUrl: sec.Key("token_url").String(), + ApiUrl: sec.Key("api_url").String(), + Enabled: sec.Key("enabled").MustBool(), + EmailAttributeName: sec.Key("email_attribute_name").String(), + AllowedDomains: util.SplitString(sec.Key("allowed_domains").String()), + HostedDomain: sec.Key("hosted_domain").String(), + AllowSignup: sec.Key("allow_sign_up").MustBool(), + Name: sec.Key("name").MustString(name), + TlsClientCert: sec.Key("tls_client_cert").String(), + TlsClientKey: sec.Key("tls_client_key").String(), + TlsClientCa: sec.Key("tls_client_ca").String(), + TlsSkipVerify: sec.Key("tls_skip_verify_insecure").MustBool(), + BrokenAuthHeaderProvider: sec.Key("broken_auth_header_provider").MustBool(), } if !info.Enabled { continue } + // handle the clients that do not properly support Basic auth headers and require passing client_id/client_secret via POST payload + if info.BrokenAuthHeaderProvider { + oauth2.RegisterBrokenAuthHeaderProvider(info.TokenUrl) + } + if name == "grafananet" { name = grafanaCom } From 54b73025dc5e504e07e290991d9d9ff7ef62d204 Mon Sep 17 00:00:00 2001 From: Tomas Dabasinskas Date: Tue, 18 Dec 2018 13:50:37 +0200 Subject: [PATCH 021/139] Add OAuth provider flag to indicate if it's broken --- pkg/setting/setting_oauth.go | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/pkg/setting/setting_oauth.go b/pkg/setting/setting_oauth.go index 93b1ab6f101..aab80f6a05e 100644 --- a/pkg/setting/setting_oauth.go +++ b/pkg/setting/setting_oauth.go @@ -1,20 +1,21 @@ package setting type OAuthInfo struct { - ClientId, ClientSecret string - Scopes []string - AuthUrl, TokenUrl string - Enabled bool - EmailAttributeName string - AllowedDomains []string - HostedDomain string - ApiUrl string - AllowSignup bool - Name string - TlsClientCert string - TlsClientKey string - TlsClientCa string - TlsSkipVerify bool + ClientId, ClientSecret string + Scopes []string + AuthUrl, TokenUrl string + Enabled bool + EmailAttributeName string + AllowedDomains []string + HostedDomain string + ApiUrl string + AllowSignup bool + Name string + TlsClientCert string + TlsClientKey string + TlsClientCa string + TlsSkipVerify bool + BrokenAuthHeaderProvider bool } type OAuther struct { From 08c12313fe5d349742f10615add0c3ff59f4ae23 Mon Sep 17 00:00:00 2001 From: Tomas Dabasinskas Date: Tue, 18 Dec 2018 13:51:17 +0200 Subject: [PATCH 022/139] Update sample and default configs --- conf/defaults.ini | 1 + conf/sample.ini | 1 + 2 files changed, 2 insertions(+) diff --git a/conf/defaults.ini b/conf/defaults.ini index 2ef2ad7942a..47f6a64eb8b 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -335,6 +335,7 @@ tls_skip_verify_insecure = false tls_client_cert = tls_client_key = tls_client_ca = +broken_auth_header_provider = false #################################### Basic Auth ########################## [auth.basic] diff --git a/conf/sample.ini b/conf/sample.ini index ba65727dc4b..b73ab850bbf 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -283,6 +283,7 @@ log_queries = ;tls_client_cert = ;tls_client_key = ;tls_client_ca = +;broken_auth_header_provider = false #################################### Grafana.com Auth #################### [auth.grafana_com] From 48fe92a9457210a5a9551a8cd5b22d82a880ec11 Mon Sep 17 00:00:00 2001 From: Victor Cinaglia Date: Tue, 18 Dec 2018 08:32:49 -0500 Subject: [PATCH 023/139] snapshots: Close response body after error check --- pkg/api/dashboard_snapshot.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/pkg/api/dashboard_snapshot.go b/pkg/api/dashboard_snapshot.go index af818be99b0..ca7e78a58cd 100644 --- a/pkg/api/dashboard_snapshot.go +++ b/pkg/api/dashboard_snapshot.go @@ -51,13 +51,10 @@ func createExternalDashboardSnapshot(cmd m.CreateDashboardSnapshotCommand) (*Cre } response, err := client.Post(setting.ExternalSnapshotUrl+"/api/snapshots", "application/json", bytes.NewBuffer(messageBytes)) - if response != nil { - defer response.Body.Close() - } - if err != nil { return nil, err } + defer response.Body.Close() if response.StatusCode != 200 { return nil, fmt.Errorf("Create external snapshot response status code %d", response.StatusCode) @@ -159,14 +156,10 @@ func GetDashboardSnapshot(c *m.ReqContext) { func deleteExternalDashboardSnapshot(externalUrl string) error { response, err := client.Get(externalUrl) - - if response != nil { - defer response.Body.Close() - } - if err != nil { return err } + defer response.Body.Close() if response.StatusCode == 200 { return nil From a44a07593f633744a826183493169932ffede2ec Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 18 Dec 2018 14:40:54 +0100 Subject: [PATCH 024/139] panel help working --- .../core/components/PanelHelp/PanelHelp.tsx | 51 +++++++++++++++---- .../dashboard/dashgrid/QueriesTab.tsx | 35 +------------ .../dashboard/dashgrid/VisualizationTab.tsx | 17 ++++++- .../features/datasources/state/navModel.ts | 2 +- .../features/plugins/__mocks__/pluginMocks.ts | 2 +- public/app/types/plugins.ts | 7 ++- 6 files changed, 67 insertions(+), 47 deletions(-) diff --git a/public/app/core/components/PanelHelp/PanelHelp.tsx b/public/app/core/components/PanelHelp/PanelHelp.tsx index 25340640d4b..7920e772b6a 100644 --- a/public/app/core/components/PanelHelp/PanelHelp.tsx +++ b/public/app/core/components/PanelHelp/PanelHelp.tsx @@ -1,39 +1,66 @@ import React, { PureComponent } from 'react'; import Remarkable from 'remarkable'; import { getBackendSrv } from '../../services/backend_srv'; -import { DataSource } from 'app/types'; +import { PluginMeta } from 'app/types'; interface Props { - dataSource: DataSource; + plugin: PluginMeta; type: string; } interface State { isError: boolean; isLoading: boolean; - help: any; + help: string; } export default class PanelHelp extends PureComponent { + state = { + isError: false, + isLoading: false, + help: '', + }; + componentDidMount(): void { this.loadHelp(); } + constructPlaceholderInfo() { + const { plugin } = this.props; + const markdown = new Remarkable(); + + return markdown.render( + `## ${plugin.name} \n by _${plugin.info.author.name} (<${plugin.info.author.url}>)_\n\n${ + plugin.info.description + }\n\n### Links \n ${plugin.info.links.map(link => { + return `${link.name}: <${link.url}>\n`; + })}` + ); + } + loadHelp = () => { - const { dataSource, type } = this.props; + const { plugin, type } = this.props; this.setState({ isLoading: true }); getBackendSrv() - .get(`/api/plugins/${dataSource.meta.id}/markdown/${type}`) + .get(`/api/plugins/${plugin.id}/markdown/${type}`) .then(response => { const markdown = new Remarkable(); const helpHtml = markdown.render(response); - this.setState({ - isError: false, - isLoading: false, - help: helpHtml, - }); + if (response === '' && this.props.type) { + this.setState({ + isError: false, + isLoading: false, + help: this.constructPlaceholderInfo(), + }); + } else { + this.setState({ + isError: false, + isLoading: false, + help: helpHtml, + }); + } }) .catch(() => { this.setState({ @@ -44,6 +71,7 @@ export default class PanelHelp extends PureComponent { }; render() { + const { type } = this.props; const { isError, isLoading, help } = this.state; if (isLoading) { @@ -54,6 +82,9 @@ export default class PanelHelp extends PureComponent { return

'Error occurred when loading help'

; } + if (type === 'panel_help' && help === '') { + } + return
; } } diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 8513f061b74..112ba50822c 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -1,6 +1,5 @@ // Libraries import React, { SFC, PureComponent } from 'react'; -import Remarkable from 'remarkable'; import _ from 'lodash'; // Components @@ -22,6 +21,7 @@ import config from 'app/core/config'; import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; import { DataSourceSelectItem, DataQuery } from 'app/types'; +import PanelHelp from '../../../core/components/PanelHelp/PanelHelp'; interface Props { panel: PanelModel; @@ -128,43 +128,13 @@ export class QueriesTab extends PureComponent { }); }; - loadHelp = () => { - const { currentDS } = this.state; - const hasHelp = currentDS.meta.hasQueryHelp; - - if (hasHelp) { - this.setState({ - helpContent:

Loading help...

, - isLoadingHelp: true, - }); - - this.backendSrv - .get(`/api/plugins/${currentDS.meta.id}/markdown/query_help`) - .then(res => { - const md = new Remarkable(); - const helpHtml = md.render(res); - this.setState({ - helpContent:
, - isLoadingHelp: false, - }); - }) - .catch(() => { - this.setState({ - helpContent:

'Error occured when loading help'

, - isLoadingHelp: false, - }); - }); - } - }; - renderQueryInspector = () => { const { panel } = this.props; return ; }; renderHelp = () => { - const { helpContent, isLoadingHelp } = this.state; - return isLoadingHelp ? : helpContent; + return ; }; onAddQuery = (query?: Partial) => { @@ -244,7 +214,6 @@ export class QueriesTab extends PureComponent { heading: 'Help', icon: 'fa fa-question', disabled: !hasQueryHelp, - onClick: this.loadHelp, render: this.renderHelp, }; diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index cdd23fb2ced..f479bab57d5 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -3,10 +3,12 @@ import React, { PureComponent } from 'react'; // Utils & Services import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; +import { getDatasourceSrv } from '../../plugins/datasource_srv'; // Components import { EditorTabBody } from './EditorTabBody'; import { VizTypePicker } from './VizTypePicker'; +import PanelHelp from 'app/core/components/PanelHelp/PanelHelp'; import { FadeIn } from 'app/core/components/Animations/FadeIn'; import { PanelOptionSection } from './PanelOptionSection'; @@ -14,6 +16,7 @@ import { PanelOptionSection } from './PanelOptionSection'; import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; import { PanelPlugin } from 'app/types/plugins'; +import { DataSourceSelectItem } from 'app/types'; interface Props { panel: PanelModel; @@ -24,6 +27,7 @@ interface Props { } interface State { + currentDataSource: DataSourceSelectItem; isVizPickerOpen: boolean; searchQuery: string; } @@ -32,13 +36,16 @@ export class VisualizationTab extends PureComponent { element: HTMLElement; angularOptions: AngularComponent; searchInput: HTMLElement; + dataSources: DataSourceSelectItem[] = getDatasourceSrv().getMetricSources(); constructor(props) { super(props); + const { panel } = props; this.state = { isVizPickerOpen: false, searchQuery: '', + currentDataSource: this.dataSources.find(datasource => datasource.value === panel.datasource), }; } @@ -198,12 +205,20 @@ export class VisualizationTab extends PureComponent { } }; + renderHelp = () => ; + render() { const { plugin } = this.props; const { isVizPickerOpen, searchQuery } = this.state; + const pluginHelp = { + heading: 'Help', + icon: 'fa fa-question', + render: this.renderHelp, + }; + return ( - + <> { url: 'url/to/GrafanaLabs', }, description: 'pretty decent plugin', - links: ['one link'], + links: [{ name: 'project', url: 'one link' }], logos: { small: 'small/logo', large: 'large/logo' }, screenshots: [{ path: `screenshot` }], updated: '2018-09-26', diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index bc33ec80409..a3519e5b5cc 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -57,13 +57,18 @@ export interface PluginInclude { path: string; } +interface PluginMetaInfoLink { + name: string; + url: string; +} + export interface PluginMetaInfo { author: { name: string; url?: string; }; description: string; - links: string[]; + links: PluginMetaInfoLink[]; logos: { large: string; small: string; From 95656e1e956935279bb9a354bd05be56bb667a1c Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 18 Dec 2018 14:49:59 +0100 Subject: [PATCH 025/139] renaming component --- .../components/PanelHelp/{PanelHelp.tsx => PluginHelp.tsx} | 4 ++-- public/app/features/dashboard/dashgrid/QueriesTab.tsx | 4 ++-- public/app/features/dashboard/dashgrid/VisualizationTab.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename public/app/core/components/PanelHelp/{PanelHelp.tsx => PluginHelp.tsx} (93%) diff --git a/public/app/core/components/PanelHelp/PanelHelp.tsx b/public/app/core/components/PanelHelp/PluginHelp.tsx similarity index 93% rename from public/app/core/components/PanelHelp/PanelHelp.tsx rename to public/app/core/components/PanelHelp/PluginHelp.tsx index 7920e772b6a..f675ab10ecb 100644 --- a/public/app/core/components/PanelHelp/PanelHelp.tsx +++ b/public/app/core/components/PanelHelp/PluginHelp.tsx @@ -14,7 +14,7 @@ interface State { help: string; } -export default class PanelHelp extends PureComponent { +export default class PluginHelp extends PureComponent { state = { isError: false, isLoading: false, @@ -48,7 +48,7 @@ export default class PanelHelp extends PureComponent { const markdown = new Remarkable(); const helpHtml = markdown.render(response); - if (response === '' && this.props.type) { + if (response === '' && this.props.type === 'help') { this.setState({ isError: false, isLoading: false, diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 112ba50822c..36f38cadbd3 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -21,7 +21,7 @@ import config from 'app/core/config'; import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; import { DataSourceSelectItem, DataQuery } from 'app/types'; -import PanelHelp from '../../../core/components/PanelHelp/PanelHelp'; +import PluginHelp from '../../../core/components/PanelHelp/PluginHelp'; interface Props { panel: PanelModel; @@ -134,7 +134,7 @@ export class QueriesTab extends PureComponent { }; renderHelp = () => { - return ; + return ; }; onAddQuery = (query?: Partial) => { diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index f479bab57d5..2cf03b3a871 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -8,7 +8,7 @@ import { getDatasourceSrv } from '../../plugins/datasource_srv'; // Components import { EditorTabBody } from './EditorTabBody'; import { VizTypePicker } from './VizTypePicker'; -import PanelHelp from 'app/core/components/PanelHelp/PanelHelp'; +import PluginHelp from 'app/core/components/PanelHelp/PluginHelp'; import { FadeIn } from 'app/core/components/Animations/FadeIn'; import { PanelOptionSection } from './PanelOptionSection'; @@ -205,7 +205,7 @@ export class VisualizationTab extends PureComponent { } }; - renderHelp = () => ; + renderHelp = () => ; render() { const { plugin } = this.props; From bf7ba9a4d1ade9982c585b66a91d199e1ed7387c Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 18 Dec 2018 14:54:50 +0100 Subject: [PATCH 026/139] updating snaps --- .../DataSourceSettings.test.tsx.snap | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/public/app/features/datasources/settings/__snapshots__/DataSourceSettings.test.tsx.snap b/public/app/features/datasources/settings/__snapshots__/DataSourceSettings.test.tsx.snap index 9da5904d1ae..bcd8237ff39 100644 --- a/public/app/features/datasources/settings/__snapshots__/DataSourceSettings.test.tsx.snap +++ b/public/app/features/datasources/settings/__snapshots__/DataSourceSettings.test.tsx.snap @@ -61,7 +61,10 @@ exports[`Render should render alpha info text 1`] = ` }, "description": "pretty decent plugin", "links": Array [ - "one link", + Object { + "name": "project", + "url": "one link", + }, ], "logos": Object { "large": "large/logo", @@ -160,7 +163,10 @@ exports[`Render should render beta info text 1`] = ` }, "description": "pretty decent plugin", "links": Array [ - "one link", + Object { + "name": "project", + "url": "one link", + }, ], "logos": Object { "large": "large/logo", @@ -254,7 +260,10 @@ exports[`Render should render component 1`] = ` }, "description": "pretty decent plugin", "links": Array [ - "one link", + Object { + "name": "project", + "url": "one link", + }, ], "logos": Object { "large": "large/logo", @@ -353,7 +362,10 @@ exports[`Render should render is ready only message 1`] = ` }, "description": "pretty decent plugin", "links": Array [ - "one link", + Object { + "name": "project", + "url": "one link", + }, ], "logos": Object { "large": "large/logo", From 659b5a3c15f57067c4a03dc5e0a854329e254c7e Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 18 Dec 2018 15:30:34 +0100 Subject: [PATCH 027/139] refactor to not crash when no links --- .../core/components/PanelHelp/PluginHelp.tsx | 20 ++++++++++++------- .../dashboard/dashgrid/VisualizationTab.tsx | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/public/app/core/components/PanelHelp/PluginHelp.tsx b/public/app/core/components/PanelHelp/PluginHelp.tsx index f675ab10ecb..b43efae2d15 100644 --- a/public/app/core/components/PanelHelp/PluginHelp.tsx +++ b/public/app/core/components/PanelHelp/PluginHelp.tsx @@ -25,18 +25,24 @@ export default class PluginHelp extends PureComponent { this.loadHelp(); } - constructPlaceholderInfo() { + constructPlaceholderInfo = () => { const { plugin } = this.props; const markdown = new Remarkable(); - return markdown.render( + const fallBack = markdown.render( `## ${plugin.name} \n by _${plugin.info.author.name} (<${plugin.info.author.url}>)_\n\n${ plugin.info.description - }\n\n### Links \n ${plugin.info.links.map(link => { - return `${link.name}: <${link.url}>\n`; - })}` + }\n\n${ + plugin.info.links + ? `### Links \n ${plugin.info.links.map(link => { + return `${link.name}: <${link.url}>\n`; + })}` + : '' + }` ); - } + + return fallBack; + }; loadHelp = () => { const { plugin, type } = this.props; @@ -48,7 +54,7 @@ export default class PluginHelp extends PureComponent { const markdown = new Remarkable(); const helpHtml = markdown.render(response); - if (response === '' && this.props.type === 'help') { + if (response === '' && type === 'help') { this.setState({ isError: false, isLoading: false, diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 2cf03b3a871..70285124b16 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -205,7 +205,7 @@ export class VisualizationTab extends PureComponent { } }; - renderHelp = () => ; + renderHelp = () => ; render() { const { plugin } = this.props; From 8f92e23e98a558189bd3644aa9534ba49b47df7c Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 18 Dec 2018 16:39:59 +0100 Subject: [PATCH 028/139] copy props to state to make it visible in the view --- .../dashboard/dashgrid/QueryOptions.tsx | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/QueryOptions.tsx b/public/app/features/dashboard/dashgrid/QueryOptions.tsx index c6d5fecb6d4..dd084418c40 100644 --- a/public/app/features/dashboard/dashgrid/QueryOptions.tsx +++ b/public/app/features/dashboard/dashgrid/QueryOptions.tsx @@ -38,7 +38,33 @@ interface Props { datasource: DataSourceSelectItem; } -export class QueryOptions extends PureComponent { +interface State { + relativeTime: string; + timeShift: string; +} + +export class QueryOptions extends PureComponent { + constructor(props) { + super(props); + + this.state = { + relativeTime: props.panel.timeFrom || '', + timeShift: props.panel.timeShift || '', + }; + } + + onRelativeTimeChange = event => { + this.setState({ + relativeTime: event.target.value, + }); + }; + + onTimeShiftChange = event => { + this.setState({ + timeShift: event.target.value, + }); + }; + onOverrideTime = (evt, status: InputStatus) => { const { value } = evt.target; const { panel } = this.props; @@ -128,8 +154,10 @@ export class QueryOptions extends PureComponent { }); } - render = () => { + render() { const hideTimeOverride = this.props.panel.hideTimeOverride; + const { relativeTime, timeShift } = this.state; + return (
{this.renderOptions()} @@ -140,9 +168,11 @@ export class QueryOptions extends PureComponent { type="text" className="width-6" placeholder="1h" + onChange={this.onRelativeTimeChange} onBlur={this.onOverrideTime} validationEvents={timeRangeValidationEvents} hideErrorMessage={true} + value={relativeTime} />
@@ -152,9 +182,11 @@ export class QueryOptions extends PureComponent { type="text" className="width-6" placeholder="1h" + onChange={this.onTimeShiftChange} onBlur={this.onTimeShift} validationEvents={timeRangeValidationEvents} hideErrorMessage={true} + value={timeShift} />
@@ -163,5 +195,5 @@ export class QueryOptions extends PureComponent {
); - }; + } } From f51222027d04269e61918d33933adc6170269f6e Mon Sep 17 00:00:00 2001 From: Baokun Lee Date: Tue, 18 Dec 2018 23:59:14 +0800 Subject: [PATCH 029/139] Raise datasources number to 5000 --- pkg/services/sqlstore/datasource.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/services/sqlstore/datasource.go b/pkg/services/sqlstore/datasource.go index 7f70e5c25fc..ccab1106880 100644 --- a/pkg/services/sqlstore/datasource.go +++ b/pkg/services/sqlstore/datasource.go @@ -53,14 +53,14 @@ func GetDataSourceByName(query *m.GetDataSourceByNameQuery) error { } func GetDataSources(query *m.GetDataSourcesQuery) error { - sess := x.Limit(1000, 0).Where("org_id=?", query.OrgId).Asc("name") + sess := x.Limit(5000, 0).Where("org_id=?", query.OrgId).Asc("name") query.Result = make([]*m.DataSource, 0) return sess.Find(&query.Result) } func GetAllDataSources(query *m.GetAllDataSourcesQuery) error { - sess := x.Limit(1000, 0).Asc("name") + sess := x.Limit(5000, 0).Asc("name") query.Result = make([]*m.DataSource, 0) return sess.Find(&query.Result) From 3f85901c4ab23d4afd6a1568c6bbae2401b3e7a1 Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Mon, 17 Dec 2018 14:39:06 -0500 Subject: [PATCH 030/139] only update session in mysql database when required --- pkg/services/session/mysql.go | 39 ++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/pkg/services/session/mysql.go b/pkg/services/session/mysql.go index f8c5d828cfa..694e16dfb90 100644 --- a/pkg/services/session/mysql.go +++ b/pkg/services/session/mysql.go @@ -29,18 +29,22 @@ import ( // MysqlStore represents a mysql session store implementation. type MysqlStore struct { - c *sql.DB - sid string - lock sync.RWMutex - data map[interface{}]interface{} + c *sql.DB + sid string + lock sync.RWMutex + data map[interface{}]interface{} + expiry int64 + dirty bool } // NewMysqlStore creates and returns a mysql session store. -func NewMysqlStore(c *sql.DB, sid string, kv map[interface{}]interface{}) *MysqlStore { +func NewMysqlStore(c *sql.DB, sid string, kv map[interface{}]interface{}, expiry int64) *MysqlStore { return &MysqlStore{ - c: c, - sid: sid, - data: kv, + c: c, + sid: sid, + data: kv, + expiry: expiry, + dirty: false, } } @@ -50,6 +54,7 @@ func (s *MysqlStore) Set(key, val interface{}) error { defer s.lock.Unlock() s.data[key] = val + s.dirty = true return nil } @@ -67,6 +72,7 @@ func (s *MysqlStore) Delete(key interface{}) error { defer s.lock.Unlock() delete(s.data, key) + s.dirty = true return nil } @@ -77,13 +83,20 @@ func (s *MysqlStore) ID() string { // Release releases resource and save data to provider. func (s *MysqlStore) Release() error { + newExpiry := time.Now().Unix() + if !s.dirty && (s.expiry+60) >= newExpiry { + return nil + } + data, err := session.EncodeGob(s.data) if err != nil { return err } _, err = s.c.Exec("UPDATE session SET data=?, expiry=? WHERE `key`=?", - data, time.Now().Unix(), s.sid) + data, newExpiry, s.sid) + s.dirty = false + s.expiry = newExpiry return err } @@ -93,6 +106,7 @@ func (s *MysqlStore) Flush() error { defer s.lock.Unlock() s.data = make(map[interface{}]interface{}) + s.dirty = true return nil } @@ -117,11 +131,12 @@ func (p *MysqlProvider) Init(expire int64, connStr string) (err error) { // Read returns raw session store by session ID. func (p *MysqlProvider) Read(sid string) (session.RawStore, error) { + expiry := time.Now().Unix() var data []byte - err := p.c.QueryRow("SELECT data FROM session WHERE `key`=?", sid).Scan(&data) + err := p.c.QueryRow("SELECT data,expiry FROM session WHERE `key`=?", sid).Scan(&data, &expiry) if err == sql.ErrNoRows { _, err = p.c.Exec("INSERT INTO session(`key`,data,expiry) VALUES(?,?,?)", - sid, "", time.Now().Unix()) + sid, "", expiry) } if err != nil { return nil, err @@ -137,7 +152,7 @@ func (p *MysqlProvider) Read(sid string) (session.RawStore, error) { } } - return NewMysqlStore(p.c, sid, kv), nil + return NewMysqlStore(p.c, sid, kv, expiry), nil } // Exist returns true if session with given ID exists. From a007730f5d8f7ffc7bafcbc1f19034caf64e122f Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 18 Dec 2018 17:20:09 +0100 Subject: [PATCH 031/139] Another take on resizing the panel, now using react-draggable --- .../dashboard/dashgrid/DashboardPanel.tsx | 20 ++++-- .../dashboard/dashgrid/PanelResizer.tsx | 69 +++++++++++++++++++ public/sass/_variables.dark.scss | 4 ++ public/sass/_variables.light.scss | 4 ++ public/sass/components/_panel_editor.scss | 55 ++++++--------- 5 files changed, 114 insertions(+), 38 deletions(-) create mode 100644 public/app/features/dashboard/dashgrid/PanelResizer.tsx diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index 6ee83d6e5b9..481dd2d5e19 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -14,6 +14,7 @@ import { PanelEditor } from './PanelEditor'; import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; import { PanelPlugin } from 'app/types'; +import { PanelResizer } from './PanelResizer'; export interface Props { panel: PanelModel; @@ -158,10 +159,21 @@ export class DashboardPanel extends PureComponent { return (
-
- {plugin.exports.Panel && this.renderReactPanel()} - {plugin.exports.PanelCtrl && this.renderAngularPanel()} -
+ ( +
+ {plugin.exports.Panel && this.renderReactPanel()} + {plugin.exports.PanelCtrl && this.renderAngularPanel()} +
+ )} + /> {panel.isEditing && ( JSX.Element; + panel: PanelModel; +} + +interface State { + editorHeight: number; +} + +export class PanelResizer extends PureComponent { + initialHeight: number = Math.floor(document.documentElement.scrollHeight * 0.4); + prevEditorHeight: number; + debouncedChangeHeight: (height: number) => void; + debouncedResizeDone: () => void; + + constructor(props) { + super(props); + const { panel } = this.props; + + this.state = { + editorHeight: this.initialHeight, + }; + + this.debouncedChangeHeight = throttle(this.changeHeight, 20, { trailing: true }); + this.debouncedResizeDone = debounce(() => { + panel.resizeDone(); + }, 200); + } + + changeHeight = height => { + this.prevEditorHeight = this.state.editorHeight; + this.setState({ + editorHeight: height, + }); + }; + + onDrag = (evt, data) => { + const newHeight = this.state.editorHeight + data.y; + this.debouncedChangeHeight(newHeight); + this.debouncedResizeDone(); + }; + + render() { + const { render, isEditing } = this.props; + const { editorHeight } = this.state; + + return ( + <> + {render(isEditing ? editorHeight : 'inherit')} + {isEditing && ( +
+ +
+
+
+ +
+ )} + + ); + } +} diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index cab0eb76dde..70db51a0fb2 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -400,3 +400,7 @@ $logs-color-unkown: $gray-2; $button-toggle-group-btn-active-bg: linear-gradient(90deg, $orange, $red); $button-toggle-group-btn-active-shadow: inset 0 0 4px $black; $button-toggle-group-btn-seperator-border: 1px solid $page-bg; + +$vertical-resize-handle-bg: $dark-5; +$vertical-resize-handle-dots: $gray-1; +$vertical-resize-handle-dots-hover: $gray-2; diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 16bb341ba27..6afd087a849 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -409,3 +409,7 @@ $logs-color-unkown: $gray-5; $button-toggle-group-btn-active-bg: $brand-primary; $button-toggle-group-btn-active-shadow: inset 0 0 4px $white; $button-toggle-group-btn-seperator-border: 1px solid $gray-6; + +$vertical-resize-handle-bg: $gray-4; +$vertical-resize-handle-dots: $gray-3; +$vertical-resize-handle-dots-hover: $gray-2; diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 9d0ad0703dc..871bc0a6747 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -84,46 +84,34 @@ } } -.panel-editor-resizer { - position: absolute; - height: 2px; - width: 100%; - top: -23px; - text-align: center; - border-bottom: 2px dashed transparent; - - &:hover { - transition: border-color 0.2s ease-in 0.4s; - transition-delay: 0.2s; - border-color: $text-color-faint; - } +.panel-editor-container__resizer { + position: relative; + margin-top: -3px; } .panel-editor-resizer__handle { - display: inline-block; - width: 180px; position: relative; - border-radius: 2px; - height: 7px; - cursor: grabbing; - background: $input-label-bg; - top: -9px; + display: block; + background: $vertical-resize-handle-bg; + width: 150px; + margin-left: -75px; + height: 6px; + cursor: ns-resize; + border-radius: 3px; + margin: 0 auto; - &:hover { - transition: background 0.2s ease-in 0.4s; - transition-delay: 0.2s; - background: linear-gradient(90deg, $orange, $red); - .panel-editor-resizer__handle-dots { - transition: opacity 0.2s ease-in; - opacity: 0; - } + &::before { + content: ' '; + position: absolute; + left: 10px; + right: 10px; + top: 2px; + border-top: 2px dotted $vertical-resize-handle-dots; } -} -.panel-editor-resizer__handle-dots { - border-top: 2px dashed $text-color-faint; - position: relative; - top: 4px; + &:hover::before { + border-color: $vertical-resize-handle-dots-hover; + } } .viz-picker { @@ -149,7 +137,6 @@ display: flex; margin-right: 10px; margin-bottom: 10px; - //border: 1px solid transparent; align-items: center; justify-content: center; padding-bottom: 6px; From e82b3632f6e79f6237ecb576095ce129539dc235 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 18 Dec 2018 19:44:17 +0100 Subject: [PATCH 032/139] fix signed in user for orgId=0 result should return active org id --- pkg/services/sqlstore/user.go | 7 ++++++- pkg/services/sqlstore/user_test.go | 23 ++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index a3ccb93b30c..312877751c9 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -345,8 +345,12 @@ func GetUserOrgList(query *m.GetUserOrgListQuery) error { return err } +func newSignedInUserCacheKey(orgID, userID int64) string { + return fmt.Sprintf("signed-in-user-%d-%d", userID, orgID) +} + func (ss *SqlStore) GetSignedInUserWithCache(query *m.GetSignedInUserQuery) error { - cacheKey := fmt.Sprintf("signed-in-user-%d-%d", query.UserId, query.OrgId) + cacheKey := newSignedInUserCacheKey(query.OrgId, query.UserId) if cached, found := ss.CacheService.Get(cacheKey); found { query.Result = cached.(*m.SignedInUser) return nil @@ -357,6 +361,7 @@ func (ss *SqlStore) GetSignedInUserWithCache(query *m.GetSignedInUserQuery) erro return err } + cacheKey = newSignedInUserCacheKey(query.Result.OrgId, query.UserId) ss.CacheService.Set(cacheKey, query.Result, time.Second*5) return nil } diff --git a/pkg/services/sqlstore/user_test.go b/pkg/services/sqlstore/user_test.go index 627f2ab1ca5..526c17a8256 100644 --- a/pkg/services/sqlstore/user_test.go +++ b/pkg/services/sqlstore/user_test.go @@ -13,7 +13,7 @@ import ( func TestUserDataAccess(t *testing.T) { Convey("Testing DB", t, func() { - InitTestDB(t) + ss := InitTestDB(t) Convey("Creating a user", func() { cmd := &m.CreateUserCommand{ @@ -153,6 +153,27 @@ func TestUserDataAccess(t *testing.T) { So(prefsQuery.Result.UserId, ShouldEqual, 0) }) }) + + Convey("when retreiving signed in user for orgId=0 result should return active org id", func() { + ss.CacheService.Flush() + + query := &m.GetSignedInUserQuery{OrgId: users[1].OrgId, UserId: users[1].Id} + err := ss.GetSignedInUserWithCache(query) + So(err, ShouldBeNil) + So(query.Result, ShouldNotBeNil) + So(query.OrgId, ShouldEqual, users[1].OrgId) + err = SetUsingOrg(&m.SetUsingOrgCommand{UserId: users[1].Id, OrgId: users[0].OrgId}) + So(err, ShouldBeNil) + query = &m.GetSignedInUserQuery{OrgId: 0, UserId: users[1].Id} + err = ss.GetSignedInUserWithCache(query) + So(err, ShouldBeNil) + So(query.Result, ShouldNotBeNil) + So(query.Result.OrgId, ShouldEqual, users[0].OrgId) + + cacheKey := newSignedInUserCacheKey(query.Result.OrgId, query.UserId) + _, found := ss.CacheService.Get(cacheKey) + So(found, ShouldBeTrue) + }) }) }) From 09b3014683050992571573ed9e09c92b69b7db4f Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 18 Dec 2018 21:47:45 +0100 Subject: [PATCH 033/139] fix only create/drop database indices if not exists/exists --- pkg/services/sqlstore/migrator/conditions.go | 31 +++++++++++++++++-- pkg/services/sqlstore/migrator/dialect.go | 3 +- pkg/services/sqlstore/migrator/migrations.go | 18 ++++------- pkg/services/sqlstore/migrator/migrator.go | 21 ++++++++----- .../sqlstore/migrator/mysql_dialect.go | 12 +++---- .../sqlstore/migrator/postgres_dialect.go | 6 ++-- .../sqlstore/migrator/sqlite_dialect.go | 7 +++-- 7 files changed, 63 insertions(+), 35 deletions(-) diff --git a/pkg/services/sqlstore/migrator/conditions.go b/pkg/services/sqlstore/migrator/conditions.go index 79bf7c7ed27..8817b43f01c 100644 --- a/pkg/services/sqlstore/migrator/conditions.go +++ b/pkg/services/sqlstore/migrator/conditions.go @@ -2,12 +2,37 @@ package migrator type MigrationCondition interface { Sql(dialect Dialect) (string, []interface{}) + IsFulfilled(results []map[string][]byte) bool } -type IfTableExistsCondition struct { +type ExistsMigrationCondition struct{} + +func (c *ExistsMigrationCondition) IsFulfilled(results []map[string][]byte) bool { + return len(results) == 1 +} + +type NotExistsMigrationCondition struct{} + +func (c *NotExistsMigrationCondition) IsFulfilled(results []map[string][]byte) bool { + return len(results) == 0 +} + +type IfIndexExistsCondition struct { + ExistsMigrationCondition + TableName string + IndexName string +} + +func (c *IfIndexExistsCondition) Sql(dialect Dialect) (string, []interface{}) { + return dialect.IndexCheckSql(c.TableName, c.IndexName) +} + +type IfIndexNotExistsCondition struct { + NotExistsMigrationCondition TableName string + IndexName string } -func (c *IfTableExistsCondition) Sql(dialect Dialect) (string, []interface{}) { - return dialect.TableCheckSql(c.TableName) +func (c *IfIndexNotExistsCondition) Sql(dialect Dialect) (string, []interface{}) { + return dialect.IndexCheckSql(c.TableName, c.IndexName) } diff --git a/pkg/services/sqlstore/migrator/dialect.go b/pkg/services/sqlstore/migrator/dialect.go index 506a01c3ed8..e8ea07e007c 100644 --- a/pkg/services/sqlstore/migrator/dialect.go +++ b/pkg/services/sqlstore/migrator/dialect.go @@ -29,10 +29,11 @@ type Dialect interface { DropTable(tableName string) string DropIndexSql(tableName string, index *Index) string - TableCheckSql(tableName string) (string, []interface{}) RenameTable(oldName string, newName string) string UpdateTableSql(tableName string, columns []*Column) string + IndexCheckSql(tableName, indexName string) (string, []interface{}) + ColString(*Column) string ColStringNoPk(*Column) string diff --git a/pkg/services/sqlstore/migrator/migrations.go b/pkg/services/sqlstore/migrator/migrations.go index fd71cc3d290..d5a4158c52a 100644 --- a/pkg/services/sqlstore/migrator/migrations.go +++ b/pkg/services/sqlstore/migrator/migrations.go @@ -109,7 +109,9 @@ type AddIndexMigration struct { } func NewAddIndexMigration(table Table, index *Index) *AddIndexMigration { - return &AddIndexMigration{tableName: table.Name, index: index} + m := &AddIndexMigration{tableName: table.Name, index: index} + m.Condition = &IfIndexNotExistsCondition{TableName: table.Name, IndexName: index.XName(table.Name)} + return m } func (m *AddIndexMigration) Table(tableName string) *AddIndexMigration { @@ -128,7 +130,9 @@ type DropIndexMigration struct { } func NewDropIndexMigration(table Table, index *Index) *DropIndexMigration { - return &DropIndexMigration{tableName: table.Name, index: index} + m := &DropIndexMigration{tableName: table.Name, index: index} + m.Condition = &IfIndexExistsCondition{TableName: table.Name, IndexName: index.XName(table.Name)} + return m } func (m *DropIndexMigration) Sql(dialect Dialect) string { @@ -179,11 +183,6 @@ func NewRenameTableMigration(oldName string, newName string) *RenameTableMigrati return &RenameTableMigration{oldName: oldName, newName: newName} } -func (m *RenameTableMigration) IfTableExists(tableName string) *RenameTableMigration { - m.Condition = &IfTableExistsCondition{TableName: tableName} - return m -} - func (m *RenameTableMigration) Rename(oldName string, newName string) *RenameTableMigration { m.oldName = oldName m.newName = newName @@ -212,11 +211,6 @@ func NewCopyTableDataMigration(targetTable string, sourceTable string, colMap ma return m } -func (m *CopyTableDataMigration) IfTableExists(tableName string) *CopyTableDataMigration { - m.Condition = &IfTableExistsCondition{TableName: tableName} - return m -} - func (m *CopyTableDataMigration) Sql(d Dialect) string { return d.CopyTableData(m.sourceTable, m.targetTable, m.sourceCols, m.targetCols) } diff --git a/pkg/services/sqlstore/migrator/migrator.go b/pkg/services/sqlstore/migrator/migrator.go index dead6f2b416..b99d397f365 100644 --- a/pkg/services/sqlstore/migrator/migrator.go +++ b/pkg/services/sqlstore/migrator/migrator.go @@ -94,8 +94,6 @@ func (mg *Migrator) Start() error { Timestamp: time.Now(), } - mg.Logger.Debug("Executing", "sql", sql) - err := mg.inTransaction(func(sess *xorm.Session) error { err := mg.exec(m, sess) if err != nil { @@ -123,18 +121,27 @@ func (mg *Migrator) exec(m Migration, sess *xorm.Session) error { condition := m.GetCondition() if condition != nil { sql, args := condition.Sql(mg.Dialect) - results, err := sess.SQL(sql).Query(args...) - if err != nil || len(results) == 0 { - mg.Logger.Debug("Skipping migration condition not fulfilled", "id", m.Id()) - return sess.Rollback() + mg.Logger.Debug("Executing migration condition sql", "id", m.Id(), "sql", sql, "args", args) + results, err := sess.SQL(sql, args...).Query() + if err != nil { + mg.Logger.Error("Executing migration condition failed", "id", m.Id(), "error", err) + return err + } + + if !condition.IsFulfilled(results) { + mg.Logger.Warn("Skipping migration: Already executed, but not recorded in migration log", "id", m.Id()) + return nil } } var err error if codeMigration, ok := m.(CodeMigration); ok { + mg.Logger.Debug("Executing code migration", "id", m.Id()) err = codeMigration.Exec(sess, mg) } else { - _, err = sess.Exec(m.Sql(mg.Dialect)) + sql := m.Sql(mg.Dialect) + mg.Logger.Debug("Executing sql migration", "id", m.Id(), "sql", sql) + _, err = sess.Exec(sql) } if err != nil { diff --git a/pkg/services/sqlstore/migrator/mysql_dialect.go b/pkg/services/sqlstore/migrator/mysql_dialect.go index 7daa4597430..b5e16d8c500 100644 --- a/pkg/services/sqlstore/migrator/mysql_dialect.go +++ b/pkg/services/sqlstore/migrator/mysql_dialect.go @@ -90,12 +90,6 @@ func (db *Mysql) SqlType(c *Column) string { return res } -func (db *Mysql) TableCheckSql(tableName string) (string, []interface{}) { - args := []interface{}{"grafana", tableName} - sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?" - return sql, args -} - func (db *Mysql) UpdateTableSql(tableName string, columns []*Column) string { var statements = []string{} @@ -108,6 +102,12 @@ func (db *Mysql) UpdateTableSql(tableName string, columns []*Column) string { return "ALTER TABLE " + db.Quote(tableName) + " " + strings.Join(statements, ", ") + ";" } +func (db *Mysql) IndexCheckSql(tableName, indexName string) (string, []interface{}) { + args := []interface{}{tableName, indexName} + sql := "SELECT 1 FROM " + db.Quote("INFORMATION_SCHEMA") + "." + db.Quote("STATISTICS") + " WHERE " + db.Quote("TABLE_SCHEMA") + " = DATABASE() AND " + db.Quote("TABLE_NAME") + "=? AND " + db.Quote("INDEX_NAME") + "=?" + return sql, args +} + func (db *Mysql) CleanDB() error { tables, _ := db.engine.DBMetas() sess := db.engine.NewSession() diff --git a/pkg/services/sqlstore/migrator/postgres_dialect.go b/pkg/services/sqlstore/migrator/postgres_dialect.go index ab8812a1e26..ce529920a39 100644 --- a/pkg/services/sqlstore/migrator/postgres_dialect.go +++ b/pkg/services/sqlstore/migrator/postgres_dialect.go @@ -101,9 +101,9 @@ func (db *Postgres) SqlType(c *Column) string { return res } -func (db *Postgres) TableCheckSql(tableName string) (string, []interface{}) { - args := []interface{}{"grafana", tableName} - sql := "SELECT table_name FROM information_schema.tables WHERE table_schema=? and table_name=?" +func (db *Postgres) IndexCheckSql(tableName, indexName string) (string, []interface{}) { + args := []interface{}{tableName, indexName} + sql := "SELECT 1 FROM " + db.Quote("pg_indexes") + " WHERE" + db.Quote("tablename") + "=? AND " + db.Quote("indexname") + "=?" return sql, args } diff --git a/pkg/services/sqlstore/migrator/sqlite_dialect.go b/pkg/services/sqlstore/migrator/sqlite_dialect.go index 446e3fcef12..9c0dec05727 100644 --- a/pkg/services/sqlstore/migrator/sqlite_dialect.go +++ b/pkg/services/sqlstore/migrator/sqlite_dialect.go @@ -68,9 +68,10 @@ func (db *Sqlite3) SqlType(c *Column) string { } } -func (db *Sqlite3) TableCheckSql(tableName string) (string, []interface{}) { - args := []interface{}{tableName} - return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args +func (db *Sqlite3) IndexCheckSql(tableName, indexName string) (string, []interface{}) { + args := []interface{}{tableName, indexName} + sql := "SELECT 1 FROM " + db.Quote("sqlite_master") + " WHERE " + db.Quote("type") + "='index' AND " + db.Quote("tbl_name") + "=? AND " + db.Quote("name") + "=?" + return sql, args } func (db *Sqlite3) DropIndexSql(tableName string, index *Index) string { From a451c201291e80fd4e398e34f4ccd46fae2acb20 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 18 Dec 2018 22:28:27 +0100 Subject: [PATCH 034/139] fix handling of indices with multiple columns (mysql) --- pkg/services/sqlstore/migrator/conditions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/sqlstore/migrator/conditions.go b/pkg/services/sqlstore/migrator/conditions.go index 8817b43f01c..b9b145de326 100644 --- a/pkg/services/sqlstore/migrator/conditions.go +++ b/pkg/services/sqlstore/migrator/conditions.go @@ -8,7 +8,7 @@ type MigrationCondition interface { type ExistsMigrationCondition struct{} func (c *ExistsMigrationCondition) IsFulfilled(results []map[string][]byte) bool { - return len(results) == 1 + return len(results) >= 1 } type NotExistsMigrationCondition struct{} From ea84ec6229ed8203205580c49a8512b37342d457 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 18 Dec 2018 22:49:38 +0100 Subject: [PATCH 035/139] changelog: adds note about closing #14109 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92366849a44..b74c4db9f7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### New Features * **Alerting**: Adds support for Google Hangouts Chat notifications [#11221](https://github.com/grafana/grafana/issues/11221), thx [@PatrickSchuster](https://github.com/PatrickSchuster) +* **Snapshots**: Enable deletion of public snapshot [#14109](https://github.com/grafana/grafana/issues/14109) ### Minor From 12f3c8f9d677f00c14c44da2425990555824afd4 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 18 Dec 2018 23:02:08 +0100 Subject: [PATCH 036/139] fix only add column if not exists for mysql --- pkg/services/sqlstore/migrator/conditions.go | 10 +++++++++ pkg/services/sqlstore/migrator/dialect.go | 5 +++++ pkg/services/sqlstore/migrator/migrations.go | 4 +++- pkg/services/sqlstore/migrator/migrator.go | 21 +++++++++++-------- .../sqlstore/migrator/mysql_dialect.go | 6 ++++++ 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/pkg/services/sqlstore/migrator/conditions.go b/pkg/services/sqlstore/migrator/conditions.go index b9b145de326..b6adfdbfa29 100644 --- a/pkg/services/sqlstore/migrator/conditions.go +++ b/pkg/services/sqlstore/migrator/conditions.go @@ -36,3 +36,13 @@ type IfIndexNotExistsCondition struct { func (c *IfIndexNotExistsCondition) Sql(dialect Dialect) (string, []interface{}) { return dialect.IndexCheckSql(c.TableName, c.IndexName) } + +type IfColumnNotExistsCondition struct { + NotExistsMigrationCondition + TableName string + ColumnName string +} + +func (c *IfColumnNotExistsCondition) Sql(dialect Dialect) (string, []interface{}) { + return dialect.ColumnCheckSql(c.TableName, c.ColumnName) +} diff --git a/pkg/services/sqlstore/migrator/dialect.go b/pkg/services/sqlstore/migrator/dialect.go index e8ea07e007c..d2e3c3e0777 100644 --- a/pkg/services/sqlstore/migrator/dialect.go +++ b/pkg/services/sqlstore/migrator/dialect.go @@ -33,6 +33,7 @@ type Dialect interface { UpdateTableSql(tableName string, columns []*Column) string IndexCheckSql(tableName, indexName string) (string, []interface{}) + ColumnCheckSql(tableName, columnName string) (string, []interface{}) ColString(*Column) string ColStringNoPk(*Column) string @@ -183,6 +184,10 @@ func (db *BaseDialect) RenameTable(oldName string, newName string) string { return fmt.Sprintf("ALTER TABLE %s RENAME TO %s", quote(oldName), quote(newName)) } +func (db *BaseDialect) ColumnCheckSql(tableName, columnName string) (string, []interface{}) { + return "", nil +} + func (db *BaseDialect) DropIndexSql(tableName string, index *Index) string { quote := db.dialect.Quote name := index.XName(tableName) diff --git a/pkg/services/sqlstore/migrator/migrations.go b/pkg/services/sqlstore/migrator/migrations.go index d5a4158c52a..cb32c6212e6 100644 --- a/pkg/services/sqlstore/migrator/migrations.go +++ b/pkg/services/sqlstore/migrator/migrations.go @@ -85,7 +85,9 @@ type AddColumnMigration struct { } func NewAddColumnMigration(table Table, col *Column) *AddColumnMigration { - return &AddColumnMigration{tableName: table.Name, column: col} + m := &AddColumnMigration{tableName: table.Name, column: col} + m.Condition = &IfColumnNotExistsCondition{TableName: table.Name, ColumnName: col.Name} + return m } func (m *AddColumnMigration) Table(tableName string) *AddColumnMigration { diff --git a/pkg/services/sqlstore/migrator/migrator.go b/pkg/services/sqlstore/migrator/migrator.go index b99d397f365..ce0b2663def 100644 --- a/pkg/services/sqlstore/migrator/migrator.go +++ b/pkg/services/sqlstore/migrator/migrator.go @@ -121,16 +121,19 @@ func (mg *Migrator) exec(m Migration, sess *xorm.Session) error { condition := m.GetCondition() if condition != nil { sql, args := condition.Sql(mg.Dialect) - mg.Logger.Debug("Executing migration condition sql", "id", m.Id(), "sql", sql, "args", args) - results, err := sess.SQL(sql, args...).Query() - if err != nil { - mg.Logger.Error("Executing migration condition failed", "id", m.Id(), "error", err) - return err - } - if !condition.IsFulfilled(results) { - mg.Logger.Warn("Skipping migration: Already executed, but not recorded in migration log", "id", m.Id()) - return nil + if sql != "" { + mg.Logger.Debug("Executing migration condition sql", "id", m.Id(), "sql", sql, "args", args) + results, err := sess.SQL(sql, args...).Query() + if err != nil { + mg.Logger.Error("Executing migration condition failed", "id", m.Id(), "error", err) + return err + } + + if !condition.IsFulfilled(results) { + mg.Logger.Warn("Skipping migration: Already executed, but not recorded in migration log", "id", m.Id()) + return nil + } } } diff --git a/pkg/services/sqlstore/migrator/mysql_dialect.go b/pkg/services/sqlstore/migrator/mysql_dialect.go index b5e16d8c500..4de55c9a12f 100644 --- a/pkg/services/sqlstore/migrator/mysql_dialect.go +++ b/pkg/services/sqlstore/migrator/mysql_dialect.go @@ -108,6 +108,12 @@ func (db *Mysql) IndexCheckSql(tableName, indexName string) (string, []interface return sql, args } +func (db *Mysql) ColumnCheckSql(tableName, columnName string) (string, []interface{}) { + args := []interface{}{tableName, columnName} + sql := "SELECT 1 FROM " + db.Quote("INFORMATION_SCHEMA") + "." + db.Quote("COLUMNS") + " WHERE " + db.Quote("TABLE_SCHEMA") + " = DATABASE() AND " + db.Quote("TABLE_NAME") + "=? AND " + db.Quote("COLUMN_NAME") + "=?" + return sql, args +} + func (db *Mysql) CleanDB() error { tables, _ := db.engine.DBMetas() sess := db.engine.NewSession() From 34d3086ec83453a6df8451713c64ae64545a53a7 Mon Sep 17 00:00:00 2001 From: Jacob Richard Date: Tue, 18 Dec 2018 21:16:29 -0600 Subject: [PATCH 037/139] Adding tests for auth proxy CIDR support --- pkg/middleware/middleware_test.go | 90 +++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go index e9a3c8059f8..b9a8afce6c6 100644 --- a/pkg/middleware/middleware_test.go +++ b/pkg/middleware/middleware_test.go @@ -271,6 +271,23 @@ func TestMiddlewareContext(t *testing.T) { }) }) + middlewareScenario("When auth_proxy is enabled and IPv4 request RemoteAddr is not within trusted CIDR block", func(sc *scenarioContext) { + setting.AuthProxyEnabled = true + setting.AuthProxyHeaderName = "X-WEBAUTH-USER" + setting.AuthProxyHeaderProperty = "username" + setting.AuthProxyWhitelist = "192.168.1.0/24, 2001::0/120" + + sc.fakeReq("GET", "/") + sc.req.Header.Add("X-WEBAUTH-USER", "torkelo") + sc.req.RemoteAddr = "192.168.3.1:12345" + sc.exec() + + Convey("should return 407 status code", func() { + So(sc.resp.Code, ShouldEqual, 407) + So(sc.resp.Body.String(), ShouldContainSubstring, "Request for user (torkelo) from 192.168.3.1 is not from the authentication proxy") + }) + }) + middlewareScenario("When auth_proxy is enabled and IPv6 request RemoteAddr is not trusted", func(sc *scenarioContext) { setting.AuthProxyEnabled = true setting.AuthProxyHeaderName = "X-WEBAUTH-USER" @@ -288,6 +305,23 @@ func TestMiddlewareContext(t *testing.T) { }) }) + middlewareScenario("When auth_proxy is enabled and IPv6 request RemoteAddr is not within trusted CIDR block", func(sc *scenarioContext) { + setting.AuthProxyEnabled = true + setting.AuthProxyHeaderName = "X-WEBAUTH-USER" + setting.AuthProxyHeaderProperty = "username" + setting.AuthProxyWhitelist = "192.168.1.0/24, 2001::0/120" + + sc.fakeReq("GET", "/") + sc.req.Header.Add("X-WEBAUTH-USER", "torkelo") + sc.req.RemoteAddr = "[2001:23]:12345" + sc.exec() + + Convey("should return 407 status code", func() { + So(sc.resp.Code, ShouldEqual, 407) + So(sc.resp.Body.String(), ShouldContainSubstring, "Request for user (torkelo) from 2001:23 is not from the authentication proxy") + }) + }) + middlewareScenario("When auth_proxy is enabled and request RemoteAddr is trusted", func(sc *scenarioContext) { setting.AuthProxyEnabled = true setting.AuthProxyHeaderName = "X-WEBAUTH-USER" @@ -316,6 +350,62 @@ func TestMiddlewareContext(t *testing.T) { }) }) + middlewareScenario("When auth_proxy is enabled and IPv4 request RemoteAddr is within trusted CIDR block", func(sc *scenarioContext) { + setting.AuthProxyEnabled = true + setting.AuthProxyHeaderName = "X-WEBAUTH-USER" + setting.AuthProxyHeaderProperty = "username" + setting.AuthProxyWhitelist = "192.168.1.0/24, 2001::0/120" + + bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error { + query.Result = &m.SignedInUser{OrgId: 4, UserId: 33} + return nil + }) + + bus.AddHandler("test", func(cmd *m.UpsertUserCommand) error { + cmd.Result = &m.User{Id: 33} + return nil + }) + + sc.fakeReq("GET", "/") + sc.req.Header.Add("X-WEBAUTH-USER", "torkelo") + sc.req.RemoteAddr = "192.168.1.10:12345" + sc.exec() + + Convey("Should init context with user info", func() { + So(sc.context.IsSignedIn, ShouldBeTrue) + So(sc.context.UserId, ShouldEqual, 33) + So(sc.context.OrgId, ShouldEqual, 4) + }) + }) + + middlewareScenario("When auth_proxy is enabled and IPv6 request RemoteAddr is within trusted CIDR block", func(sc *scenarioContext) { + setting.AuthProxyEnabled = true + setting.AuthProxyHeaderName = "X-WEBAUTH-USER" + setting.AuthProxyHeaderProperty = "username" + setting.AuthProxyWhitelist = "192.168.1.0/24, 2001::0/120" + + bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error { + query.Result = &m.SignedInUser{OrgId: 4, UserId: 33} + return nil + }) + + bus.AddHandler("test", func(cmd *m.UpsertUserCommand) error { + cmd.Result = &m.User{Id: 33} + return nil + }) + + sc.fakeReq("GET", "/") + sc.req.Header.Add("X-WEBAUTH-USER", "torkelo") + sc.req.RemoteAddr = "[2001::23]:12345" + sc.exec() + + Convey("Should init context with user info", func() { + So(sc.context.IsSignedIn, ShouldBeTrue) + So(sc.context.UserId, ShouldEqual, 33) + So(sc.context.OrgId, ShouldEqual, 4) + }) + }) + middlewareScenario("When session exists for previous user, create a new session", func(sc *scenarioContext) { setting.AuthProxyEnabled = true setting.AuthProxyHeaderName = "X-WEBAUTH-USER" From a6d90151da8ff07138d18597c324d91aecc0179e Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 19 Dec 2018 08:58:14 +0100 Subject: [PATCH 038/139] changelog: adds note about closing #14546 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b74c4db9f7b..6f55381f5e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * **Templating**: Escaping "Custom" template variables [#13754](https://github.com/grafana/grafana/issues/13754), thx [@IntegersOfK](https://github.com/IntegersOfK) * **Admin**: When multiple user invitations, all links are the same as the first user who was invited [#14483](https://github.com/grafana/grafana/issues/14483) * **LDAP**: Upgrade go-ldap to v3 [#14548](https://github.com/grafana/grafana/issues/14548) +* **Proxy whitelist**: Add CIDR capability to auth_proxy whitelist [#14546](https://github.com/grafana/grafana/issues/14546), thx [@jacobrichard](https://github.com/jacobrichard) # 5.4.2 (2018-12-13) From 9cd0067187d591080ea64a6bc40ea8524abc75ba Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Wed, 19 Dec 2018 09:08:56 +0100 Subject: [PATCH 039/139] Add min/max height when resizing and replace debounce with throttle --- .../dashboard/dashgrid/PanelResizer.tsx | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelResizer.tsx b/public/app/features/dashboard/dashgrid/PanelResizer.tsx index 770952ae347..7d52076b54b 100644 --- a/public/app/features/dashboard/dashgrid/PanelResizer.tsx +++ b/public/app/features/dashboard/dashgrid/PanelResizer.tsx @@ -1,5 +1,5 @@ import React, { PureComponent } from 'react'; -import { debounce, throttle } from 'lodash'; +import { throttle } from 'lodash'; import Draggable from 'react-draggable'; import { PanelModel } from '../panel_model'; @@ -17,8 +17,8 @@ interface State { export class PanelResizer extends PureComponent { initialHeight: number = Math.floor(document.documentElement.scrollHeight * 0.4); prevEditorHeight: number; - debouncedChangeHeight: (height: number) => void; - debouncedResizeDone: () => void; + throttledChangeHeight: (height: number) => void; + throttledResizeDone: () => void; constructor(props) { super(props); @@ -28,13 +28,25 @@ export class PanelResizer extends PureComponent { editorHeight: this.initialHeight, }; - this.debouncedChangeHeight = throttle(this.changeHeight, 20, { trailing: true }); - this.debouncedResizeDone = debounce(() => { + this.throttledChangeHeight = throttle(this.changeHeight, 20, { trailing: true }); + this.throttledResizeDone = throttle(() => { panel.resizeDone(); - }, 200); + }, 50); + } + + get largestHeight() { + return document.documentElement.scrollHeight * 0.9; + } + get smallestHeight() { + return 100; } changeHeight = height => { + const sh = this.smallestHeight; + const lh = this.largestHeight; + height = height < sh ? sh : height; + height = height > lh ? lh : height; + this.prevEditorHeight = this.state.editorHeight; this.setState({ editorHeight: height, @@ -43,8 +55,8 @@ export class PanelResizer extends PureComponent { onDrag = (evt, data) => { const newHeight = this.state.editorHeight + data.y; - this.debouncedChangeHeight(newHeight); - this.debouncedResizeDone(); + this.throttledChangeHeight(newHeight); + this.throttledResizeDone(); }; render() { From 2046309de51fb85376ce50c9dc9c9da4f0cbfbee Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 19 Dec 2018 10:42:57 +0100 Subject: [PATCH 040/139] rewrite angular view --- .../features/alerting/partials/alert_tab.html | 336 ++++++++---------- 1 file changed, 155 insertions(+), 181 deletions(-) diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index 0e4e48a89a9..5fcc7ae5e44 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -1,191 +1,165 @@ -
-
- +
+
+ {{ctrl.error}} +
+
+
+
+
+
+ Name + +
+
+
+ Evaluate every + +
+
+ + + + If an alert rule has a configured For and the query violates the configured + threshold it + will first go from OK to Pending. + Going from OK to Pending Grafana will not send any notifications. Once the alert + rule + has + been firing for more than For duration, it will change to Alerting and send alert + notifications. + +
+
+
+
+
+
+
+
Conditions
+
+
+
+ + WHEN +
+
+ + + OF +
+
+ + +
+
+ + + + +
+
+ +
+
+
+ +
+
+
+ If no data or all values are null + SET STATE TO +
+ +
+
-
-
-
- {{ctrl.error}} -
+
+ If execution error or timeout + SET STATE TO +
+ +
+
-
-
Alert Config
-
- Name - -
-
-
- Evaluate every - -
-
- - - - If an alert rule has a configured For and the query violates the configured threshold it will first go from OK to Pending. - Going from OK to Pending Grafana will not send any notifications. Once the alert rule has been firing for more than For duration, it will change to Alerting and send alert notifications. - -
-
-
+
+ +
+
-
-
Conditions
-
-
- - WHEN -
-
- - - OF -
-
- - -
-
- - - - -
-
- -
-
+
+ Evaluating rule +
-
- -
-
- -
-
- If no data or all values are null - SET STATE TO -
- -
-
- -
- If execution error or timeout - SET STATE TO -
- -
-
- -
- -
-
- -
- Evaluating rule -
- -
- -
-
- -
-
Notifications
-
-
- Send to - +
+ +
+
+
+
+
Notifications
+
+
+
+ Send to +  {{nc.name}}  - + - -
-
-
- Message - -
-
- -
- -
- State history (last 50 state changes) -
- -
-
- No state changes recorded -
- -
    -
  1. -
    - -
    -
    -
    -
    - {{al.stateModel.text}} -
    -
    - {{al.info}} -
    -
    - {{al.time}} -
    -
  2. -
-
-
-
+ +
+
+
+ Message + +
+
+
-
-
Panel has no alert rule defined
- -
-
+
+
Panel has no alert rule defined
+ +
From 60ea99078ee7557908d0c598c1ff1b791c8daa89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 19 Dec 2018 10:53:14 +0100 Subject: [PATCH 041/139] Panel help view fixes --- pkg/api/plugins.go | 8 +++++ pkg/plugins/datasource_plugin.go | 12 -------- .../{PanelHelp => PluginHelp}/PluginHelp.tsx | 29 +++++-------------- .../dashboard/dashgrid/QueriesTab.tsx | 4 +-- .../dashboard/dashgrid/VisualizationTab.tsx | 10 ++----- public/app/plugins/panel/graph/README.md | 8 ++--- 6 files changed, 23 insertions(+), 48 deletions(-) rename public/app/core/components/{PanelHelp => PluginHelp}/PluginHelp.tsx (71%) diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index 455420e4688..0d54f0707a6 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -164,6 +164,14 @@ func GetPluginMarkdown(c *m.ReqContext) Response { return Error(500, "Could not get markdown file", err) } + // fallback try readme + if len(content) == 0 { + content, err = plugins.GetPluginMarkdown(pluginID, "readme") + if err != nil { + return Error(501, "Could not get markdown file", err) + } + } + resp := Respond(200, content) resp.Header("Content-Type", "text/plain; charset=utf-8") return resp diff --git a/pkg/plugins/datasource_plugin.go b/pkg/plugins/datasource_plugin.go index 04b77a892c5..dd4ae6972aa 100644 --- a/pkg/plugins/datasource_plugin.go +++ b/pkg/plugins/datasource_plugin.go @@ -3,10 +3,8 @@ package plugins import ( "context" "encoding/json" - "os" "os/exec" "path" - "path/filepath" "time" "github.com/grafana/grafana-plugin-model/go/datasource" @@ -29,7 +27,6 @@ type DataSourcePlugin struct { QueryOptions map[string]bool `json:"queryOptions,omitempty"` BuiltIn bool `json:"builtIn,omitempty"` Mixed bool `json:"mixed,omitempty"` - HasQueryHelp bool `json:"hasQueryHelp,omitempty"` Routes []*AppPluginRoute `json:"routes"` Backend bool `json:"backend,omitempty"` @@ -48,15 +45,6 @@ func (p *DataSourcePlugin) Load(decoder *json.Decoder, pluginDir string) error { return err } - // look for help markdown - helpPath := filepath.Join(p.PluginDir, "QUERY_HELP.md") - if _, err := os.Stat(helpPath); os.IsNotExist(err) { - helpPath = filepath.Join(p.PluginDir, "query_help.md") - } - if _, err := os.Stat(helpPath); err == nil { - p.HasQueryHelp = true - } - DataSources[p.Id] = p return nil } diff --git a/public/app/core/components/PanelHelp/PluginHelp.tsx b/public/app/core/components/PluginHelp/PluginHelp.tsx similarity index 71% rename from public/app/core/components/PanelHelp/PluginHelp.tsx rename to public/app/core/components/PluginHelp/PluginHelp.tsx index b43efae2d15..c37498afc45 100644 --- a/public/app/core/components/PanelHelp/PluginHelp.tsx +++ b/public/app/core/components/PluginHelp/PluginHelp.tsx @@ -1,10 +1,12 @@ import React, { PureComponent } from 'react'; import Remarkable from 'remarkable'; import { getBackendSrv } from '../../services/backend_srv'; -import { PluginMeta } from 'app/types'; interface Props { - plugin: PluginMeta; + plugin: { + name: string; + id: string; + }; type: string; } @@ -14,7 +16,7 @@ interface State { help: string; } -export default class PluginHelp extends PureComponent { +export class PluginHelp extends PureComponent { state = { isError: false, isLoading: false, @@ -25,24 +27,9 @@ export default class PluginHelp extends PureComponent { this.loadHelp(); } - constructPlaceholderInfo = () => { - const { plugin } = this.props; - const markdown = new Remarkable(); - - const fallBack = markdown.render( - `## ${plugin.name} \n by _${plugin.info.author.name} (<${plugin.info.author.url}>)_\n\n${ - plugin.info.description - }\n\n${ - plugin.info.links - ? `### Links \n ${plugin.info.links.map(link => { - return `${link.name}: <${link.url}>\n`; - })}` - : '' - }` - ); - - return fallBack; - }; + constructPlaceholderInfo() { + return 'No plugin help or readme markdown file was found'; + } loadHelp = () => { const { plugin, type } = this.props; diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 36f38cadbd3..9ad0bb3cadd 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -21,7 +21,7 @@ import config from 'app/core/config'; import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; import { DataSourceSelectItem, DataQuery } from 'app/types'; -import PluginHelp from '../../../core/components/PanelHelp/PluginHelp'; +import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp'; interface Props { panel: PanelModel; @@ -203,7 +203,6 @@ export class QueriesTab extends PureComponent { render() { const { panel } = this.props; const { currentDS, isAddingMixed } = this.state; - const { hasQueryHelp } = currentDS.meta; const queryInspector = { title: 'Query Inspector', @@ -213,7 +212,6 @@ export class QueriesTab extends PureComponent { const dsHelp = { heading: 'Help', icon: 'fa fa-question', - disabled: !hasQueryHelp, render: this.renderHelp, }; diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 70285124b16..060ebad835f 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -3,12 +3,11 @@ import React, { PureComponent } from 'react'; // Utils & Services import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; -import { getDatasourceSrv } from '../../plugins/datasource_srv'; // Components import { EditorTabBody } from './EditorTabBody'; import { VizTypePicker } from './VizTypePicker'; -import PluginHelp from 'app/core/components/PanelHelp/PluginHelp'; +import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp'; import { FadeIn } from 'app/core/components/Animations/FadeIn'; import { PanelOptionSection } from './PanelOptionSection'; @@ -16,7 +15,6 @@ import { PanelOptionSection } from './PanelOptionSection'; import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; import { PanelPlugin } from 'app/types/plugins'; -import { DataSourceSelectItem } from 'app/types'; interface Props { panel: PanelModel; @@ -27,7 +25,6 @@ interface Props { } interface State { - currentDataSource: DataSourceSelectItem; isVizPickerOpen: boolean; searchQuery: string; } @@ -36,16 +33,13 @@ export class VisualizationTab extends PureComponent { element: HTMLElement; angularOptions: AngularComponent; searchInput: HTMLElement; - dataSources: DataSourceSelectItem[] = getDatasourceSrv().getMetricSources(); constructor(props) { super(props); - const { panel } = props; this.state = { isVizPickerOpen: false, searchQuery: '', - currentDataSource: this.dataSources.find(datasource => datasource.value === panel.datasource), }; } @@ -205,7 +199,7 @@ export class VisualizationTab extends PureComponent { } }; - renderHelp = () => ; + renderHelp = () => ; render() { const { plugin } = this.props; diff --git a/public/app/plugins/panel/graph/README.md b/public/app/plugins/panel/graph/README.md index 2dc8682f0e3..e1184beb8e7 100644 --- a/public/app/plugins/panel/graph/README.md +++ b/public/app/plugins/panel/graph/README.md @@ -1,7 +1,7 @@ -# Graph Panel - Native Plugin +# Graph Panel -The Graph is the main graph panel and is **included** with Grafana. It provides a very rich set of graphing options. +This is the main Graph panel and is **included** with Grafana. It provides a very rich set of graphing options. -Read more about it here: +For full reference documentation: -[http://docs.grafana.org/reference/graph/](http://docs.grafana.org/reference/graph/) \ No newline at end of file +[http://docs.grafana.org/reference/graph/](http://docs.grafana.org/reference/graph/) From 9607b27935d499e82ee26b7623449530255cd099 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Wed, 19 Dec 2018 11:04:40 +0100 Subject: [PATCH 042/139] changelog: adds note about closing #14486 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f55381f5e2..688bb0fa2e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ * **LDAP**: Upgrade go-ldap to v3 [#14548](https://github.com/grafana/grafana/issues/14548) * **Proxy whitelist**: Add CIDR capability to auth_proxy whitelist [#14546](https://github.com/grafana/grafana/issues/14546), thx [@jacobrichard](https://github.com/jacobrichard) +### Bug fixes +* **Search**: Fix for issue with scrolling the "tags filter" dropdown, fixes [#14486](https://github.com/grafana/grafana/issues/14486) + # 5.4.2 (2018-12-13) * **Datasource admin**: Fix for issue creating new data source when same name exists [#14467](https://github.com/grafana/grafana/issues/14467) From 16ad0f65ea226c478cc4ee2d17974eba2bd64784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 19 Dec 2018 11:40:38 +0100 Subject: [PATCH 043/139] Increase recent and starred limit in search and home dashboard, closes #13950 --- public/app/core/services/search_srv.ts | 4 ++-- public/dashboards/home.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/app/core/services/search_srv.ts b/public/app/core/services/search_srv.ts index aa5e146530b..22d33921ebd 100644 --- a/public/app/core/services/search_srv.ts +++ b/public/app/core/services/search_srv.ts @@ -31,7 +31,7 @@ export class SearchSrv { } private queryForRecentDashboards() { - const dashIds = _.take(impressionSrv.getDashboardOpened(), 5); + const dashIds = _.take(impressionSrv.getDashboardOpened(), 30); if (dashIds.length === 0) { return Promise.resolve([]); } @@ -70,7 +70,7 @@ export class SearchSrv { return Promise.resolve(); } - return this.backendSrv.search({ starred: true, limit: 5 }).then(result => { + return this.backendSrv.search({ starred: true, limit: 30 }).then(result => { if (result.length > 0) { sections['starred'] = { title: 'Starred', diff --git a/public/dashboards/home.json b/public/dashboards/home.json index ff69bb6f856..55cf7242aa6 100644 --- a/public/dashboards/home.json +++ b/public/dashboards/home.json @@ -31,7 +31,7 @@ "folderId": 0, "headings": true, "id": 3, - "limit": 4, + "limit": 30, "links": [], "query": "", "recent": true, From f95359fb344ca94545d9d234cf7c43c289448358 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Wed, 19 Dec 2018 12:58:10 +0100 Subject: [PATCH 044/139] Notify user on query error --- .../features/dashboard/dashgrid/DataPanel.tsx | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index 9926410f40d..72f405dcbe7 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DataPanel.tsx @@ -1,5 +1,6 @@ // Library import React, { Component } from 'react'; +import Tooltip from 'app/core/components/Tooltip/Tooltip'; // Services import { getDatasourceSrv, DatasourceSrv } from 'app/features/plugins/datasource_srv'; @@ -138,7 +139,7 @@ export class DataPanel extends Component { const timeSeries = response.data; if (isFirstLoad && loading === LoadingState.Loading) { - return this.renderLoadingSpinner(); + return this.renderLoadingState(); } if (!queries.length) { @@ -151,7 +152,7 @@ export class DataPanel extends Component { return ( <> - {this.renderLoadingSpinner()} + {this.renderLoadingState()} {this.props.children({ timeSeries, loading, @@ -160,15 +161,26 @@ export class DataPanel extends Component { ); } - private renderLoadingSpinner(): JSX.Element { + private renderLoadingState(): JSX.Element { const { loading } = this.state; - if (loading === LoadingState.Loading) { return (
); + } else if (loading === LoadingState.Error) { + return ( + + + + + ); } return null; From f3ba3b4df0e4a71e32b0e70024a11ba9204665c5 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 19 Dec 2018 13:09:53 +0100 Subject: [PATCH 045/139] render editor toolbar buttons --- .../features/dashboard/dashgrid/AlertTab.tsx | 22 ++++++++++++++++++- .../dashboard/dashgrid/PanelEditor.tsx | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/AlertTab.tsx b/public/app/features/dashboard/dashgrid/AlertTab.tsx index 7df7864c758..f9f565222d8 100644 --- a/public/app/features/dashboard/dashgrid/AlertTab.tsx +++ b/public/app/features/dashboard/dashgrid/AlertTab.tsx @@ -3,9 +3,11 @@ import React, { PureComponent } from 'react'; import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; import { EditorTabBody } from './EditorTabBody'; import 'app/features/alerting/AlertTabCtrl'; +import { PanelModel } from '../panel_model'; interface Props { angularPanel?: AngularComponent; + panel: PanelModel; } export class AlertTab extends PureComponent { @@ -63,8 +65,26 @@ export class AlertTab extends PureComponent { } render() { + const { alert } = this.props.panel; + + const stateHistory = { + title: 'State history', + render: () => { + return
State history
; + }, + }; + + const deleteAlert = { + title: 'Delete button', + render: () => { + return
Hello
; + }, + }; + + const toolbarItems = alert ? [deleteAlert, stateHistory] : []; + return ( - +
(this.element = element)} /> ); diff --git a/public/app/features/dashboard/dashgrid/PanelEditor.tsx b/public/app/features/dashboard/dashgrid/PanelEditor.tsx index a746d6c4b91..b3d94f43487 100644 --- a/public/app/features/dashboard/dashgrid/PanelEditor.tsx +++ b/public/app/features/dashboard/dashgrid/PanelEditor.tsx @@ -54,7 +54,7 @@ export class PanelEditor extends PureComponent { case 'queries': return ; case 'alert': - return ; + return ; case 'visualization': return ( Date: Wed, 19 Dec 2018 13:38:49 +0100 Subject: [PATCH 046/139] export init notifier func makes it possible to validate that an notifier can be initialzed from the provisioning package --- pkg/services/alerting/notifier.go | 6 ++++-- pkg/services/alerting/test_notification.go | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/services/alerting/notifier.go b/pkg/services/alerting/notifier.go index 9ce50eadd6b..75c68615750 100644 --- a/pkg/services/alerting/notifier.go +++ b/pkg/services/alerting/notifier.go @@ -166,7 +166,7 @@ func (n *notificationService) getNeededNotifiers(orgId int64, notificationIds [] var result notifierStateSlice for _, notification := range query.Result { - not, err := n.createNotifierFor(notification) + not, err := InitNotifier(notification) if err != nil { n.log.Error("Could not create notifier", "notifier", notification.Id, "error", err) continue @@ -195,7 +195,8 @@ func (n *notificationService) getNeededNotifiers(orgId int64, notificationIds [] return result, nil } -func (n *notificationService) createNotifierFor(model *m.AlertNotification) (Notifier, error) { +// InitNotifier instantiate a new notifier based on the model +func InitNotifier(model *m.AlertNotification) (Notifier, error) { notifierPlugin, found := notifierFactories[model.Type] if !found { return nil, errors.New("Unsupported notification type") @@ -208,6 +209,7 @@ type NotifierFactory func(notification *m.AlertNotification) (Notifier, error) var notifierFactories = make(map[string]*NotifierPlugin) +// RegisterNotifier register an notifier func RegisterNotifier(plugin *NotifierPlugin) { notifierFactories[plugin.Type] = plugin } diff --git a/pkg/services/alerting/test_notification.go b/pkg/services/alerting/test_notification.go index 8aa1b80aa22..b6e59f694c8 100644 --- a/pkg/services/alerting/test_notification.go +++ b/pkg/services/alerting/test_notification.go @@ -32,7 +32,7 @@ func handleNotificationTestCommand(cmd *NotificationTestCommand) error { Settings: cmd.Settings, } - notifiers, err := notifier.createNotifierFor(model) + notifiers, err := InitNotifier(model) if err != nil { log.Error2("Failed to create notifier", "error", err.Error()) From 3aa24b3afa3a348c7ed3594757f307b283f0f840 Mon Sep 17 00:00:00 2001 From: Tomas Dabasinskas Date: Wed, 19 Dec 2018 14:59:33 +0200 Subject: [PATCH 047/139] Rename the setting and add description --- conf/defaults.ini | 2 +- conf/sample.ini | 5 ++++- pkg/social/social.go | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index 47f6a64eb8b..97c87edb8b2 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -335,7 +335,7 @@ tls_skip_verify_insecure = false tls_client_cert = tls_client_key = tls_client_ca = -broken_auth_header_provider = false +send_client_credentials_via_post = false #################################### Basic Auth ########################## [auth.basic] diff --git a/conf/sample.ini b/conf/sample.ini index b73ab850bbf..473e4e8450c 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -283,7 +283,10 @@ log_queries = ;tls_client_cert = ;tls_client_key = ;tls_client_ca = -;broken_auth_header_provider = false + +; Set to true to enable sending client_id and client_secret via POST body instead of Basic authentication HTTP header +; This might be required if the OAuth provider is not RFC6749 compliant, only supporting credentials passed via POST payload +;send_client_credentials_via_post = false #################################### Grafana.com Auth #################### [auth.grafana_com] diff --git a/pkg/social/social.go b/pkg/social/social.go index da827deaa03..0349a271865 100644 --- a/pkg/social/social.go +++ b/pkg/social/social.go @@ -79,7 +79,7 @@ func NewOAuthService() { TlsClientKey: sec.Key("tls_client_key").String(), TlsClientCa: sec.Key("tls_client_ca").String(), TlsSkipVerify: sec.Key("tls_skip_verify_insecure").MustBool(), - BrokenAuthHeaderProvider: sec.Key("broken_auth_header_provider").MustBool(), + BrokenAuthHeaderProvider: sec.Key("send_client_credentials_via_post").MustBool(), } if !info.Enabled { From e8823f71b0ec9ad13eed1247788bd27fc59236f7 Mon Sep 17 00:00:00 2001 From: Tomas Dabasinskas Date: Wed, 19 Dec 2018 15:29:49 +0200 Subject: [PATCH 048/139] Add documentation --- docs/sources/auth/generic-oauth.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/sources/auth/generic-oauth.md b/docs/sources/auth/generic-oauth.md index 6fa6531fc98..c3c44426ba7 100644 --- a/docs/sources/auth/generic-oauth.md +++ b/docs/sources/auth/generic-oauth.md @@ -17,7 +17,7 @@ can find examples using Okta, BitBucket, OneLogin and Azure. This callback URL must match the full HTTP address that you use in your browser to access Grafana, but with the prefix path of `/login/generic_oauth`. -You may have to set the `root_url` option of `[server]` for the callback URL to be +You may have to set the `root_url` option of `[server]` for the callback URL to be correct. For example in case you are serving Grafana behind a proxy. Example config: @@ -209,6 +209,17 @@ allowed_organizations = token_url = https://.my.centrify.com/OAuth2/Token/ ``` +## Set up OAuth2 with non-compliant providers + +Some OAuth2 providers might not support `client_id` and `client_secret` passed via Basic Authentication HTTP header, which +results in `invalid_client` error. To allow Grafana to authenticate via these type of providers, the client identifiers must be +send via POST body, which can be enabled via the following settings: + + ```bash + [auth.generic_oauth] + send_client_credentials_via_post = true + ``` +
From eb517a3791e079e53c69bbe2c5581a38ddfc1ba1 Mon Sep 17 00:00:00 2001 From: Tomas Dabasinskas Date: Wed, 19 Dec 2018 15:36:45 +0200 Subject: [PATCH 049/139] Update field name --- pkg/setting/setting_oauth.go | 30 +++++++++++++++--------------- pkg/social/social.go | 36 ++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/pkg/setting/setting_oauth.go b/pkg/setting/setting_oauth.go index aab80f6a05e..f0a3beccb44 100644 --- a/pkg/setting/setting_oauth.go +++ b/pkg/setting/setting_oauth.go @@ -1,21 +1,21 @@ package setting type OAuthInfo struct { - ClientId, ClientSecret string - Scopes []string - AuthUrl, TokenUrl string - Enabled bool - EmailAttributeName string - AllowedDomains []string - HostedDomain string - ApiUrl string - AllowSignup bool - Name string - TlsClientCert string - TlsClientKey string - TlsClientCa string - TlsSkipVerify bool - BrokenAuthHeaderProvider bool + ClientId, ClientSecret string + Scopes []string + AuthUrl, TokenUrl string + Enabled bool + EmailAttributeName string + AllowedDomains []string + HostedDomain string + ApiUrl string + AllowSignup bool + Name string + TlsClientCert string + TlsClientKey string + TlsClientCa string + TlsSkipVerify bool + SendClientCredentialsViaPost bool } type OAuther struct { diff --git a/pkg/social/social.go b/pkg/social/social.go index 0349a271865..60099a028d6 100644 --- a/pkg/social/social.go +++ b/pkg/social/social.go @@ -63,23 +63,23 @@ func NewOAuthService() { for _, name := range allOauthes { sec := setting.Raw.Section("auth." + name) info := &setting.OAuthInfo{ - ClientId: sec.Key("client_id").String(), - ClientSecret: sec.Key("client_secret").String(), - Scopes: util.SplitString(sec.Key("scopes").String()), - AuthUrl: sec.Key("auth_url").String(), - TokenUrl: sec.Key("token_url").String(), - ApiUrl: sec.Key("api_url").String(), - Enabled: sec.Key("enabled").MustBool(), - EmailAttributeName: sec.Key("email_attribute_name").String(), - AllowedDomains: util.SplitString(sec.Key("allowed_domains").String()), - HostedDomain: sec.Key("hosted_domain").String(), - AllowSignup: sec.Key("allow_sign_up").MustBool(), - Name: sec.Key("name").MustString(name), - TlsClientCert: sec.Key("tls_client_cert").String(), - TlsClientKey: sec.Key("tls_client_key").String(), - TlsClientCa: sec.Key("tls_client_ca").String(), - TlsSkipVerify: sec.Key("tls_skip_verify_insecure").MustBool(), - BrokenAuthHeaderProvider: sec.Key("send_client_credentials_via_post").MustBool(), + ClientId: sec.Key("client_id").String(), + ClientSecret: sec.Key("client_secret").String(), + Scopes: util.SplitString(sec.Key("scopes").String()), + AuthUrl: sec.Key("auth_url").String(), + TokenUrl: sec.Key("token_url").String(), + ApiUrl: sec.Key("api_url").String(), + Enabled: sec.Key("enabled").MustBool(), + EmailAttributeName: sec.Key("email_attribute_name").String(), + AllowedDomains: util.SplitString(sec.Key("allowed_domains").String()), + HostedDomain: sec.Key("hosted_domain").String(), + AllowSignup: sec.Key("allow_sign_up").MustBool(), + Name: sec.Key("name").MustString(name), + TlsClientCert: sec.Key("tls_client_cert").String(), + TlsClientKey: sec.Key("tls_client_key").String(), + TlsClientCa: sec.Key("tls_client_ca").String(), + TlsSkipVerify: sec.Key("tls_skip_verify_insecure").MustBool(), + SendClientCredentialsViaPost: sec.Key("send_client_credentials_via_post").MustBool(), } if !info.Enabled { @@ -87,7 +87,7 @@ func NewOAuthService() { } // handle the clients that do not properly support Basic auth headers and require passing client_id/client_secret via POST payload - if info.BrokenAuthHeaderProvider { + if info.SendClientCredentialsViaPost { oauth2.RegisterBrokenAuthHeaderProvider(info.TokenUrl) } From 7637ea55e4718c7aa1a47d85ee01e2e50919c762 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 19 Dec 2018 16:11:35 +0100 Subject: [PATCH 050/139] changelog: adds note about closing #14562 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 688bb0fa2e1..5b420dcc374 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * **Admin**: When multiple user invitations, all links are the same as the first user who was invited [#14483](https://github.com/grafana/grafana/issues/14483) * **LDAP**: Upgrade go-ldap to v3 [#14548](https://github.com/grafana/grafana/issues/14548) * **Proxy whitelist**: Add CIDR capability to auth_proxy whitelist [#14546](https://github.com/grafana/grafana/issues/14546), thx [@jacobrichard](https://github.com/jacobrichard) +* **OAuth**: Support OAuth providers that are not RFC6749 compliant [#14562](https://github.com/grafana/grafana/issues/14562), thx [@tdabasinskas](https://github.com/tdabasinskas) ### Bug fixes * **Search**: Fix for issue with scrolling the "tags filter" dropdown, fixes [#14486](https://github.com/grafana/grafana/issues/14486) From 5b83f6d49d2f8ab163b4f170cba97fdde60d988a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 19 Dec 2018 17:00:26 +0100 Subject: [PATCH 051/139] Fixes undefined issue with angular panels and editorTabs --- .../dashboard/dashgrid/VisualizationTab.tsx | 1 + public/app/features/panel/metrics_panel_ctrl.ts | 2 -- public/app/features/panel/panel_ctrl.ts | 16 +++++----------- public/app/plugins/panel/dashlist/module.ts | 1 - public/app/plugins/panel/pluginlist/module.ts | 1 - public/app/plugins/panel/text/module.ts | 1 - 6 files changed, 6 insertions(+), 16 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 060ebad835f..42d9bf6a6eb 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -106,6 +106,7 @@ export class VisualizationTab extends PureComponent { } const panelCtrl = scope.$$childHead.ctrl; + panelCtrl.initEditMode(); let template = ''; for (let i = 0; i < panelCtrl.editorTabs.length; i++) { diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index 443ae17d287..5557b477b8f 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -30,8 +30,6 @@ class MetricsPanelCtrl extends PanelCtrl { constructor($scope, $injector) { super($scope, $injector); - // make metrics tab the default - this.editorTabIndex = 1; this.$q = $injector.get('$q'); this.contextSrv = $injector.get('contextSrv'); this.datasourceSrv = $injector.get('datasourceSrv'); diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 86f80b114e3..2bd43ee2a29 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -18,7 +18,6 @@ export class PanelCtrl { panel: any; error: any; dashboard: any; - editorTabIndex: number; pluginName: string; pluginId: string; editorTabs: any; @@ -39,7 +38,7 @@ export class PanelCtrl { this.$location = $injector.get('$location'); this.$scope = $scope; this.$timeout = $injector.get('$timeout'); - this.editorTabIndex = 0; + this.editorTabs = []; this.events = this.panel.events; this.timing = {}; @@ -90,10 +89,10 @@ export class PanelCtrl { } initEditMode() { - this.editorTabs = []; - - this.editModeInitiated = true; - this.events.emit('init-edit-mode', null); + if (!this.editModeInitiated) { + this.editModeInitiated = true; + this.events.emit('init-edit-mode', null); + } } addEditorTab(title, directiveFn, index?, icon?) { @@ -212,11 +211,6 @@ export class PanelCtrl { this.containerHeight = $(window).height(); } - // hacky solution - if (this.panel.isEditing && !this.editModeInitiated) { - this.initEditMode(); - } - this.height = this.containerHeight - (PANEL_BORDER + PANEL_HEADER_HEIGHT); } diff --git a/public/app/plugins/panel/dashlist/module.ts b/public/app/plugins/panel/dashlist/module.ts index 1b260107587..ba6f1a8b4f4 100644 --- a/public/app/plugins/panel/dashlist/module.ts +++ b/public/app/plugins/panel/dashlist/module.ts @@ -60,7 +60,6 @@ class DashListCtrl extends PanelCtrl { } onInitEditMode() { - this.editorTabIndex = 1; this.modes = ['starred', 'search', 'recently viewed']; this.addEditorTab('Options', 'public/app/plugins/panel/dashlist/editor.html'); } diff --git a/public/app/plugins/panel/pluginlist/module.ts b/public/app/plugins/panel/pluginlist/module.ts index eeac352f799..55ca160652d 100644 --- a/public/app/plugins/panel/pluginlist/module.ts +++ b/public/app/plugins/panel/pluginlist/module.ts @@ -29,7 +29,6 @@ class PluginListCtrl extends PanelCtrl { } onInitEditMode() { - this.editorTabIndex = 1; this.addEditorTab('Options', 'public/app/plugins/panel/pluginlist/editor.html'); } diff --git a/public/app/plugins/panel/text/module.ts b/public/app/plugins/panel/text/module.ts index 874691fab97..08ab4cd2b96 100644 --- a/public/app/plugins/panel/text/module.ts +++ b/public/app/plugins/panel/text/module.ts @@ -43,7 +43,6 @@ export class TextPanelCtrl extends PanelCtrl { onInitEditMode() { this.addEditorTab('Options', 'public/app/plugins/panel/text/editor.html'); - this.editorTabIndex = 1; if (this.panel.mode === 'text') { this.panel.mode = 'markdown'; From 281312ffcd1bc2344834411c2a05f2bbe7f3183b Mon Sep 17 00:00:00 2001 From: Kofi Jedamzik Date: Wed, 19 Dec 2018 22:23:25 +0100 Subject: [PATCH 052/139] =?UTF-8?q?Add=20units=20for=20blood=20sugar=20con?= =?UTF-8?q?centration=20=F0=9F=92=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/app/core/utils/kbn.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/app/core/utils/kbn.ts b/public/app/core/utils/kbn.ts index b8539ea5013..d32844c44ed 100644 --- a/public/app/core/utils/kbn.ts +++ b/public/app/core/utils/kbn.ts @@ -629,6 +629,8 @@ kbn.valueFormats.conmgm3 = kbn.formatBuilders.fixedUnit('mg/m³'); kbn.valueFormats.conmgNm3 = kbn.formatBuilders.fixedUnit('mg/Nm³'); kbn.valueFormats.congm3 = kbn.formatBuilders.fixedUnit('g/m³'); kbn.valueFormats.congNm3 = kbn.formatBuilders.fixedUnit('g/Nm³'); +kbn.valueFormats.conmgdL = kbn.formatBuilders.fixedUnit('mg/dL'); +kbn.valueFormats.conmmolL = kbn.formatBuilders.fixedUnit('mmol/L'); // Time kbn.valueFormats.hertz = kbn.formatBuilders.decimalSIPrefix('Hz'); @@ -1209,6 +1211,8 @@ kbn.getUnitFormats = () => { { text: 'milligram per normal cubic meter (mg/Nm³)', value: 'conmgNm3' }, { text: 'gram per cubic meter (g/m³)', value: 'congm3' }, { text: 'gram per normal cubic meter (g/Nm³)', value: 'congNm3' }, + { text: 'milligrams per decilitre (mg/dL)', value: 'conmgdL' }, + { text: 'millimoles per litre (mmol/L)', value: 'conmmolL' }, ], }, ]; From f089c7ee5e5bb768f8148f95fafe792b5fbcb3d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 20 Dec 2018 09:25:04 +0100 Subject: [PATCH 053/139] Grafana ui library poc --- package.json | 4 +++- packages/grafana-ui/index.ts | 21 +++++++++++++++++++ packages/grafana-ui/package.json | 11 ++++++++++ .../dashboard/dashgrid/DashboardGrid.tsx | 3 +++ public/app/features/plugins/plugin_loader.ts | 2 ++ 5 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 packages/grafana-ui/index.ts create mode 100644 packages/grafana-ui/package.json diff --git a/package.json b/package.json index d374cd524b4..31e80e3b47d 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,5 @@ { + "private": true, "author": { "name": "Torkel Ödegaard", "company": "Grafana Labs" @@ -178,5 +179,6 @@ "resolutions": { "caniuse-db": "1.0.30000772", "**/@types/react": "16.7.6" - } + }, + "workspaces": ["packages/grafana-ui"] } diff --git a/packages/grafana-ui/index.ts b/packages/grafana-ui/index.ts new file mode 100644 index 00000000000..4beb45d6fb3 --- /dev/null +++ b/packages/grafana-ui/index.ts @@ -0,0 +1,21 @@ +export class Google { + hello() { + return 'hello'; + } +} + +class Singleton { + constructor(private state) {} + + hello() { + return this.state; + } + + change() { + this.state = 'mod2'; + } +} + +const singletonSrv = new Singleton('hello'); + +export { singletonSrv }; diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json new file mode 100644 index 00000000000..e3c407b500a --- /dev/null +++ b/packages/grafana-ui/package.json @@ -0,0 +1,11 @@ +{ + "name": "grafana-ui", + "version": "1.0.0", + "description": "", + "main": "index.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index a401505b787..ad15eca8654 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -7,6 +7,9 @@ import { DashboardModel } from '../dashboard_model'; import { PanelModel } from '../panel_model'; import classNames from 'classnames'; import sizeMe from 'react-sizeme'; +import { Google } from 'grafana-ui'; + +console.log(Google); let lastGridWidth = 1200; let ignoreNextWidthChange = false; diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index 8e0958f6c1b..97e35ab5631 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -26,6 +26,7 @@ import * as ticks from 'app/core/utils/ticks'; import impressionSrv from 'app/core/services/impression_srv'; import builtInPlugins from './built_in_plugins'; import * as d3 from 'd3'; +import * as grafanaUI from 'grafana-ui'; // rxjs import { Observable } from 'rxjs/Observable'; @@ -71,6 +72,7 @@ function exposeToPlugin(name: string, component: any) { }); } +exposeToPlugin('grafana-ui', grafanaUI); exposeToPlugin('lodash', _); exposeToPlugin('moment', moment); exposeToPlugin('jquery', jquery); From 6e66b2b90646e8c63fef0d697a7b833ea6c4fb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 20 Dec 2018 16:56:46 +0100 Subject: [PATCH 054/139] breaking up grafana into multiple packages poc --- packages/grafana-ui/package.json | 12 ++++--- packages/grafana-ui/{ => src}/index.ts | 7 +++- packages/grafana-ui/src/other.ts | 5 +++ packages/grafana-ui/tsconfig.json | 36 +++++++++++++++++++ packages/grafana-ui/types/index.ts | 4 +++ .../dashboard/dashgrid/VisualizationTab.tsx | 1 + public/app/features/plugins/plugin_loader.ts | 2 +- yarn.lock | 5 +++ 8 files changed, 66 insertions(+), 6 deletions(-) rename packages/grafana-ui/{ => src}/index.ts (63%) create mode 100644 packages/grafana-ui/src/other.ts create mode 100644 packages/grafana-ui/tsconfig.json create mode 100644 packages/grafana-ui/types/index.ts diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index e3c407b500a..73eff40d3d7 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -1,11 +1,15 @@ { - "name": "grafana-ui", + "name": "@grafana/ui", "version": "1.0.0", "description": "", - "main": "index.ts", + "main": "dist/index.js", + "types": "dist/types", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "tsc --noEmit" }, "author": "", - "license": "ISC" + "license": "ISC", + "devDependencies": { + "typescript": "^3.2.2" + } } diff --git a/packages/grafana-ui/index.ts b/packages/grafana-ui/src/index.ts similarity index 63% rename from packages/grafana-ui/index.ts rename to packages/grafana-ui/src/index.ts index 4beb45d6fb3..015fff3bca8 100644 --- a/packages/grafana-ui/index.ts +++ b/packages/grafana-ui/src/index.ts @@ -1,11 +1,16 @@ +export { Other } from './other'; +import { TimeSeries } from '../types'; + export class Google { + data: TimeSeries; + hello() { return 'hello'; } } class Singleton { - constructor(private state) {} + constructor(private state: string) {} hello() { return this.state; diff --git a/packages/grafana-ui/src/other.ts b/packages/grafana-ui/src/other.ts new file mode 100644 index 00000000000..cbc1390dd05 --- /dev/null +++ b/packages/grafana-ui/src/other.ts @@ -0,0 +1,5 @@ +export class Other { + static hello() { + return "hello from other"; + } +} diff --git a/packages/grafana-ui/tsconfig.json b/packages/grafana-ui/tsconfig.json new file mode 100644 index 00000000000..f9e577c23d0 --- /dev/null +++ b/packages/grafana-ui/tsconfig.json @@ -0,0 +1,36 @@ +{ + "include": [ + "src/**/*.ts", + "src/**/*.tsx" + ], + "exclude": [ + "dist" + ], + "scripts": { + "build": "tsc" + }, + "compilerOptions": { + "moduleResolution": "node", + "target": "es5", + "lib": ["es6", "dom"], + "jsx": "react", + "module": "esnext", + "outDir": "dist", + "declaration": false, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "importHelpers": true, + "noEmitHelpers": true, + "removeComments": false, + "inlineSourceMap": false, + "sourceMap": true, + "noEmitOnError": false, + "emitDecoratorMetadata": false, + "experimentalDecorators": true, + "declaration": true, + "declarationDir": "./dist/types", + "noImplicitAny": true, + "strictNullChecks": true + } +} diff --git a/packages/grafana-ui/types/index.ts b/packages/grafana-ui/types/index.ts new file mode 100644 index 00000000000..ed86915b4cf --- /dev/null +++ b/packages/grafana-ui/types/index.ts @@ -0,0 +1,4 @@ + +export interface TimeSeries { + name: string; +} diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 42d9bf6a6eb..53364502383 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -15,6 +15,7 @@ import { PanelOptionSection } from './PanelOptionSection'; import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; import { PanelPlugin } from 'app/types/plugins'; +import { TimeSeries } from '@grafana/ui/types'; interface Props { panel: PanelModel; diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index 97e35ab5631..2c394cb77e7 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -26,7 +26,7 @@ import * as ticks from 'app/core/utils/ticks'; import impressionSrv from 'app/core/services/impression_srv'; import builtInPlugins from './built_in_plugins'; import * as d3 from 'd3'; -import * as grafanaUI from 'grafana-ui'; +import * as grafanaUI from '@grafana/ui'; // rxjs import { Observable } from 'rxjs/Observable'; diff --git a/yarn.lock b/yarn.lock index 9bc6c7dcd63..ecdc347c4f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14544,6 +14544,11 @@ typescript@^3.0.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.1.tgz#3362ba9dd1e482ebb2355b02dfe8bcd19a2c7c96" integrity sha512-Veu0w4dTc/9wlWNf2jeRInNodKlcdLgemvPsrNpfu5Pq39sgfFjvIIgTsvUHCoLBnMhPoUA+tFxsXjU6VexVRQ== +typescript@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.2.tgz#fe8101c46aa123f8353523ebdcf5730c2ae493e5" + integrity sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg== + ua-parser-js@^0.7.18: version "0.7.19" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b" From 6aacd0734b6c4ec99913c0772bd9329dee9a12da Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Fri, 21 Dec 2018 11:57:21 +0100 Subject: [PATCH 056/139] typings and renamings --- .../features/dashboard/dashgrid/AlertTab.tsx | 8 +++--- .../dashboard/dashgrid/EditorTabBody.tsx | 26 +++++++++++++------ .../dashboard/dashgrid/QueriesTab.tsx | 19 +++++++------- .../dashboard/dashgrid/VisualizationTab.tsx | 10 +++---- 4 files changed, 36 insertions(+), 27 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/AlertTab.tsx b/public/app/features/dashboard/dashgrid/AlertTab.tsx index f9f565222d8..ee7d320a85b 100644 --- a/public/app/features/dashboard/dashgrid/AlertTab.tsx +++ b/public/app/features/dashboard/dashgrid/AlertTab.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; -import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; -import { EditorTabBody } from './EditorTabBody'; +import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; +import { EditorTabBody, EditorToolbarView, ToolbarButtonType } from './EditorTabBody'; import 'app/features/alerting/AlertTabCtrl'; import { PanelModel } from '../panel_model'; @@ -67,11 +67,12 @@ export class AlertTab extends PureComponent { render() { const { alert } = this.props.panel; - const stateHistory = { + const stateHistory: EditorToolbarView = { title: 'State history', render: () => { return
State history
; }, + buttonType: ToolbarButtonType.View, }; const deleteAlert = { @@ -79,6 +80,7 @@ export class AlertTab extends PureComponent { render: () => { return
Hello
; }, + buttonType: ToolbarButtonType.Action, }; const toolbarItems = alert ? [deleteAlert, stateHistory] : []; diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index 7606d327405..2cd247ed704 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -10,21 +10,28 @@ interface Props { children: JSX.Element; heading: string; renderToolbar?: () => JSX.Element; - toolbarItems?: EditorToolBarView[]; + toolbarItems?: EditorToolbarView[]; } -export interface EditorToolBarView { +export enum ToolbarButtonType { + Action = 'action', + View = 'view', +} + +export interface EditorToolbarView { title?: string; heading?: string; imgSrc?: string; icon?: string; disabled?: boolean; onClick?: () => void; - render: (closeFunction?: any) => JSX.Element | JSX.Element[]; + render?: (closeFunction?: any) => JSX.Element | JSX.Element[]; + action?: () => void; + buttonType: ToolbarButtonType; } interface State { - openView?: EditorToolBarView; + openView?: EditorToolbarView; isOpen: boolean; fadeIn: boolean; } @@ -48,7 +55,7 @@ export class EditorTabBody extends PureComponent { this.setState({ fadeIn: true }); } - onToggleToolBarView = (item: EditorToolBarView) => { + onToggleToolBarView = (item: EditorToolbarView) => { this.setState({ openView: item, isOpen: !this.state.isOpen, @@ -74,12 +81,15 @@ export class EditorTabBody extends PureComponent { return state; } - renderButton(view: EditorToolBarView) { + renderButton(view: EditorToolbarView) { const onClick = () => { if (view.onClick) { view.onClick(); } - this.onToggleToolBarView(view); + + if (view.buttonType !== ToolbarButtonType.Action) { + this.onToggleToolBarView(view); + } }; return ( @@ -91,7 +101,7 @@ export class EditorTabBody extends PureComponent { ); } - renderOpenView(view: EditorToolBarView) { + renderOpenView(view: EditorToolbarView) { return ( {view.render()} diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 9ad0bb3cadd..98287e4888e 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -1,26 +1,23 @@ // Libraries -import React, { SFC, PureComponent } from 'react'; +import React, { PureComponent, SFC } from 'react'; import _ from 'lodash'; - // Components import './../../panel/metrics_tab'; -import { EditorTabBody } from './EditorTabBody'; +import { EditorTabBody, EditorToolbarView, ToolbarButtonType } from './EditorTabBody'; import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; import { QueryInspector } from './QueryInspector'; import { QueryOptions } from './QueryOptions'; import { AngularQueryComponentScope } from 'app/features/panel/metrics_tab'; import { PanelOptionSection } from './PanelOptionSection'; - // Services import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; -import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv'; -import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; +import { BackendSrv, getBackendSrv } from 'app/core/services/backend_srv'; +import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; import config from 'app/core/config'; - // Types import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; -import { DataSourceSelectItem, DataQuery } from 'app/types'; +import { DataQuery, DataSourceSelectItem } from 'app/types'; import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp'; interface Props { @@ -204,15 +201,17 @@ export class QueriesTab extends PureComponent { const { panel } = this.props; const { currentDS, isAddingMixed } = this.state; - const queryInspector = { + const queryInspector: EditorToolbarView = { title: 'Query Inspector', render: this.renderQueryInspector, + buttonType: ToolbarButtonType.View, }; - const dsHelp = { + const dsHelp: EditorToolbarView = { heading: 'Help', icon: 'fa fa-question', render: this.renderHelp, + buttonType: ToolbarButtonType.View, }; return ( diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 42d9bf6a6eb..8a57dd17190 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -1,16 +1,13 @@ // Libraries import React, { PureComponent } from 'react'; - // Utils & Services -import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; - +import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; // Components -import { EditorTabBody } from './EditorTabBody'; +import { EditorTabBody, EditorToolbarView, ToolbarButtonType } from './EditorTabBody'; import { VizTypePicker } from './VizTypePicker'; import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp'; import { FadeIn } from 'app/core/components/Animations/FadeIn'; import { PanelOptionSection } from './PanelOptionSection'; - // Types import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; @@ -206,10 +203,11 @@ export class VisualizationTab extends PureComponent { const { plugin } = this.props; const { isVizPickerOpen, searchQuery } = this.state; - const pluginHelp = { + const pluginHelp: EditorToolbarView = { heading: 'Help', icon: 'fa fa-question', render: this.renderHelp, + buttonType: ToolbarButtonType.View, }; return ( From 2fec5c7577caa09e83333861763992c00fb2d078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 21 Dec 2018 12:27:43 +0100 Subject: [PATCH 057/139] Grafana ui lib is starting to work --- packages/grafana-ui/package.json | 3 +-- .../DeleteButton/DeleteButton.test.tsx | 0 .../components/DeleteButton/DeleteButton.tsx | 14 +++++----- packages/grafana-ui/src/components/index.ts | 1 + packages/grafana-ui/src/index.ts | 27 +------------------ packages/grafana-ui/src/other.ts | 5 ---- packages/grafana-ui/types/index.ts | 4 --- public/app/features/api-keys/ApiKeysPage.tsx | 4 +-- .../dashboard/dashgrid/DashboardGrid.tsx | 3 --- .../dashboard/dashgrid/VisualizationTab.tsx | 1 - public/app/features/plugins/plugin_loader.ts | 2 +- public/app/features/teams/TeamList.tsx | 4 +-- public/app/features/teams/TeamMembers.tsx | 4 +-- 13 files changed, 17 insertions(+), 55 deletions(-) rename {public/app/core => packages/grafana-ui/src}/components/DeleteButton/DeleteButton.test.tsx (100%) rename {public/app/core => packages/grafana-ui/src}/components/DeleteButton/DeleteButton.tsx (81%) create mode 100644 packages/grafana-ui/src/components/index.ts delete mode 100644 packages/grafana-ui/src/other.ts delete mode 100644 packages/grafana-ui/types/index.ts diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 73eff40d3d7..1466e0de80a 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -2,8 +2,7 @@ "name": "@grafana/ui", "version": "1.0.0", "description": "", - "main": "dist/index.js", - "types": "dist/types", + "main": "src/index.ts", "scripts": { "test": "tsc --noEmit" }, diff --git a/public/app/core/components/DeleteButton/DeleteButton.test.tsx b/packages/grafana-ui/src/components/DeleteButton/DeleteButton.test.tsx similarity index 100% rename from public/app/core/components/DeleteButton/DeleteButton.test.tsx rename to packages/grafana-ui/src/components/DeleteButton/DeleteButton.test.tsx diff --git a/public/app/core/components/DeleteButton/DeleteButton.tsx b/packages/grafana-ui/src/components/DeleteButton/DeleteButton.tsx similarity index 81% rename from public/app/core/components/DeleteButton/DeleteButton.tsx rename to packages/grafana-ui/src/components/DeleteButton/DeleteButton.tsx index a83ce6097ad..de2db07aa04 100644 --- a/public/app/core/components/DeleteButton/DeleteButton.tsx +++ b/packages/grafana-ui/src/components/DeleteButton/DeleteButton.tsx @@ -1,15 +1,15 @@ import React, { PureComponent } from 'react'; -export interface DeleteButtonProps { - onConfirmDelete(); +interface Props { + onConfirm(); } -export interface DeleteButtonStates { +interface State { showConfirm: boolean; } -export default class DeleteButton extends PureComponent { - state: DeleteButtonStates = { +export class DeleteButton extends PureComponent { + state: State = { showConfirm: false, }; @@ -33,7 +33,7 @@ export default class DeleteButton extends PureComponent Cancel - + Confirm Delete diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts new file mode 100644 index 00000000000..b57b9bcfdb7 --- /dev/null +++ b/packages/grafana-ui/src/components/index.ts @@ -0,0 +1 @@ +export { DeleteButton } from './DeleteButton/DeleteButton'; diff --git a/packages/grafana-ui/src/index.ts b/packages/grafana-ui/src/index.ts index 015fff3bca8..07635cbbc8e 100644 --- a/packages/grafana-ui/src/index.ts +++ b/packages/grafana-ui/src/index.ts @@ -1,26 +1 @@ -export { Other } from './other'; -import { TimeSeries } from '../types'; - -export class Google { - data: TimeSeries; - - hello() { - return 'hello'; - } -} - -class Singleton { - constructor(private state: string) {} - - hello() { - return this.state; - } - - change() { - this.state = 'mod2'; - } -} - -const singletonSrv = new Singleton('hello'); - -export { singletonSrv }; +export * from './components'; diff --git a/packages/grafana-ui/src/other.ts b/packages/grafana-ui/src/other.ts deleted file mode 100644 index cbc1390dd05..00000000000 --- a/packages/grafana-ui/src/other.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class Other { - static hello() { - return "hello from other"; - } -} diff --git a/packages/grafana-ui/types/index.ts b/packages/grafana-ui/types/index.ts deleted file mode 100644 index ed86915b4cf..00000000000 --- a/packages/grafana-ui/types/index.ts +++ /dev/null @@ -1,4 +0,0 @@ - -export interface TimeSeries { - name: string; -} diff --git a/public/app/features/api-keys/ApiKeysPage.tsx b/public/app/features/api-keys/ApiKeysPage.tsx index d2aa1f24c57..e14873fa9f6 100644 --- a/public/app/features/api-keys/ApiKeysPage.tsx +++ b/public/app/features/api-keys/ApiKeysPage.tsx @@ -13,7 +13,7 @@ import ApiKeysAddedModal from './ApiKeysAddedModal'; import config from 'app/core/config'; import appEvents from 'app/core/app_events'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; -import DeleteButton from 'app/core/components/DeleteButton/DeleteButton'; +import { DeleteButton } from '@grafana/ui'; export interface Props { navModel: NavModel; @@ -224,7 +224,7 @@ export class ApiKeysPage extends PureComponent { {key.name} {key.role} - this.onDeleteApiKey(key)} /> + this.onDeleteApiKey(key)} /> ); diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index ad15eca8654..a401505b787 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -7,9 +7,6 @@ import { DashboardModel } from '../dashboard_model'; import { PanelModel } from '../panel_model'; import classNames from 'classnames'; import sizeMe from 'react-sizeme'; -import { Google } from 'grafana-ui'; - -console.log(Google); let lastGridWidth = 1200; let ignoreNextWidthChange = false; diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 53364502383..42d9bf6a6eb 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -15,7 +15,6 @@ import { PanelOptionSection } from './PanelOptionSection'; import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; import { PanelPlugin } from 'app/types/plugins'; -import { TimeSeries } from '@grafana/ui/types'; interface Props { panel: PanelModel; diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index 2c394cb77e7..775cf9507fe 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -72,7 +72,7 @@ function exposeToPlugin(name: string, component: any) { }); } -exposeToPlugin('grafana-ui', grafanaUI); +exposeToPlugin('@grafana/ui', grafanaUI); exposeToPlugin('lodash', _); exposeToPlugin('moment', moment); exposeToPlugin('jquery', jquery); diff --git a/public/app/features/teams/TeamList.tsx b/public/app/features/teams/TeamList.tsx index d8e12e338e9..d1551d6baa6 100644 --- a/public/app/features/teams/TeamList.tsx +++ b/public/app/features/teams/TeamList.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; import { hot } from 'react-hot-loader'; import PageHeader from 'app/core/components/PageHeader/PageHeader'; -import DeleteButton from 'app/core/components/DeleteButton/DeleteButton'; +import { DeleteButton } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import PageLoader from 'app/core/components/PageLoader/PageLoader'; import { NavModel, Team } from '../../types'; @@ -58,7 +58,7 @@ export class TeamList extends PureComponent { {team.memberCount} - this.deleteTeam(team)} /> + this.deleteTeam(team)} /> ); diff --git a/public/app/features/teams/TeamMembers.tsx b/public/app/features/teams/TeamMembers.tsx index 0e20f4be664..a25f1786a5b 100644 --- a/public/app/features/teams/TeamMembers.tsx +++ b/public/app/features/teams/TeamMembers.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; import SlideDown from 'app/core/components/Animations/SlideDown'; import { UserPicker } from 'app/core/components/Select/UserPicker'; -import DeleteButton from 'app/core/components/DeleteButton/DeleteButton'; +import { DeleteButton } from '@grafana/ui'; import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; import { TeamMember, User } from 'app/types'; import { loadTeamMembers, addTeamMember, removeTeamMember, setSearchMemberQuery } from './state/actions'; @@ -76,7 +76,7 @@ export class TeamMembers extends PureComponent { {member.email} {syncEnabled && this.renderLabels(member.labels)} - this.onRemoveMember(member)} /> + this.onRemoveMember(member)} /> ); From 8b4083a6026e9fe6ac3669d025ca700eb23b396c Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 21 Dec 2018 13:01:25 +0100 Subject: [PATCH 058/139] changelog: add notes about closing #14519 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b420dcc374..867253ccd18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * **LDAP**: Upgrade go-ldap to v3 [#14548](https://github.com/grafana/grafana/issues/14548) * **Proxy whitelist**: Add CIDR capability to auth_proxy whitelist [#14546](https://github.com/grafana/grafana/issues/14546), thx [@jacobrichard](https://github.com/jacobrichard) * **OAuth**: Support OAuth providers that are not RFC6749 compliant [#14562](https://github.com/grafana/grafana/issues/14562), thx [@tdabasinskas](https://github.com/tdabasinskas) +* **Units**: Add blood glucose level units mg/dL and mmol/L [#14519](https://github.com/grafana/grafana/issues/14519), thx [@kjedamzik](https://github.com/kjedamzik) ### Bug fixes * **Search**: Fix for issue with scrolling the "tags filter" dropdown, fixes [#14486](https://github.com/grafana/grafana/issues/14486) @@ -24,7 +25,7 @@ * **Datasource admin**: Fix for issue creating new data source when same name exists [#14467](https://github.com/grafana/grafana/issues/14467) * **OAuth**: Fix for oauth auto login setting, can now be set using env variable [#14435](https://github.com/grafana/grafana/issues/14435) -* **Dashboard search**: Fix for searching tags in tags filter dropdown. +* **Dashboard search**: Fix for searching tags in tags filter dropdown. # 5.4.1 (2018-12-10) From 52f583fee762e35a88040d152e5a6a9cfe5ad05a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 21 Dec 2018 14:23:32 +0100 Subject: [PATCH 059/139] @grafana/ui lib now contains one components, seperate lint & tsc steps --- jest.config.js | 4 +++- package.json | 3 ++- packages/grafana-ui/package.json | 14 +++++++++++- .../DeleteButton/DeleteButton.test.tsx | 9 ++++---- .../components/DeleteButton/DeleteButton.tsx | 8 +++---- packages/grafana-ui/tsconfig.json | 22 ++----------------- packages/grafana-ui/tslint.json | 3 +++ .../__snapshots__/TeamList.test.tsx.snap | 10 ++++----- .../__snapshots__/TeamMembers.test.tsx.snap | 20 ++++++++--------- scripts/grunt/default_task.js | 16 +++++++++++--- scripts/grunt/options/exec.js | 16 ++++++++++---- scripts/grunt/options/sasslint.js | 1 + scripts/grunt/options/tslint.js | 11 ---------- 13 files changed, 73 insertions(+), 64 deletions(-) create mode 100644 packages/grafana-ui/tslint.json delete mode 100644 scripts/grunt/options/tslint.js diff --git a/jest.config.js b/jest.config.js index cac634fbf10..e4d669edd8f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,7 +6,9 @@ module.exports = { }, "moduleDirectories": ["node_modules", "public"], "roots": [ - "/public" + "/public/app", + "/public/test", + "packages" ], "testRegex": "(\\.|/)(test)\\.(jsx?|tsx?)$", "moduleFileExtensions": [ diff --git a/package.json b/package.json index 31e80e3b47d..0f0f7fefa83 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,8 @@ "watch": "webpack --progress --colors --watch --mode development --config scripts/webpack/webpack.dev.js", "build": "grunt build", "test": "grunt test", - "lint": "tslint -c tslint.json --project tsconfig.json", + "tslint": "tslint -c tslint.json --project tsconfig.json", + "typecheck": "tsc --noEmit", "jest": "jest --notify --watch", "api-tests": "jest --notify --watch --config=tests/api/jest.js", "precommit": "lint-staged && grunt precommit" diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 1466e0de80a..39950d31d71 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -4,11 +4,23 @@ "description": "", "main": "src/index.ts", "scripts": { - "test": "tsc --noEmit" + "tslint": "tslint -c tslint.json --project tsconfig.json", + "typecheck": "tsc --noEmit" }, "author": "", "license": "ISC", + "dependencies": { + "react": "^16.6.3", + "react-dom": "^16.6.3", + "react-popper": "^1.3.0", + "react-highlight-words": "0.11.0", + "@torkelo/react-select": "2.1.1", + "react-transition-group": "^2.2.1", + "react-virtualized": "^9.21.0" + }, "devDependencies": { + "@types/jest": "^23.3.2", + "@types/react": "^16.7.6", "typescript": "^3.2.2" } } diff --git a/packages/grafana-ui/src/components/DeleteButton/DeleteButton.test.tsx b/packages/grafana-ui/src/components/DeleteButton/DeleteButton.test.tsx index 12acadee18a..f6d5a676971 100644 --- a/packages/grafana-ui/src/components/DeleteButton/DeleteButton.test.tsx +++ b/packages/grafana-ui/src/components/DeleteButton/DeleteButton.test.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import DeleteButton from './DeleteButton'; +import { DeleteButton } from './DeleteButton'; import { shallow } from 'enzyme'; describe('DeleteButton', () => { - let wrapper; - let deleted; + let wrapper: any; + let deleted: any; beforeAll(() => { deleted = false; @@ -12,7 +12,8 @@ describe('DeleteButton', () => { function deleteItem() { deleted = true; } - wrapper = shallow( deleteItem()} />); + + wrapper = shallow( deleteItem()} />); }); it('should show confirm delete when clicked', () => { diff --git a/packages/grafana-ui/src/components/DeleteButton/DeleteButton.tsx b/packages/grafana-ui/src/components/DeleteButton/DeleteButton.tsx index de2db07aa04..df65d156ab3 100644 --- a/packages/grafana-ui/src/components/DeleteButton/DeleteButton.tsx +++ b/packages/grafana-ui/src/components/DeleteButton/DeleteButton.tsx @@ -1,7 +1,7 @@ -import React, { PureComponent } from 'react'; +import React, { PureComponent, SyntheticEvent } from 'react'; interface Props { - onConfirm(); + onConfirm(): void; } interface State { @@ -13,7 +13,7 @@ export class DeleteButton extends PureComponent { showConfirm: false, }; - onClickDelete = event => { + onClickDelete = (event: SyntheticEvent) => { if (event) { event.preventDefault(); } @@ -23,7 +23,7 @@ export class DeleteButton extends PureComponent { }); }; - onClickCancel = event => { + onClickCancel = (event: SyntheticEvent) => { if (event) { event.preventDefault(); } diff --git a/packages/grafana-ui/tsconfig.json b/packages/grafana-ui/tsconfig.json index f9e577c23d0..ed6009f1ebd 100644 --- a/packages/grafana-ui/tsconfig.json +++ b/packages/grafana-ui/tsconfig.json @@ -1,4 +1,5 @@ { + "extends": "../../tsconfig.json", "include": [ "src/**/*.ts", "src/**/*.tsx" @@ -6,30 +7,11 @@ "exclude": [ "dist" ], - "scripts": { - "build": "tsc" - }, "compilerOptions": { - "moduleResolution": "node", - "target": "es5", - "lib": ["es6", "dom"], - "jsx": "react", + "rootDir": ".", "module": "esnext", "outDir": "dist", - "declaration": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "importHelpers": true, - "noEmitHelpers": true, - "removeComments": false, - "inlineSourceMap": false, - "sourceMap": true, - "noEmitOnError": false, - "emitDecoratorMetadata": false, - "experimentalDecorators": true, "declaration": true, - "declarationDir": "./dist/types", "noImplicitAny": true, "strictNullChecks": true } diff --git a/packages/grafana-ui/tslint.json b/packages/grafana-ui/tslint.json new file mode 100644 index 00000000000..0946f20963a --- /dev/null +++ b/packages/grafana-ui/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tslint.json" +} diff --git a/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap index 73f081d496a..ae94691df0e 100644 --- a/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap @@ -124,7 +124,7 @@ exports[`Render should render teams table 1`] = ` className="text-right" > @@ -174,7 +174,7 @@ exports[`Render should render teams table 1`] = ` className="text-right" > @@ -224,7 +224,7 @@ exports[`Render should render teams table 1`] = ` className="text-right" > @@ -274,7 +274,7 @@ exports[`Render should render teams table 1`] = ` className="text-right" > @@ -324,7 +324,7 @@ exports[`Render should render teams table 1`] = ` className="text-right" > diff --git a/public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap index d0a88bd97b0..5ebddb36d48 100644 --- a/public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap @@ -204,7 +204,7 @@ exports[`Render should render team members 1`] = ` className="text-right" > @@ -229,7 +229,7 @@ exports[`Render should render team members 1`] = ` className="text-right" > @@ -254,7 +254,7 @@ exports[`Render should render team members 1`] = ` className="text-right" > @@ -279,7 +279,7 @@ exports[`Render should render team members 1`] = ` className="text-right" > @@ -304,7 +304,7 @@ exports[`Render should render team members 1`] = ` className="text-right" > @@ -441,7 +441,7 @@ exports[`Render should render team members when sync enabled 1`] = ` className="text-right" > @@ -482,7 +482,7 @@ exports[`Render should render team members when sync enabled 1`] = ` className="text-right" > @@ -523,7 +523,7 @@ exports[`Render should render team members when sync enabled 1`] = ` className="text-right" > @@ -564,7 +564,7 @@ exports[`Render should render team members when sync enabled 1`] = ` className="text-right" > @@ -605,7 +605,7 @@ exports[`Render should render team members when sync enabled 1`] = ` className="text-right" > diff --git a/scripts/grunt/default_task.js b/scripts/grunt/default_task.js index 7b975aac977..1deb23eb220 100644 --- a/scripts/grunt/default_task.js +++ b/scripts/grunt/default_task.js @@ -15,10 +15,20 @@ module.exports = function (grunt) { 'no-only-tests' ]); + grunt.registerTask('tslint', [ + 'newer:exec:tslintPackages', + 'newer:exec:tslintRoot', + ]); + + grunt.registerTask('typecheck', [ + 'newer:exec:typecheckPackages', + 'newer:exec:typecheckRoot', + ]); + grunt.registerTask('precommit', [ - 'sasslint', - 'newer:exec:tslint', - 'newer:exec:tsc', + 'newer:sasslint', + 'typecheck', + 'tslint', 'no-only-tests' ]); diff --git a/scripts/grunt/options/exec.js b/scripts/grunt/options/exec.js index 3b60c5c3be6..27bfd7ae43d 100644 --- a/scripts/grunt/options/exec.js +++ b/scripts/grunt/options/exec.js @@ -2,12 +2,20 @@ module.exports = function (config, grunt) { 'use strict'; return { - tslint: { - command: 'node ./node_modules/tslint/lib/tslintCli.js -c tslint.json --project ./tsconfig.json', + tslintPackages: { + command: 'yarn workspaces run tslint', + src: ['packages/**/*.ts*'], + }, + tslintRoot: { + command: 'yarn run tslint', src: ['public/app/**/*.ts*'], }, - tsc: { - command: 'yarn tsc --noEmit', + typecheckPackages: { + command: 'yarn workspaces run typecheck', + src: ['packages/**/*.ts*'], + }, + typecheckRoot: { + command: 'yarn run typecheck', src: ['public/app/**/*.ts*'], }, jest: 'node ./node_modules/jest-cli/bin/jest.js --maxWorkers 2', diff --git a/scripts/grunt/options/sasslint.js b/scripts/grunt/options/sasslint.js index 8ba5ea3047f..9877f49a5a3 100644 --- a/scripts/grunt/options/sasslint.js +++ b/scripts/grunt/options/sasslint.js @@ -4,6 +4,7 @@ module.exports = function(config) { options: { configFile: 'public/sass/.sass-lint.yml', }, + // src: ['public/sass#<{(||)}>#*'], target: [ 'public/sass/*.scss', 'public/sass/components/*.scss', diff --git a/scripts/grunt/options/tslint.js b/scripts/grunt/options/tslint.js deleted file mode 100644 index d51c2062676..00000000000 --- a/scripts/grunt/options/tslint.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = function(config, grunt) { - 'use strict' - // dummy to avoid template compile error - return { - source: { - files: { - src: "" - } - } - }; -}; From 41eb0ba52e95eb6040849fa14fec6ab44d82774d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Kreileder?= Date: Tue, 12 Jun 2018 18:32:59 +0200 Subject: [PATCH 060/139] Add support for InfluxDB's time zone clause --- public/app/plugins/datasource/influxdb/influx_query.ts | 4 ++++ .../datasource/influxdb/partials/query.editor.html | 10 ++++++++++ public/app/plugins/datasource/influxdb/query_ctrl.ts | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index 60eac1d3f2b..3ee9d703c54 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -256,6 +256,10 @@ export default class InfluxQuery { query += ' SLIMIT ' + target.slimit; } + if (target.tz) { + query += " tz('" + target.tz + "')"; + } + return query; } diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index e1c35d77115..658cf18daee 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -119,6 +119,16 @@
+
+
+ + +
+
+
+
+
+
diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.ts b/public/app/plugins/datasource/influxdb/query_ctrl.ts index f531fe6c4d9..57460ec4d2a 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.ts +++ b/public/app/plugins/datasource/influxdb/query_ctrl.ts @@ -100,6 +100,9 @@ export class InfluxQueryCtrl extends QueryCtrl { if (!this.target.slimit) { options.push(this.uiSegmentSrv.newSegment({ value: 'SLIMIT' })); } + if (!this.target.tz) { + options.push(this.uiSegmentSrv.newSegment({ value: 'tz' })); + } if (this.target.orderByTime === 'ASC') { options.push(this.uiSegmentSrv.newSegment({ value: 'ORDER BY time DESC' })); } @@ -124,6 +127,10 @@ export class InfluxQueryCtrl extends QueryCtrl { this.target.slimit = 10; break; } + case 'tz': { + this.target.tz = 'UTC'; + break; + } case 'ORDER BY time DESC': { this.target.orderByTime = 'DESC'; break; From 7268f16c54ea4fec7e6434cd991f524b648a171d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 21 Dec 2018 14:37:38 +0100 Subject: [PATCH 061/139] grunt test task update --- scripts/grunt/default_task.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/grunt/default_task.js b/scripts/grunt/default_task.js index 1deb23eb220..8a71ea26627 100644 --- a/scripts/grunt/default_task.js +++ b/scripts/grunt/default_task.js @@ -10,7 +10,8 @@ module.exports = function (grunt) { grunt.registerTask('test', [ 'sasslint', - 'exec:tslint', + 'tslint', + 'typecheck', "exec:jest", 'no-only-tests' ]); From 4002f80ab880e34b60a855b03820f4b21d1f9814 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Fri, 21 Dec 2018 14:56:49 +0100 Subject: [PATCH 062/139] delete works --- .../features/alerting/partials/alert_tab.html | 4 +- .../features/dashboard/dashgrid/AlertTab.tsx | 46 +++++++++++++------ 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index 5fcc7ae5e44..cbddb93207a 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -1,4 +1,4 @@ -
+
{{ctrl.error}}
@@ -154,7 +154,7 @@
-
+
Panel has no alert rule defined
diff --git a/public/app/features/dashboard/dashgrid/QueryOptions.tsx b/public/app/features/dashboard/dashgrid/QueryOptions.tsx index dd084418c40..fad70d92990 100644 --- a/public/app/features/dashboard/dashgrid/QueryOptions.tsx +++ b/public/app/features/dashboard/dashgrid/QueryOptions.tsx @@ -10,6 +10,7 @@ import { Input } from 'app/core/components/Form'; import { EventsWithValidation } from 'app/core/components/Form/Input'; import { InputStatus } from 'app/core/components/Form/Input'; import DataSourceOption from './DataSourceOption'; +import { GfFormLabel } from '@grafana/ui'; // Types import { PanelModel } from '../panel_model'; @@ -163,7 +164,7 @@ export class QueryOptions extends PureComponent { {this.renderOptions()}
- Relative time + Relative time Date: Tue, 25 Dec 2018 20:18:38 +0100 Subject: [PATCH 083/139] Husky and sasslint fixes, fixes #14638 --- package.json | 10 +++-- public/sass/.sass-lint.yml | 38 +++++++++++++------ public/sass/components/_drop.scss | 12 +++--- public/sass/components/_filter-list.scss | 6 +-- public/sass/components/_jsontree.scss | 16 +++++++- public/sass/components/_panel_editor.scss | 2 - .../components/_panel_gettingstarted.scss | 5 +-- public/sass/components/_panel_graph.scss | 1 - public/sass/components/_shortcuts.scss | 3 +- public/sass/components/_sidemenu.scss | 3 +- public/sass/components/_slate_editor.scss | 1 - public/sass/components/_submenu.scss | 3 -- public/sass/components/_switch.scss | 6 +-- public/sass/components/_tabbed_view.scss | 1 - public/sass/components/_tags.scss | 1 + public/sass/components/_tagsinput.scss | 3 ++ public/sass/components/_timepicker.scss | 9 +++++ public/sass/components/_tooltip.scss | 9 +++++ public/sass/components/_view_states.scss | 3 ++ public/sass/components/edit_sidemenu.scss | 1 + public/sass/fonts.scss | 2 +- public/sass/grafana.dark.scss | 6 +-- public/sass/grafana.light.scss | 6 +-- public/sass/layout/_page.scss | 6 +-- public/sass/mixins/_forms.scss | 9 +---- public/sass/pages/_history.scss | 4 -- public/sass/pages/_login.scss | 14 ++++--- public/sass/pages/_plugins.scss | 10 +++-- public/sass/utils/_validation.scss | 3 -- scripts/grunt/options/sasslint.js | 9 ++--- 30 files changed, 117 insertions(+), 85 deletions(-) diff --git a/package.json b/package.json index 481ec1959ed..999cd05102f 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,7 @@ "grunt-newer": "^1.3.0", "grunt-notify": "^0.4.5", "grunt-postcss": "^0.8.0", - "grunt-sass": "^2.0.0", - "grunt-sass-lint": "^0.2.2", + "grunt-sass-lint": "^0.2.4", "grunt-usemin": "3.1.1", "grunt-webpack": "^3.0.2", "html-loader": "^0.5.1", @@ -116,7 +115,12 @@ "typecheck": "tsc --noEmit", "jest": "jest --notify --watch", "api-tests": "jest --notify --watch --config=tests/api/jest.js", - "precommit": "lint-staged && grunt precommit" + "precommit": "grunt precommit" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged && grunt precommit" + } }, "lint-staged": { "*.{ts,tsx}": [ diff --git a/public/sass/.sass-lint.yml b/public/sass/.sass-lint.yml index 2e79bc584db..1d1e2fd9c73 100644 --- a/public/sass/.sass-lint.yml +++ b/public/sass/.sass-lint.yml @@ -1,23 +1,41 @@ options: formatter: stylish -files: - include: '**/*.s+(a|c)ss' - ignore: - - './utils/*.scss' - rules: + quotes: + - 0 + - + style: 'single' + + brace-style: + - 2 + - + style: '1tbs' + + space-before-brace: + - 2 + + no-duplicate-properties: + - 0 + - + exclude: + - 'font-size' + - 'word-break' + + empty-line-between-blocks: + - 0 + - + allow-single-line-rulesets: 1 + # Extends extends-before-mixins: 0 extends-before-declarations: 0 placeholder-in-extend: 0 - # Mixins mixins-before-declarations: 0 # Line Spacing one-declaration-per-line: 0 - empty-line-between-blocks: 0 single-line-per-selector: 0 # Disallows @@ -25,7 +43,6 @@ rules: no-color-literals: 0 no-css-comments: 0 no-debug: 0 - no-duplicate-properties: 0 no-empty-rulesets: 1 no-extends: 0 no-ids: 0 @@ -40,6 +57,7 @@ rules: no-vendor-prefixes: 0 no-warn: 0 property-units: 0 + pseudo-element: 0 # Nesting force-attribute-nesting: 0 @@ -57,7 +75,6 @@ rules: # Style Guide bem-depth: 0 border-zero: 0 - brace-style: 0 clean-import-paths: 0 empty-args: 0 hex-length: 0 @@ -66,7 +83,6 @@ rules: leading-zero: 0 nesting-depth: 0 property-sort-order: 0 - quotes: 0 shorthand-values: 0 url-quotes: 0 variable-for-property: 0 @@ -76,12 +92,10 @@ rules: space-after-comma: 0 space-before-colon: 0 space-after-colon: 0 - space-before-brace: 0 space-before-bang: 0 space-after-bang: 0 space-between-parens: 0 space-around-operator: 0 # Final Items - trailing-semicolon: 0 final-newline: 0 diff --git a/public/sass/components/_drop.scss b/public/sass/components/_drop.scss index 6568414ed88..8d9d4fc6b7d 100644 --- a/public/sass/components/_drop.scss +++ b/public/sass/components/_drop.scss @@ -5,13 +5,13 @@ $useDropShadow: false; $attachmentOffset: 0%; $easing: cubic-bezier(0, 0, 0.265, 1); -@include drop-theme("error", $popover-error-bg, $popover-color); -@include drop-theme("popover", $popover-bg, $popover-color, $popover-border-color); -@include drop-theme("help", $popover-help-bg, $popover-help-color); +@include drop-theme('error', $popover-error-bg, $popover-color); +@include drop-theme('popover', $popover-bg, $popover-color, $popover-border-color); +@include drop-theme('help', $popover-help-bg, $popover-help-color); -@include drop-animation-scale("drop", "help", $attachmentOffset: $attachmentOffset, $easing: $easing); -@include drop-animation-scale("drop", "error", $attachmentOffset: $attachmentOffset, $easing: $easing); -@include drop-animation-scale("drop", "popover", $attachmentOffset: $attachmentOffset, $easing: $easing); +@include drop-animation-scale('drop', 'help', $attachmentOffset: $attachmentOffset, $easing: $easing); +@include drop-animation-scale('drop', 'error', $attachmentOffset: $attachmentOffset, $easing: $easing); +@include drop-animation-scale('drop', 'popover', $attachmentOffset: $attachmentOffset, $easing: $easing); .drop-element { z-index: 10000; diff --git a/public/sass/components/_filter-list.scss b/public/sass/components/_filter-list.scss index 7713aa05ac2..90d0a21c539 100644 --- a/public/sass/components/_filter-list.scss +++ b/public/sass/components/_filter-list.scss @@ -67,17 +67,17 @@ text-transform: uppercase; &.online { - background-image: url("/img/online.svg"); + background-image: url('/img/online.svg'); color: $online; } &.warn { - background-image: url("/img/warn-tiny.svg"); + background-image: url('/img/warn-tiny.svg'); color: $warn; } &.critical { - background-image: url("/img/critical.svg"); + background-image: url('/img/critical.svg'); color: $critical; } } diff --git a/public/sass/components/_jsontree.scss b/public/sass/components/_jsontree.scss index 665deda0f12..39804a9c240 100644 --- a/public/sass/components/_jsontree.scss +++ b/public/sass/components/_jsontree.scss @@ -3,16 +3,20 @@ json-tree { .json-tree-key { vertical-align: middle; } + .expandable { position: relative; + &::before { pointer-events: none; } + &::before, & > .json-tree-key { cursor: pointer; } } + .json-tree-branch-preview { display: inline-block; vertical-align: middle; @@ -24,36 +28,44 @@ json-tree { ul { padding-left: $spacer; } + li, ul { list-style: none; } + li { line-height: 1.3rem; } + .json-tree-key { color: $variable; padding: 5px 10px 5px 15px; + &::after { - content: ":"; + content: ':'; } } + json-node.expandable { &::before { - content: "\25b6"; + content: '\25b6'; position: absolute; left: 0px; font-size: 8px; transition: transform 0.1s ease; } + &.expanded::before { transform: rotate(90deg); } } + .json-tree-leaf-value, .json-tree-branch-preview { word-break: break-all; } + .json-tree-branch-preview { overflow: hidden; font-style: italic; diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index e483ae1c2b3..615307c8a83 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -174,7 +174,6 @@ } .panel-editor-tabs { - position: relative; z-index: 2; display: flex; flex-direction: column; @@ -221,7 +220,6 @@ &:hover { filter: $panel-editor-side-menu-shadow; - transform: translate(-2px, -2px); transform: scale(1.1); } } diff --git a/public/sass/components/_panel_gettingstarted.scss b/public/sass/components/_panel_gettingstarted.scss index f46c4569589..5bbc4ba29ca 100644 --- a/public/sass/components/_panel_gettingstarted.scss +++ b/public/sass/components/_panel_gettingstarted.scss @@ -51,8 +51,7 @@ $path-position: $marker-size-half - ($path-height / 2); min-width: $marker-size; &::after { - right: -50%; - content: ""; + content: ''; display: block; position: absolute; z-index: 1; @@ -105,7 +104,7 @@ $path-position: $marker-size-half - ($path-height / 2); // change icon to check .icon-gf::before { - content: "\e604"; + content: '\e604'; } } .progress-text { diff --git a/public/sass/components/_panel_graph.scss b/public/sass/components/_panel_graph.scss index 9f7a9575b61..b35a600b9e1 100644 --- a/public/sass/components/_panel_graph.scss +++ b/public/sass/components/_panel_graph.scss @@ -368,7 +368,6 @@ font-size: $font-size-sm; position: absolute; text-align: center; - font-size: 12px; } .alert-handle-wrapper { diff --git a/public/sass/components/_shortcuts.scss b/public/sass/components/_shortcuts.scss index b5f61872585..447ca8c12a2 100644 --- a/public/sass/components/_shortcuts.scss +++ b/public/sass/components/_shortcuts.scss @@ -33,9 +33,8 @@ text-align: center; margin-right: 0.3rem; padding: 3px 5px; - font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace; + font: 11px Consolas, 'Liberation Mono', Menlo, Courier, monospace; line-height: 10px; - color: #555; vertical-align: middle; background-color: $btn-inverse-bg; border: solid 1px $btn-inverse-bg-hl; diff --git a/public/sass/components/_sidemenu.scss b/public/sass/components/_sidemenu.scss index 5fdb1a5e32e..237574b93bc 100644 --- a/public/sass/components/_sidemenu.scss +++ b/public/sass/components/_sidemenu.scss @@ -29,12 +29,12 @@ .sidemenu-open { .sidemenu { background: $side-menu-bg; - position: initial; height: auto; box-shadow: $side-menu-shadow; position: relative; z-index: $zindex-sidemenu; } + .sidemenu__top, .sidemenu__bottom { display: block; @@ -212,7 +212,6 @@ li.sidemenu-org-switcher { .sidemenu { width: 100%; background: $side-menu-bg-mobile; - position: initial; height: auto; box-shadow: $side-menu-shadow; position: relative; diff --git a/public/sass/components/_slate_editor.scss b/public/sass/components/_slate_editor.scss index 714b8c487c2..200f102d69a 100644 --- a/public/sass/components/_slate_editor.scss +++ b/public/sass/components/_slate_editor.scss @@ -32,7 +32,6 @@ left: -10000px; opacity: 0; border-radius: $border-radius; - transition: opacity 0.75s; border: $panel-border; max-height: calc(66vh); overflow-y: scroll; diff --git a/public/sass/components/_submenu.scss b/public/sass/components/_submenu.scss index a6844b9e66b..d4be5264d9d 100644 --- a/public/sass/components/_submenu.scss +++ b/public/sass/components/_submenu.scss @@ -22,10 +22,8 @@ } .submenu-item { - margin-right: 20px; display: inline-block; margin-right: 15px; - display: inline-block; float: left; .fa-caret-down { @@ -42,7 +40,6 @@ background-color: $input-bg; border: 1px solid $input-border-color; border-radius: $input-border-radius; - color: $input-color; box-sizing: content-box; display: inline-block; color: $text-color; diff --git a/public/sass/components/_switch.scss b/public/sass/components/_switch.scss index ab100a8c752..92756f81a76 100644 --- a/public/sass/components/_switch.scss +++ b/public/sass/components/_switch.scss @@ -19,15 +19,14 @@ gf-form-switch[disabled] { } .gf-form-switch { + display: flex; position: relative; - display: inline-block; width: 60px; height: $gf-form-input-height; background: $switch-bg; border: 1px solid $input-border-color; border-left: none; border-radius: $input-border-radius; - display: flex; align-items: center; justify-content: center; @@ -77,14 +76,13 @@ input:checked + .gf-form-switch__slider::before { .gf-form-checkbox { position: relative; - display: inline-block; + display: flex; width: 50px; height: $gf-form-input-height; background: $switch-bg; border: 1px solid $input-border-color; border-left: none; border-radius: $input-border-radius; - display: flex; align-items: center; justify-content: center; diff --git a/public/sass/components/_tabbed_view.scss b/public/sass/components/_tabbed_view.scss index ef21d54f71c..a47d9d65ceb 100644 --- a/public/sass/components/_tabbed_view.scss +++ b/public/sass/components/_tabbed_view.scss @@ -31,7 +31,6 @@ .tabbed-view-close-btn { float: right; - padding: 0; margin: 0; background-color: transparent; border: none; diff --git a/public/sass/components/_tags.scss b/public/sass/components/_tags.scss index 014d9f0be1e..692259facb3 100644 --- a/public/sass/components/_tags.scss +++ b/public/sass/components/_tags.scss @@ -25,6 +25,7 @@ border-width: 1px; border-style: solid; box-shadow: 0 0 1px rgba($white, 0.2); + .icon-tag { position: relative; top: 1px; diff --git a/public/sass/components/_tagsinput.scss b/public/sass/components/_tagsinput.scss index f4f0ed4c84d..e8cf9ea44e9 100644 --- a/public/sass/components/_tagsinput.scss +++ b/public/sass/components/_tagsinput.scss @@ -24,12 +24,15 @@ [data-role='remove'] { margin-left: 8px; cursor: pointer; + &::after { content: 'x'; padding: 0px 2px; } + &:hover { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + &:active { box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } diff --git a/public/sass/components/_timepicker.scss b/public/sass/components/_timepicker.scss index f3a2079f749..1fea5e9634b 100644 --- a/public/sass/components/_timepicker.scss +++ b/public/sass/components/_timepicker.scss @@ -60,9 +60,11 @@ list-style: none; float: left; margin: 0 30px 10px 0px; + li { line-height: 22px; } + li.active { border-bottom: 1px solid $blue; margin: 3px 0; @@ -77,26 +79,32 @@ td { padding: 1px; } + button { @include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl); background-image: none; border: none; color: $text-color; + &.active span { color: $blue; font-weight: bold; } + .text-info { color: $orange; font-weight: bold; } + &.btn-sm { font-size: $font-size-sm; padding: 5px 11px; } + &:hover { color: $text-color-strong; } + &[disabled] { color: $text-color; } @@ -119,6 +127,7 @@ .fa-chevron-left::before { content: '\f053'; } + .fa-chevron-right::before { content: '\f054'; } diff --git a/public/sass/components/_tooltip.scss b/public/sass/components/_tooltip.scss index b5799e49301..650d8ba1b1a 100644 --- a/public/sass/components/_tooltip.scss +++ b/public/sass/components/_tooltip.scss @@ -11,21 +11,26 @@ font-size: 11px; line-height: 1.4; @include opacity(0); + &.in { @include opacity(100); } + &.top { margin-top: -3px; padding: 5px 0; } + &.right { margin-left: 3px; padding: 0 5px; } + &.bottom { margin-top: 3px; padding: 5px 0; } + &.left { margin-left: -3px; padding: 0 5px; @@ -60,6 +65,7 @@ border-width: $tooltipArrowWidth $tooltipArrowWidth 0; border-top-color: $tooltipArrowColor; } + &.right .tooltip-arrow { top: 50%; left: 0; @@ -67,6 +73,7 @@ border-width: $tooltipArrowWidth $tooltipArrowWidth $tooltipArrowWidth 0; border-right-color: $tooltipArrowColor; } + &.left .tooltip-arrow { top: 50%; right: 0; @@ -74,6 +81,7 @@ border-width: $tooltipArrowWidth 0 $tooltipArrowWidth $tooltipArrowWidth; border-left-color: $tooltipArrowColor; } + &.bottom .tooltip-arrow { top: 0; left: 50%; @@ -98,6 +106,7 @@ max-height: 600px; overflow: hidden; line-height: 14px; + a { color: $tooltipLinkColor; } diff --git a/public/sass/components/_view_states.scss b/public/sass/components/_view_states.scss index c0f8c72ab4f..b92bd596193 100644 --- a/public/sass/components/_view_states.scss +++ b/public/sass/components/_view_states.scss @@ -10,6 +10,7 @@ .navbar-page-btn { transform: translate3d(-36px, 0, 0); + i { opacity: 0; } @@ -40,9 +41,11 @@ .navbar { display: none; } + .scroll-canvas--dashboard { height: 100%; } + .submenu-controls { display: none; } diff --git a/public/sass/components/edit_sidemenu.scss b/public/sass/components/edit_sidemenu.scss index 4fc6795c068..8d60851b4d3 100644 --- a/public/sass/components/edit_sidemenu.scss +++ b/public/sass/components/edit_sidemenu.scss @@ -46,6 +46,7 @@ li { float: left; } + a { margin: 0.3rem 1rem; } diff --git a/public/sass/fonts.scss b/public/sass/fonts.scss index 8d50752f4e7..02fb498356a 100644 --- a/public/sass/fonts.scss +++ b/public/sass/fonts.scss @@ -1 +1 @@ -@import "base/fonts"; +@import 'base/fonts'; diff --git a/public/sass/grafana.dark.scss b/public/sass/grafana.dark.scss index 53193d213e6..f7f5163f36f 100644 --- a/public/sass/grafana.dark.scss +++ b/public/sass/grafana.dark.scss @@ -1,3 +1,3 @@ -@import "variables"; -@import "variables.dark"; -@import "grafana"; +@import 'variables'; +@import 'variables.dark'; +@import 'grafana'; diff --git a/public/sass/grafana.light.scss b/public/sass/grafana.light.scss index e03d24470e6..d7824edfc5d 100644 --- a/public/sass/grafana.light.scss +++ b/public/sass/grafana.light.scss @@ -1,3 +1,3 @@ -@import "variables"; -@import "variables.light"; -@import "grafana"; +@import 'variables'; +@import 'variables.light'; +@import 'grafana'; diff --git a/public/sass/layout/_page.scss b/public/sass/layout/_page.scss index a17c1c67d7b..818dc6c3e60 100644 --- a/public/sass/layout/_page.scss +++ b/public/sass/layout/_page.scss @@ -42,15 +42,13 @@ overflow: auto; height: 100%; -webkit-overflow-scrolling: touch; + display: flex; + flex-direction: column; &--dashboard { height: calc(100% - 56px); } - // Sticky footer - display: flex; - flex-direction: column; - > div { flex-grow: 1; } diff --git a/public/sass/mixins/_forms.scss b/public/sass/mixins/_forms.scss index ce488f0f636..5d2a4421353 100644 --- a/public/sass/mixins/_forms.scss +++ b/public/sass/mixins/_forms.scss @@ -13,16 +13,9 @@ .custom-control { color: $color; } - // Set the border and box shadow on specific inputs to match + .form-control { border-color: $color; - // @include box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work - - &:focus { - // border-color: darken($border-color, 10%); - // $shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten($border-color, 20%); - // @include box-shadow($shadow); - } } // Set validation states also for addons diff --git a/public/sass/pages/_history.scss b/public/sass/pages/_history.scss index ea845b78445..42aa58f0bb7 100644 --- a/public/sass/pages/_history.scss +++ b/public/sass/pages/_history.scss @@ -29,10 +29,6 @@ white-space: nowrap; position: relative; - &:before, - &:after { - } - &:after { left: -40px; } diff --git a/public/sass/pages/_login.scss b/public/sass/pages/_login.scss index 4baff47b2a8..091391da7ff 100644 --- a/public/sass/pages/_login.scss +++ b/public/sass/pages/_login.scss @@ -13,9 +13,11 @@ $login-border: #8daac5; background-image: url(../img/heatmap_bg_test.svg); background-size: cover; color: #d8d9da; + & a { color: #d8d9da !important; } + & .btn-primary { @include buttonBackground(#ff6600, #bc3e06); } @@ -198,6 +200,10 @@ select:-webkit-autofill:focus { border: none; font-size: 15px; padding: 10px 10px; + font-weight: bold; + display: inline-block; + width: 170px; + color: $text-color; &.active { background: darken($tight-form-bg, 5%); @@ -207,11 +213,6 @@ select:-webkit-autofill:focus { &:focus { outline: 0; } - - font-weight: bold; - display: inline-block; - width: 170px; - color: $text-color; } .password-strength { @@ -222,10 +223,12 @@ select:-webkit-autofill:focus { padding-top: 3px; color: darken($text-color, 20%); border-top: 3px solid $red; + &.password-strength-ok { width: 40%; border-top: 3px solid lighten($yellow, 10%); } + &.password-strength-good { width: 100%; border-top: 3px solid lighten($green, 10%); @@ -252,6 +255,7 @@ select:-webkit-autofill:focus { .password-recovery { background: $tight-form-bg; padding: 10px; + a { color: $gray-2; } diff --git a/public/sass/pages/_plugins.scss b/public/sass/pages/_plugins.scss index 15149dd0d35..2570eb6c791 100644 --- a/public/sass/pages/_plugins.scss +++ b/public/sass/pages/_plugins.scss @@ -3,9 +3,11 @@ font-size: $font-size-sm; position: relative; top: 1.2rem; + &:hover { color: $link-hover-color; } + img { vertical-align: top; } @@ -18,12 +20,12 @@ } .plugin-info-list-item { - img { - width: 16px; - } - white-space: nowrap; max-width: $page-sidebar-width; text-overflow: ellipsis; overflow: hidden; + + img { + width: 16px; + } } diff --git a/public/sass/utils/_validation.scss b/public/sass/utils/_validation.scss index 657d1f0414b..307afde42d1 100644 --- a/public/sass/utils/_validation.scss +++ b/public/sass/utils/_validation.scss @@ -1,6 +1,3 @@ -input[type='text'].ng-dirty.ng-invalid { -} - input.validation-error, input.ng-dirty.ng-invalid { box-shadow: inset 0 0px 5px $red; diff --git a/scripts/grunt/options/sasslint.js b/scripts/grunt/options/sasslint.js index 9877f49a5a3..e7651b5ece8 100644 --- a/scripts/grunt/options/sasslint.js +++ b/scripts/grunt/options/sasslint.js @@ -4,10 +4,9 @@ module.exports = function(config) { options: { configFile: 'public/sass/.sass-lint.yml', }, - // src: ['public/sass#<{(||)}>#*'], - target: [ - 'public/sass/*.scss', - 'public/sass/components/*.scss', - ] + src: [ + 'public/sass/**/*.scss', + 'packages/**/*.scss', + ], }; }; From 9cd449ed52f39c7aa2ce6a5afb80fcc7b3ef5c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 25 Dec 2018 21:11:18 +0100 Subject: [PATCH 084/139] added node-sass as dev dependency, needed after I removed grunt-sass --- package.json | 5 ++- yarn.lock | 100 +++++++++------------------------------------------ 2 files changed, 18 insertions(+), 87 deletions(-) diff --git a/package.json b/package.json index 999cd05102f..3e0eaf07458 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.1.0", "@rtsao/plugin-proposal-class-properties": "^7.0.1-patch.1", + "@types/classnames": "^2.2.6", "@types/d3": "^4.10.1", "@types/enzyme": "^3.1.13", "@types/jest": "^23.3.2", @@ -26,7 +27,6 @@ "@types/react-custom-scrollbars": "^4.0.5", "@types/react-dom": "^16.0.9", "@types/react-select": "^2.0.4", - "@types/classnames": "^2.2.6", "angular-mocks": "1.6.6", "autoprefixer": "^6.4.0", "axios": "^0.17.1", @@ -52,9 +52,7 @@ "grunt-cli": "~1.2.0", "grunt-contrib-clean": "~1.0.0", "grunt-contrib-compress": "^1.3.0", - "grunt-contrib-concat": "^1.0.1", "grunt-contrib-copy": "~1.0.0", - "grunt-contrib-cssmin": "~1.0.2", "grunt-exec": "^1.0.1", "grunt-newer": "^1.3.0", "grunt-notify": "^0.4.5", @@ -75,6 +73,7 @@ "ng-annotate-webpack-plugin": "^0.3.0", "ngtemplate-loader": "^2.0.1", "npm": "^5.4.2", + "node-sass": "^4.11.0", "optimize-css-assets-webpack-plugin": "^4.0.2", "phantomjs-prebuilt": "^2.1.15", "postcss-browser-reporter": "^0.5.0", diff --git a/yarn.lock b/yarn.lock index 1fad049fcea..eb1d91066ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1098,7 +1098,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^16.1.0", "@types/react@^16.7.6": +"@types/react@*", "@types/react@16.7.6", "@types/react@^16.1.0", "@types/react@^16.7.6": version "16.7.6" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.7.6.tgz#80e4bab0d0731ad3ae51f320c4b08bdca5f03040" integrity sha512-QBUfzftr/8eg/q3ZRgf/GaDP6rTYc7ZNem+g4oZM38C9vXyV8AWRWaTQuW5yCoZTsfHrN7b3DeEiUnqH9SrnpA== @@ -1412,7 +1412,7 @@ ajv@^4.7.0: co "^4.6.0" json-stable-stringify "^1.0.1" -ajv@^5.1.0, ajv@^5.3.0: +ajv@^5.3.0: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= @@ -1862,7 +1862,7 @@ aws-sign2@~0.7.0: resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= -aws4@^1.6.0, aws4@^1.8.0: +aws4@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== @@ -3168,7 +3168,7 @@ caniuse-api@^1.5.2: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: +caniuse-db@1.0.30000772, caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: version "1.0.30000772" resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000772.tgz#51aae891768286eade4a3d8319ea76d6a01b512b" integrity sha1-UarokXaChureSj2DGep21qAbUSs= @@ -3670,7 +3670,7 @@ combined-stream@1.0.6: dependencies: delayed-stream "~1.0.0" -combined-stream@~1.0.5, combined-stream@~1.0.6: +combined-stream@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== @@ -5035,14 +5035,6 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" -each-async@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/each-async/-/each-async-1.1.1.tgz#dee5229bdf0ab6ba2012a395e1b869abf8813473" - integrity sha1-3uUim98KtrogEqOV4bhpq/iBNHM= - dependencies: - onetime "^1.0.0" - set-immediate-shim "^1.0.0" - eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -5715,7 +5707,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@~3.0.1, extend@~3.0.2: +extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -6095,7 +6087,7 @@ fork-ts-checker-webpack-plugin@^0.4.9: resolve "^1.5.0" tapable "^1.0.0" -form-data@~2.3.1, form-data@~2.3.2: +form-data@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" integrity sha1-SXBJi+YEwgwAXU9cI67NIda0kJk= @@ -6776,22 +6768,13 @@ grunt-postcss@^0.8.0: diff "^2.0.2" postcss "^5.0.0" -grunt-sass-lint@^0.2.2: +grunt-sass-lint@^0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/grunt-sass-lint/-/grunt-sass-lint-0.2.4.tgz#06f77635ad8a5048968ea33c5584b40a18281e35" integrity sha512-jV88yXoxFFvr4R3WVBl0uz4YBzNxXTrCJ7ZBKrYby/SjRCw2sieKPkt5tpWDcQZIj9XrKsOpKuHQn08MaECVwg== dependencies: sass-lint "^1.12.0" -grunt-sass@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/grunt-sass/-/grunt-sass-2.1.0.tgz#b7ba1d85ef4c2d9b7d8195fe65f664ac7554efa1" - integrity sha512-XkexnQt/9rhReNd+Y7T0n/2g5FqYOQKfi2iSlpwDqvgs7EgEaGTxNhnWzHnbW5oNRvzL9AHopBG3AgRxL0d+DA== - dependencies: - each-async "^1.0.0" - node-sass "^4.7.2" - object-assign "^4.0.1" - grunt-usemin@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/grunt-usemin/-/grunt-usemin-3.1.1.tgz#5ab679510d672cea566cc717abe8b8a009f641c2" @@ -6874,14 +6857,6 @@ har-schema@^2.0.0: resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" - integrity sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0= - dependencies: - ajv "^5.1.0" - har-schema "^2.0.0" - har-validator@~5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29" @@ -10097,10 +10072,10 @@ node-releases@^1.0.1: dependencies: semver "^5.3.0" -node-sass@^4.7.2: - version "4.9.3" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.9.3.tgz#f407cf3d66f78308bb1e346b24fa428703196224" - integrity sha512-XzXyGjO+84wxyH7fV6IwBOTrEBe2f0a6SBze9QWWYR/cL74AcQUks2AsqcCZenl/Fp/JVbuEaLpgrLtocwBUww== +node-sass@^4.11.0: + version "4.11.0" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.11.0.tgz#183faec398e9cbe93ba43362e2768ca988a6369a" + integrity sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA== dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -10117,7 +10092,7 @@ node-sass@^4.7.2: nan "^2.10.0" node-gyp "^3.8.0" npmlog "^4.0.0" - request "2.87.0" + request "^2.88.0" sass-graph "^2.2.4" stdout-stream "^1.4.0" "true-case-path" "^1.0.2" @@ -10498,11 +10473,6 @@ nwsapi@^2.0.7: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.9.tgz#77ac0cdfdcad52b6a1151a84e73254edc33ed016" integrity sha512-nlWFSCTYQcHk/6A9FFnfhKc14c3aFhfdNBXgo8Qgi9QTBu/qg3Ww+Uiz9wMzXd1T8GFxPc2QIHB6Qtf2XFryFQ== -oauth-sign@~0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" - integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= - oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -11896,7 +11866,7 @@ qs@6.5.1: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" integrity sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A== -qs@~6.5.1, qs@~6.5.2: +qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== @@ -12700,33 +12670,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.0" tough-cookie ">=2.3.3" -request@2.87.0: - version "2.87.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" - integrity sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.6.0" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" - forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" - performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - tough-cookie "~2.3.3" - tunnel-agent "^0.6.0" - uuid "^3.1.0" - -request@^2.74.0, request@^2.81.0, request@^2.85.0, request@^2.87.0: +request@^2.74.0, request@^2.81.0, request@^2.85.0, request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -13217,11 +13161,6 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -set-immediate-shim@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" - integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= - set-value@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" @@ -14383,13 +14322,6 @@ tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.4.3: psl "^1.1.24" punycode "^1.4.1" -tough-cookie@~2.3.3: - version "2.3.4" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" - integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA== - dependencies: - punycode "^1.4.1" - tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -14898,7 +14830,7 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^3.0.1, uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2: +uuid@^3.0.1, uuid@^3.2.1, uuid@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== From ca7f75f35309cec46c76a13e88485c2f4ef6fbe5 Mon Sep 17 00:00:00 2001 From: Patrick Freeman Date: Wed, 26 Dec 2018 13:32:53 -0500 Subject: [PATCH 085/139] Fix general tab typos --- public/app/features/panel/partials/general_tab.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/features/panel/partials/general_tab.html b/public/app/features/panel/partials/general_tab.html index 7a4f1f7e17f..d6c2d4804a0 100644 --- a/public/app/features/panel/partials/general_tab.html +++ b/public/app/features/panel/partials/general_tab.html @@ -22,7 +22,7 @@
- Repat + Repeat
@@ -42,7 +42,7 @@
-
Drildown Links
+
Drilldown Links
From 74124ec8ed088a2a345c812a55a16cbeb265ac52 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 27 Dec 2018 10:48:11 +0100 Subject: [PATCH 086/139] makes cache mode configurable this makes the cache mode in the sqlite connection string configurable. the default also changed from shared to private to solve #107272 but allow the user to use shared if performance is more important. ref #10727 --- conf/defaults.ini | 3 +++ conf/sample.ini | 3 +++ docs/sources/installation/configuration.md | 6 +++++ pkg/services/sqlstore/sqlstore.go | 29 ++++++++++++++-------- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index 97c87edb8b2..7f61ac96870 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -103,6 +103,9 @@ server_cert_name = # For "sqlite3" only, path relative to data_path setting path = grafana.db +# For "sqlite3" only. cache mode setting used for connecting to the database +cache_mode = private + #################################### Session ############################# [session] # Either "memory", "file", "redis", "mysql", "postgres", "memcache", default is "file" diff --git a/conf/sample.ini b/conf/sample.ini index 473e4e8450c..014016d45bc 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -99,6 +99,9 @@ # Set to true to log the sql calls and execution times. log_queries = +# For "sqlite3" only. cache mode setting used for connecting to the database. (private, shared) +;cache_mode = private + #################################### Session #################################### [session] # Either "memory", "file", "redis", "mysql", "postgres", default is "file" diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index 30ef020a3de..0e5a55b3c0e 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -250,6 +250,12 @@ Sets the maximum amount of time a connection may be reused. The default is 14400 Set to `true` to log the sql calls and execution times. +### cache_mode + +For "sqlite3" only. [Shared cache](https://www.sqlite.org/sharedcache.html) setting used for connecting to the database. (private, shared) +Defaults to private. + +
## [security] diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 95b53be9d4a..d0e93177d8b 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -243,7 +243,7 @@ func (ss *SqlStore) buildConnectionString() (string, error) { ss.dbCfg.Path = filepath.Join(ss.Cfg.DataPath, ss.dbCfg.Path) } os.MkdirAll(path.Dir(ss.dbCfg.Path), os.ModePerm) - cnnstr = "file:" + ss.dbCfg.Path + "?cache=shared&mode=rwc" + cnnstr = fmt.Sprintf("file:%s?cache=%s&mode=rwc", ss.dbCfg.Path, ss.dbCfg.CacheMode) default: return "", fmt.Errorf("Unknown database type: %s", ss.dbCfg.Type) } @@ -319,6 +319,8 @@ func (ss *SqlStore) readConfig() { ss.dbCfg.ClientCertPath = sec.Key("client_cert_path").String() ss.dbCfg.ServerCertName = sec.Key("server_cert_name").String() ss.dbCfg.Path = sec.Key("path").MustString("data/grafana.db") + + ss.dbCfg.CacheMode = sec.Key("cache_mode").MustString("private") } func InitTestDB(t *testing.T) *SqlStore { @@ -391,13 +393,20 @@ func IsTestDbPostgres() bool { } type DatabaseConfig struct { - Type, Host, Name, User, Pwd, Path, SslMode string - CaCertPath string - ClientKeyPath string - ClientCertPath string - ServerCertName string - ConnectionString string - MaxOpenConn int - MaxIdleConn int - ConnMaxLifetime int + Type string + Host string + Name string + User string + Pwd string + Path string + SslMode string + CaCertPath string + ClientKeyPath string + ClientCertPath string + ServerCertName string + ConnectionString string + MaxOpenConn int + MaxIdleConn int + ConnMaxLifetime int + CacheMode string } From 24636da80056d6044128f943ab45eb9506944142 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 27 Dec 2018 13:16:58 +0100 Subject: [PATCH 087/139] closes the body properly on successful webhooks this commit also adds a test docker container for receiving alerting web hook requests --- .../blocks/alert_webhook_listener/Dockerfile | 7 ++++++ .../docker-compose.yaml | 5 ++++ .../blocks/alert_webhook_listener/main.go | 24 +++++++++++++++++++ pkg/services/notifications/webhook.go | 6 ++++- 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 devenv/docker/blocks/alert_webhook_listener/Dockerfile create mode 100644 devenv/docker/blocks/alert_webhook_listener/docker-compose.yaml create mode 100644 devenv/docker/blocks/alert_webhook_listener/main.go diff --git a/devenv/docker/blocks/alert_webhook_listener/Dockerfile b/devenv/docker/blocks/alert_webhook_listener/Dockerfile new file mode 100644 index 00000000000..a6bb87f15f5 --- /dev/null +++ b/devenv/docker/blocks/alert_webhook_listener/Dockerfile @@ -0,0 +1,7 @@ + +FROM golang:latest +ADD main.go / +WORKDIR / +RUN go build -o main . +EXPOSE 3010 +ENTRYPOINT ["/main"] diff --git a/devenv/docker/blocks/alert_webhook_listener/docker-compose.yaml b/devenv/docker/blocks/alert_webhook_listener/docker-compose.yaml new file mode 100644 index 00000000000..3e6c5389f6c --- /dev/null +++ b/devenv/docker/blocks/alert_webhook_listener/docker-compose.yaml @@ -0,0 +1,5 @@ + alert_webhook_listener: + build: docker/blocks/alert_webhook_listener + network_mode: host + ports: + - "3010:3010" diff --git a/devenv/docker/blocks/alert_webhook_listener/main.go b/devenv/docker/blocks/alert_webhook_listener/main.go new file mode 100644 index 00000000000..355c903e9f6 --- /dev/null +++ b/devenv/docker/blocks/alert_webhook_listener/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "io" + "io/ioutil" + "net/http" +) + +func hello(w http.ResponseWriter, r *http.Request) { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return + } + + line := fmt.Sprintf("webbhook: -> %s", string(body)) + fmt.Println(line) + io.WriteString(w, line) +} + +func main() { + http.HandleFunc("/", hello) + http.ListenAndServe(":3010", nil) +} diff --git a/pkg/services/notifications/webhook.go b/pkg/services/notifications/webhook.go index a236a1d1c4e..dbe441c915e 100644 --- a/pkg/services/notifications/webhook.go +++ b/pkg/services/notifications/webhook.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "io" "io/ioutil" "net" "net/http" @@ -69,11 +70,14 @@ func (ns *NotificationService) sendWebRequestSync(ctx context.Context, webhook * return err } + defer resp.Body.Close() + if resp.StatusCode/100 == 2 { + // flushing the body enables the transport to reuse the same connection + io.Copy(ioutil.Discard, resp.Body) return nil } - defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return err From d42f821c879f6d45d7cb927b910238b9ed933bc2 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 14 Dec 2018 15:47:23 +0100 Subject: [PATCH 088/139] Update rxjs --- package.json | 3 +- public/app/core/live/live_srv.ts | 2 +- public/app/features/plugins/plugin_loader.ts | 9 +-- yarn.lock | 72 +++----------------- 4 files changed, 15 insertions(+), 71 deletions(-) diff --git a/package.json b/package.json index 3e0eaf07458..bd8226ca56e 100644 --- a/package.json +++ b/package.json @@ -182,7 +182,8 @@ "redux-thunk": "^2.3.0", "remarkable": "^1.7.1", "rst2html": "github:thoward/rst2html#990cb89", - "rxjs": "^5.4.3", + "rxjs": "^6.3.3", + "rxjs-compat": "^6.3.3", "slate": "^0.33.4", "slate-plain-serializer": "^0.5.10", "slate-prism": "^0.5.0", diff --git a/public/app/core/live/live_srv.ts b/public/app/core/live/live_srv.ts index b49c38b9007..3666fde3b91 100644 --- a/public/app/core/live/live_srv.ts +++ b/public/app/core/live/live_srv.ts @@ -1,7 +1,7 @@ import _ from 'lodash'; import config from 'app/core/config'; -import { Observable } from 'rxjs/Observable'; +import { Observable } from 'rxjs'; export class LiveSrv { conn: any; diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index 775cf9507fe..3c4fa29382d 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -29,14 +29,7 @@ import * as d3 from 'd3'; import * as grafanaUI from '@grafana/ui'; // rxjs -import { Observable } from 'rxjs/Observable'; -import { Subject } from 'rxjs/Subject'; - -// these imports add functions to Observable -import 'rxjs/add/observable/empty'; -import 'rxjs/add/observable/from'; -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/combineAll'; +import { Observable, Subject } from 'rxjs'; // add cache busting const bust = `?_cache=${Date.now()}`; diff --git a/yarn.lock b/yarn.lock index eb1d91066ba..b8e3c1f2905 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2893,13 +2893,6 @@ browserify-sign@^4.0.0: inherits "^2.0.1" parse-asn1 "^5.0.0" -browserify-zlib@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d" - integrity sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0= - dependencies: - pako "~0.2.0" - browserify-zlib@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" @@ -3365,7 +3358,7 @@ classnames@2.x, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== -clean-css@3.4.x, clean-css@~3.4.2: +clean-css@3.4.x: version "3.4.28" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.28.tgz#bf1945e82fc808f55695e6ddeaec01400efd03ff" integrity sha1-vxlF6C/ICPVWlebd6uwBQA79A/8= @@ -3768,7 +3761,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@1.6.2, concat-stream@^1.4.1, concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.5.2: +concat-stream@1.6.2, concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.5.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -5854,7 +5847,7 @@ figgy-pudding@^3.0.0, figgy-pudding@^3.1.0, figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== -figures@^1.0.1, figures@^1.3.5, figures@^1.7.0: +figures@^1.3.5, figures@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4= @@ -6676,14 +6669,6 @@ grunt-contrib-compress@^1.3.0: optionalDependencies: iltorb "^1.0.13" -grunt-contrib-concat@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/grunt-contrib-concat/-/grunt-contrib-concat-1.0.1.tgz#61509863084e871d7e86de48c015259ed97745bd" - integrity sha1-YVCYYwhOhx1+ht5IwBUlntl3Rb0= - dependencies: - chalk "^1.0.0" - source-map "^0.5.3" - grunt-contrib-copy@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz#7060c6581e904b8ab0d00f076e0a8f6e3e7c3573" @@ -6692,15 +6677,6 @@ grunt-contrib-copy@~1.0.0: chalk "^1.1.1" file-sync-cmp "^0.1.0" -grunt-contrib-cssmin@~1.0.2: - version "1.0.2" - resolved "http://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-1.0.2.tgz#1734cbd3d84ca7364758b7e58ff18e52aa60bb76" - integrity sha1-FzTL09hMpzZHWLflj/GOUqpgu3Y= - dependencies: - chalk "^1.0.0" - clean-css "~3.4.2" - maxmin "^1.1.0" - grunt-exec@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/grunt-exec/-/grunt-exec-1.0.1.tgz#e5d53a39c5f346901305edee5c87db0f2af999c4" @@ -6820,14 +6796,6 @@ gud@^1.0.0: resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== -gzip-size@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-1.0.0.tgz#66cf8b101047227b95bace6ea1da0c177ed5c22f" - integrity sha1-Zs+LEBBHInuVus5uodoMF37Vwi8= - dependencies: - browserify-zlib "^0.1.4" - concat-stream "^1.4.1" - gzip-size@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-4.1.0.tgz#8ae096257eabe7d69c45be2b67c448124ffb517c" @@ -9362,16 +9330,6 @@ math-random@^1.0.1: resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" integrity sha1-izqsWIuKZuSXXjzepn97sylgH6w= -maxmin@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/maxmin/-/maxmin-1.1.0.tgz#71365e84a99dd8f8b3f7d5fde2f00d1e7f73be61" - integrity sha1-cTZehKmd2Piz99X94vANHn9zvmE= - dependencies: - chalk "^1.0.0" - figures "^1.0.1" - gzip-size "^1.0.0" - pretty-bytes "^1.0.0" - md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -9446,7 +9404,7 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" -meow@^3.1.0, meow@^3.3.0, meow@^3.7.0: +meow@^3.3.0, meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= @@ -10895,11 +10853,6 @@ pacote@^8.1.6: unique-filename "^1.1.0" which "^1.3.0" -pako@~0.2.0: - version "0.2.9" - resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" - integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU= - pako@~1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" @@ -11644,14 +11597,6 @@ prettier@^1.12.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.3.tgz#90238dd4c0684b7edce5f83b0fb7328e48bd0895" integrity sha512-qZDVnCrnpsRJJq5nSsiHCE3BYMED2OtsI+cmzIzF1QIfqm5ALf8tEJcO27zV1gKNKRPdhjO0dNWnrzssDQ1tFg== -pretty-bytes@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84" - integrity sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ= - dependencies: - get-stdin "^4.0.1" - meow "^3.1.0" - pretty-bytes@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9" @@ -12908,14 +12853,19 @@ rx-lite@^3.1.2: resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI= -rxjs@^5.4.2, rxjs@^5.4.3, rxjs@^5.5.2: +rxjs-compat@^6.3.3: + version "6.3.3" + resolved "https://registry.yarnpkg.com/rxjs-compat/-/rxjs-compat-6.3.3.tgz#2ab3b9ac0dac0c073749d55fef9c03ea1df2045c" + integrity sha512-caGN7ixiabHpOofginKEquuHk7GgaCrC7UpUQ9ZqGp80tMc68msadOeP/2AKy2R4YJsT1+TX5GZCtxO82qWkyA== + +rxjs@^5.4.2, rxjs@^5.5.2: version "5.5.12" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc" integrity sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw== dependencies: symbol-observable "1.0.1" -rxjs@^6.1.0: +rxjs@^6.1.0, rxjs@^6.3.3: version "6.3.3" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz#3c6a7fa420e844a81390fb1158a9ec614f4bad55" integrity sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw== From 9895b1e6d00fb7358ce0e1a25b8818c115420e13 Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 28 Dec 2018 10:49:00 +0100 Subject: [PATCH 089/139] adds orgId to user dto for provisioned dashboards --- pkg/services/dashboards/dashboard_service.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/services/dashboards/dashboard_service.go b/pkg/services/dashboards/dashboard_service.go index 7e334ff656f..6aa1276b093 100644 --- a/pkg/services/dashboards/dashboard_service.go +++ b/pkg/services/dashboards/dashboard_service.go @@ -175,7 +175,9 @@ func (dr *dashboardServiceImpl) SaveProvisionedDashboard(dto *SaveDashboardDTO, dto.User = &models.SignedInUser{ UserId: 0, OrgRole: models.ROLE_ADMIN, + OrgId: dto.OrgId, } + cmd, err := dr.buildSaveDashboardCommand(dto, true, false) if err != nil { return nil, err From ce50e9331ab50d2a1946e3f4a95287c7a1989621 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Sun, 30 Dec 2018 22:53:00 +0100 Subject: [PATCH 090/139] ldap: adds extra debug logging --- pkg/login/ldap.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/login/ldap.go b/pkg/login/ldap.go index 218f7321e65..402160ef5e3 100644 --- a/pkg/login/ldap.go +++ b/pkg/login/ldap.go @@ -292,6 +292,8 @@ func (a *ldapAuther) searchForUser(username string) (*LdapUserInfo, error) { Filter: strings.Replace(a.server.SearchFilter, "%s", ldap.EscapeFilter(username), -1), } + a.log.Debug("Ldap Search For User Request", "info", spew.Sdump(searchReq)) + searchResult, err = a.conn.Search(&searchReq) if err != nil { return nil, err From 8a2d1b98e9b1036ce680a33ba1a1fedf446f474a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 31 Dec 2018 08:57:01 +0100 Subject: [PATCH 091/139] Removed rxjs compat --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index bd8226ca56e..eefe2cbbe53 100644 --- a/package.json +++ b/package.json @@ -183,7 +183,6 @@ "remarkable": "^1.7.1", "rst2html": "github:thoward/rst2html#990cb89", "rxjs": "^6.3.3", - "rxjs-compat": "^6.3.3", "slate": "^0.33.4", "slate-plain-serializer": "^0.5.10", "slate-prism": "^0.5.0", From 26a5e077373ad41fa308f4850ad7fac60aedb942 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Mon, 31 Dec 2018 11:25:28 +0000 Subject: [PATCH 092/139] Loki: query limit configurable in datasource - Loki queries must contain a limit - this change makes this limit configurable in the datasource - keep default at 1000, add tooltip on why inc/dec makes sense - added tests --- .../datasource/loki/datasource.test.ts | 31 ++++++++++++++++++- .../app/plugins/datasource/loki/datasource.ts | 9 +++++- .../datasource/loki/partials/config.html | 16 +++++++++- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index ddb4d6ed549..dddd208d3a1 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -1,10 +1,39 @@ import LokiDatasource from './datasource'; describe('LokiDatasource', () => { - const instanceSettings = { + const instanceSettings: any = { url: 'myloggingurl', }; + describe('when querying', () => { + const backendSrvMock = { datasourceRequest: jest.fn() }; + + const templateSrvMock = { + getAdhocFilters: () => [], + replace: a => a, + }; + + const range = { from: 'now-6h', to: 'now' }; + + test('should use default limit when no limit given', () => { + const ds = new LokiDatasource(instanceSettings, backendSrvMock, templateSrvMock); + backendSrvMock.datasourceRequest = jest.fn(); + ds.query({ range, targets: [{ expr: 'foo' }] }); + expect(backendSrvMock.datasourceRequest.mock.calls.length).toBe(1); + expect(backendSrvMock.datasourceRequest.mock.calls[0][0].url).toContain('limit=1000'); + }); + + test('should use custom limit if set', () => { + const customData = { ...(instanceSettings.jsonData || {}), queryLimit: 20 }; + const customSettings = { ...instanceSettings, jsonData: customData }; + const ds = new LokiDatasource(customSettings, backendSrvMock, templateSrvMock); + backendSrvMock.datasourceRequest = jest.fn(); + ds.query({ range, targets: [{ expr: 'foo' }] }); + expect(backendSrvMock.datasourceRequest.mock.calls.length).toBe(1); + expect(backendSrvMock.datasourceRequest.mock.calls[0][0].url).toContain('limit=20'); + }); + }); + describe('when performing testDataSource', () => { let ds; let result; diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index ebbe6bb4b56..1ea10965737 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -29,10 +29,13 @@ function serializeParams(data: any) { export default class LokiDatasource { languageProvider: LanguageProvider; + queryLimit: number; /** @ngInject */ constructor(private instanceSettings, private backendSrv, private templateSrv) { this.languageProvider = new LanguageProvider(this); + const settingsData = instanceSettings.jsonData || {}; + this.queryLimit = parseInt(settingsData.queryLimit, 10) || DEFAULT_LIMIT; } _request(apiUrl: string, data?, options?: any) { @@ -47,7 +50,7 @@ export default class LokiDatasource { } mergeStreams(streams: LogsStream[], intervalMs: number): LogsModel { - const logs = mergeStreamsToLogs(streams); + const logs = mergeStreamsToLogs(streams, this.queryLimit); logs.series = makeSeriesForLogs(logs.rows, intervalMs); return logs; } @@ -61,6 +64,7 @@ export default class LokiDatasource { ...parseQuery(interpolated), start, end, + limit: this.queryLimit, }; } @@ -77,6 +81,9 @@ export default class LokiDatasource { return Promise.all(queries).then((results: any[]) => { // Flatten streams from multiple queries const allStreams: LogsStream[] = results.reduce((acc, response, i) => { + if (!response) { + return acc; + } const streams: LogsStream[] = response.data.streams || []; // Inject search for match highlighting const search: string = queryTargets[i].regexp; diff --git a/public/app/plugins/datasource/loki/partials/config.html b/public/app/plugins/datasource/loki/partials/config.html index 8e79cc0adcc..f13b00314bb 100644 --- a/public/app/plugins/datasource/loki/partials/config.html +++ b/public/app/plugins/datasource/loki/partials/config.html @@ -1,2 +1,16 @@ - \ No newline at end of file + + +
+
+
+ Line limit + + + Loki queries must contain a limit of maximum number of lines returned (default: 1000). + Increase this limit to have a bigger result set for ad-hoc analysis. + Decrease this limit if your browser becomes sluggish when displaying the log results. + +
+
+
From 39833b0df788bfdc951fac748b5e712c886d0c24 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Mon, 31 Dec 2018 12:05:07 +0000 Subject: [PATCH 093/139] Explore: logging UI style fixes - baseline aligment for meta field labels - use variables for font sizes and margins --- public/sass/components/_panel_logs.scss | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/public/sass/components/_panel_logs.scss b/public/sass/components/_panel_logs.scss index 5fdddf85e92..dac962057bf 100644 --- a/public/sass/components/_panel_logs.scss +++ b/public/sass/components/_panel_logs.scss @@ -31,20 +31,21 @@ $column-horizontal-spacing: 10px; .logs-panel-meta { flex: 1; color: $text-color-weak; - margin-bottom: 10px; + margin-bottom: $spacer; min-width: 30%; display: flex; } .logs-panel-meta__item { - margin-right: 1em; + margin-right: $spacer; display: flex; + align-items: baseline; } .logs-panel-meta__label { - margin-right: 0.5em; - font-size: 0.9em; - font-weight: 500; + margin-right: $spacer / 2; + font-size: $font-size-sm; + font-weight: $font-weight-semi-bold; } .logs-panel-meta__value { @@ -59,7 +60,7 @@ $column-horizontal-spacing: 10px; .logs-rows { font-family: $font-family-monospace; - font-size: 12px; + font-size: $font-size-sm; display: table; table-layout: fixed; } From 32438c4716fbb3a2156426513f9d641ed41e2292 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Mon, 31 Dec 2018 12:38:13 +0000 Subject: [PATCH 094/139] Loki: change query row to be single field again - tried split selector and search for a week, and it did not feel natural - keeping some of the original modifications in QueryField from splitting PR - adapted CheatSheet and placeholder text - added new label selector state: No labels found --- .../loki/components/LokiCheatSheet.tsx | 10 ++-- .../loki/components/LokiQueryField.tsx | 59 +++++-------------- 2 files changed, 19 insertions(+), 50 deletions(-) diff --git a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx index c3a85495a59..49f6b74e8b6 100644 --- a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx +++ b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx @@ -3,22 +3,22 @@ import React from 'react'; const CHEAT_SHEET_ITEMS = [ { title: 'See your logs', - label: 'Start by selecting a log stream from the Log Labels selector.', + label: 'Start by selecting a log stream from the Log labels selector.', }, { - title: 'Logs From a Job', + title: 'Logs from a "job"', expression: '{job="default/prometheus"}', label: 'Returns all log lines emitted by instances of this job.', }, { - title: 'Combine Stream Selectors', + title: 'Combine stream selectors', expression: '{app="cassandra",namespace="prod"}', label: 'Returns all log lines from streams that have both labels.', }, { - title: 'Search For Text', + title: 'Search for text', expression: '{app="cassandra"} (duration|latency)\\s*(=|is|of)\\s*[\\d\\.]+', - label: 'The right search field takes a regular expression to search for.', + label: 'Add a regular expression after the selector to filter for.', }, ]; diff --git a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx index f5d42a2e8cf..820c964f7b1 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx @@ -12,14 +12,18 @@ import BracesPlugin from 'app/features/explore/slate-plugins/braces'; import RunnerPlugin from 'app/features/explore/slate-plugins/runner'; import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField'; import { DataQuery } from 'app/types'; -import { parseQuery, formatQuery } from '../query_utils'; const PRISM_SYNTAX = 'promql'; -const SEARCH_FIELD_STYLES = { - width: '66%', - marginLeft: 3, -}; +function getChooserText(hasSytax, hasLogLabels) { + if (!hasSytax) { + return 'Loading labels...'; + } + if (!hasLogLabels) { + return '(No labels found)'; + } + return 'Log labels'; +} export function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadText }: QueryFieldState): string { // Modify suggestion based on context @@ -145,35 +149,12 @@ class LokiQueryField extends React.PureComponent { - const enableSearchField = !this.modifiedQuery && value; - this.modifiedQuery = value; - // Send text change to parent - const { initialQuery, onQueryChange } = this.props; - if (onQueryChange) { - const search = this.modifiedSearch || parseQuery(initialQuery.expr).regexp; - const expr = formatQuery(value, search); - const query = { - ...initialQuery, - expr, - }; - onQueryChange(query, override); - } - // Enable the search field if we have a selector query - if (enableSearchField) { - this.forceUpdate(); - } - }; - - onChangeSearch = (value: string, override?: boolean) => { - this.modifiedSearch = value; // Send text change to parent const { initialQuery, onQueryChange } = this.props; if (onQueryChange) { - const selector = this.modifiedQuery || parseQuery(initialQuery.expr).query; - const expr = formatQuery(selector, value); const query = { ...initialQuery, - expr, + expr: value, }; onQueryChange(query, override); } @@ -223,10 +204,8 @@ class LokiQueryField extends React.PureComponent 0; + const chooserText = getChooserText(syntaxLoaded, hasLogLabels); return (
@@ -241,11 +220,11 @@ class LokiQueryField extends React.PureComponent @@ -261,16 +240,6 @@ class LokiQueryField extends React.PureComponent ) : null}
-
- -
); } From 652c149ca23cea6441e7fc1934b7ff0553f3e6b2 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Mon, 31 Dec 2018 12:55:03 +0000 Subject: [PATCH 095/139] Explore: fix loading indicator z-index on panel container - existing z-index placed the loading indicator above dropdowns of the panel children - removed z-index for natural ordering --- public/sass/pages/_explore.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index 37ed0bcbc92..dd1afea7394 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -135,7 +135,6 @@ position: absolute; animation: loader 2s cubic-bezier(0.17, 0.67, 0.83, 0.67); animation-iteration-count: 100; - z-index: 2; background: $blue; } From f4b4ee3ff817d1af72e6601a84fd880abf9c5089 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Mon, 31 Dec 2018 14:10:45 +0000 Subject: [PATCH 096/139] Review feedback --- .../app/plugins/datasource/loki/datasource.test.ts | 6 +++--- public/app/plugins/datasource/loki/datasource.ts | 12 ++++++------ .../app/plugins/datasource/loki/partials/config.html | 6 +++--- .../plugins/datasource/loki/result_transformer.ts | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index dddd208d3a1..b7f67ffc0e7 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -15,7 +15,7 @@ describe('LokiDatasource', () => { const range = { from: 'now-6h', to: 'now' }; - test('should use default limit when no limit given', () => { + test('should use default max lines when no limit given', () => { const ds = new LokiDatasource(instanceSettings, backendSrvMock, templateSrvMock); backendSrvMock.datasourceRequest = jest.fn(); ds.query({ range, targets: [{ expr: 'foo' }] }); @@ -23,8 +23,8 @@ describe('LokiDatasource', () => { expect(backendSrvMock.datasourceRequest.mock.calls[0][0].url).toContain('limit=1000'); }); - test('should use custom limit if set', () => { - const customData = { ...(instanceSettings.jsonData || {}), queryLimit: 20 }; + test('should use custom max lines if limit is set', () => { + const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 }; const customSettings = { ...instanceSettings, jsonData: customData }; const ds = new LokiDatasource(customSettings, backendSrvMock, templateSrvMock); backendSrvMock.datasourceRequest = jest.fn(); diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 1ea10965737..eb33c7ef285 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -9,11 +9,11 @@ import LanguageProvider from './language_provider'; import { mergeStreamsToLogs } from './result_transformer'; import { formatQuery, parseQuery } from './query_utils'; -export const DEFAULT_LIMIT = 1000; +export const DEFAULT_MAX_LINES = 1000; const DEFAULT_QUERY_PARAMS = { direction: 'BACKWARD', - limit: DEFAULT_LIMIT, + limit: DEFAULT_MAX_LINES, regexp: '', query: '', }; @@ -29,13 +29,13 @@ function serializeParams(data: any) { export default class LokiDatasource { languageProvider: LanguageProvider; - queryLimit: number; + maxLines: number; /** @ngInject */ constructor(private instanceSettings, private backendSrv, private templateSrv) { this.languageProvider = new LanguageProvider(this); const settingsData = instanceSettings.jsonData || {}; - this.queryLimit = parseInt(settingsData.queryLimit, 10) || DEFAULT_LIMIT; + this.maxLines = parseInt(settingsData.maxLines, 10) || DEFAULT_MAX_LINES; } _request(apiUrl: string, data?, options?: any) { @@ -50,7 +50,7 @@ export default class LokiDatasource { } mergeStreams(streams: LogsStream[], intervalMs: number): LogsModel { - const logs = mergeStreamsToLogs(streams, this.queryLimit); + const logs = mergeStreamsToLogs(streams, this.maxLines); logs.series = makeSeriesForLogs(logs.rows, intervalMs); return logs; } @@ -64,7 +64,7 @@ export default class LokiDatasource { ...parseQuery(interpolated), start, end, - limit: this.queryLimit, + limit: this.maxLines, }; } diff --git a/public/app/plugins/datasource/loki/partials/config.html b/public/app/plugins/datasource/loki/partials/config.html index f13b00314bb..d209b51730a 100644 --- a/public/app/plugins/datasource/loki/partials/config.html +++ b/public/app/plugins/datasource/loki/partials/config.html @@ -4,10 +4,10 @@
- Line limit - + Maximum lines + - Loki queries must contain a limit of maximum number of lines returned (default: 1000). + Loki queries must contain a limit of the maximum number of lines returned (default: 1000). Increase this limit to have a bigger result set for ad-hoc analysis. Decrease this limit if your browser becomes sluggish when displaying the log results. diff --git a/public/app/plugins/datasource/loki/result_transformer.ts b/public/app/plugins/datasource/loki/result_transformer.ts index b24f7d473b9..1f86f20d6fd 100644 --- a/public/app/plugins/datasource/loki/result_transformer.ts +++ b/public/app/plugins/datasource/loki/result_transformer.ts @@ -11,7 +11,7 @@ import { LogsStreamLabels, LogsMetaKind, } from 'app/core/logs_model'; -import { DEFAULT_LIMIT } from './datasource'; +import { DEFAULT_MAX_LINES } from './datasource'; /** * Returns the log level of a log line. @@ -140,7 +140,7 @@ export function processEntry( }; } -export function mergeStreamsToLogs(streams: LogsStream[], limit = DEFAULT_LIMIT): LogsModel { +export function mergeStreamsToLogs(streams: LogsStream[], limit = DEFAULT_MAX_LINES): LogsModel { // Unique model identifier const id = streams.map(stream => stream.labels).join(); From a364487b3a14864740214c24f329cd6b154f6079 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Mon, 31 Dec 2018 14:44:13 +0000 Subject: [PATCH 097/139] Logs data model: add more log levels - add `eror` used in github.com/inconshreveable/log15 (used by Grafana as well) - add levels from SYSLOG --- public/app/core/logs_model.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index 0947688bc89..4e8c6207959 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -2,14 +2,23 @@ import _ from 'lodash'; import { TimeSeries } from 'app/core/core'; import colors, { getThemeColor } from 'app/core/utils/colors'; +/** + * Mapping of log level abbreviation to canonical log level. + * Supported levels are reduce to limit color variation. + */ export enum LogLevel { + emerg = 'critical', + alert = 'critical', crit = 'critical', critical = 'critical', warn = 'warning', warning = 'warning', err = 'error', + eror = 'error', error = 'error', info = 'info', + notice = 'info', + dbug = 'debug', debug = 'debug', trace = 'trace', unkown = 'unkown', From 5f4eeea481a3ddb6e91c735509550ae9fe25c9e2 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Mon, 31 Dec 2018 19:17:07 +0100 Subject: [PATCH 098/139] Update yarn.lock --- yarn.lock | 5 ----- 1 file changed, 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index b8e3c1f2905..78300be233e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12853,11 +12853,6 @@ rx-lite@^3.1.2: resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI= -rxjs-compat@^6.3.3: - version "6.3.3" - resolved "https://registry.yarnpkg.com/rxjs-compat/-/rxjs-compat-6.3.3.tgz#2ab3b9ac0dac0c073749d55fef9c03ea1df2045c" - integrity sha512-caGN7ixiabHpOofginKEquuHk7GgaCrC7UpUQ9ZqGp80tMc68msadOeP/2AKy2R4YJsT1+TX5GZCtxO82qWkyA== - rxjs@^5.4.2, rxjs@^5.5.2: version "5.5.12" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc" From 67ebdcbcc395cc2548a3148c5f7770b4d185307c Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Mon, 31 Dec 2018 18:30:32 +0000 Subject: [PATCH 099/139] Explore: Remember last use datasource - use local storage to store last used database - renamed `datasourceName` property to `initialDatasourceId` for consistency - adapted tests --- public/app/core/utils/explore.test.ts | 9 ++++----- public/app/core/utils/explore.ts | 2 +- public/app/features/explore/Explore.tsx | 21 +++++++++++++++------ public/app/types/explore.ts | 2 +- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index 7ceebbd8047..fcd92681004 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -14,7 +14,6 @@ const DEFAULT_EXPLORE_STATE: ExploreState = { datasourceError: null, datasourceLoading: null, datasourceMissing: false, - datasourceName: '', exploreDatasources: [], graphInterval: 1000, history: [], @@ -69,7 +68,7 @@ describe('state functions', () => { it('returns url parameter value for a state object', () => { const state = { ...DEFAULT_EXPLORE_STATE, - datasourceName: 'foo', + initialDatasourceId: 'foo', range: { from: 'now-5h', to: 'now', @@ -94,7 +93,7 @@ describe('state functions', () => { it('returns url parameter value for a state object', () => { const state = { ...DEFAULT_EXPLORE_STATE, - datasourceName: 'foo', + initialDatasourceId: 'foo', range: { from: 'now-5h', to: 'now', @@ -120,7 +119,7 @@ describe('state functions', () => { it('can parse the serialized state into the original state', () => { const state = { ...DEFAULT_EXPLORE_STATE, - datasourceName: 'foo', + initialDatasourceId: 'foo', range: { from: 'now - 5h', to: 'now', @@ -144,7 +143,7 @@ describe('state functions', () => { const resultState = { ...rest, datasource: DEFAULT_EXPLORE_STATE.datasource, - datasourceName: datasource, + initialDatasourceId: datasource, initialQueries: queries, }; diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index b7de48ac479..1927c616d50 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -105,7 +105,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState { export function serializeStateToUrlParam(state: ExploreState, compact?: boolean): string { const urlState: ExploreUrlState = { - datasource: state.datasourceName, + datasource: state.initialDatasourceId, queries: state.initialQueries.map(clearQueryKeys), range: state.range, }; diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index f56d9cabd16..11e9becb9bb 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -40,6 +40,8 @@ import ErrorBoundary from './ErrorBoundary'; import { Alert } from './Error'; import TimePicker, { parseTime } from './TimePicker'; +const LAST_USED_DATASOURCE_KEY = 'grafana.explore.datasource'; + interface ExploreProps { datasourceSrv: DatasourceSrv; onChangeSplit: (split: boolean, state?: ExploreState) => void; @@ -90,6 +92,10 @@ interface ExploreProps { export class Explore extends React.PureComponent { el: any; exploreEvents: Emitter; + /** + * Set via URL or local storage + */ + initialDatasourceId: string; /** * Current query expressions of the rows including their modifications, used for running queries. * Not kept in component state to prevent edit-render roundtrips. @@ -115,6 +121,7 @@ export class Explore extends React.PureComponent { initialQueries = splitState.initialQueries; } else { const { datasource, queries, range } = props.urlState as ExploreUrlState; + const initialDatasourceId = datasource || store.get(LAST_USED_DATASOURCE_KEY); initialQueries = ensureQueries(queries); const initialRange = { from: parseTime(range.from), to: parseTime(range.to) } || { ...DEFAULT_RANGE }; // Millies step for helper bar charts @@ -124,10 +131,10 @@ export class Explore extends React.PureComponent { datasourceError: null, datasourceLoading: null, datasourceMissing: false, - datasourceName: datasource, exploreDatasources: [], graphInterval: initialGraphInterval, graphResult: [], + initialDatasourceId, initialQueries, history: [], logsResult: null, @@ -151,7 +158,7 @@ export class Explore extends React.PureComponent { async componentDidMount() { const { datasourceSrv } = this.props; - const { datasourceName } = this.state; + const { initialDatasourceId } = this.state; if (!datasourceSrv) { throw new Error('No datasource service passed as props.'); } @@ -165,10 +172,10 @@ export class Explore extends React.PureComponent { if (datasources.length > 0) { this.setState({ datasourceLoading: true, exploreDatasources }); - // Priority: datasource in url, default datasource, first explore datasource + // Priority for datasource preselection: URL, localstorage, default datasource let datasource; - if (datasourceName) { - datasource = await datasourceSrv.get(datasourceName); + if (initialDatasourceId) { + datasource = await datasourceSrv.get(initialDatasourceId); } else { datasource = await datasourceSrv.get(); } @@ -253,13 +260,15 @@ export class Explore extends React.PureComponent { supportsLogs, supportsTable, datasourceLoading: false, - datasourceName: datasource.name, + initialDatasourceId: datasource.name, initialQueries: nextQueries, logsHighlighterExpressions: undefined, showingStartPage: Boolean(StartPage), }, () => { if (datasourceError === null) { + // Save last-used datasource + store.set(LAST_USED_DATASOURCE_KEY, datasource.name); this.onSubmit(); } } diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 647b3e99398..7ea0462900d 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -155,11 +155,11 @@ export interface ExploreState { datasourceError: any; datasourceLoading: boolean | null; datasourceMissing: boolean; - datasourceName?: string; exploreDatasources: DataSourceSelectItem[]; graphInterval: number; // in ms graphResult?: any[]; history: HistoryItem[]; + initialDatasourceId?: string; initialQueries: DataQuery[]; logsHighlighterExpressions?: string[]; logsResult?: LogsModel; From a5c05d273f0d4b890b56db1a83e5bbb927675611 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Mon, 31 Dec 2018 19:44:00 +0100 Subject: [PATCH 100/139] Tweak datetime picker layout for mobile --- public/sass/components/_timepicker.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/sass/components/_timepicker.scss b/public/sass/components/_timepicker.scss index 1fea5e9634b..f424e42ded5 100644 --- a/public/sass/components/_timepicker.scss +++ b/public/sass/components/_timepicker.scss @@ -20,9 +20,12 @@ display: flex; flex-direction: column; position: absolute; + left: 20px; right: 20px; top: $navbarHeight; - width: 550px; + @include media-breakpoint-up(md) { + width: 550px; + } .popover-box { max-width: 100%; From d9c93e572ea5e8589c4a862b06ced294c1407c38 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 2 Jan 2019 10:50:52 +0100 Subject: [PATCH 101/139] hide protip if not defined --- .../core/components/EmptyListCTA/EmptyListCTA.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx b/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx index ae0e39cc26d..d63af72ae4d 100644 --- a/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx +++ b/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx @@ -24,12 +24,14 @@ class EmptyListCTA extends Component { {buttonTitle} -
- ProTip: {proTip} - - {proTipLinkTitle} - -
+ {proTip && ( +
+ ProTip: {proTip} + + {proTipLinkTitle} + +
+ )}
); } From 9aef3f4fb1ec5cc3a33f29cab746784ee03b9eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 2 Jan 2019 10:54:57 +0100 Subject: [PATCH 102/139] Fixed issues with panel size in edit mode, fixes #14703 --- .../app/features/dashboard/dashgrid/DashboardPanel.tsx | 6 +++--- public/app/features/dashboard/dashgrid/PanelResizer.tsx | 7 ++++--- public/app/features/panel/panel_ctrl.ts | 9 ++++----- public/sass/components/_panel_editor.scss | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index 481dd2d5e19..9de298e0799 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -160,14 +160,14 @@ export class DashboardPanel extends PureComponent { return (
( + render={styles => (
{plugin.exports.Panel && this.renderReactPanel()} {plugin.exports.PanelCtrl && this.renderAngularPanel()} diff --git a/public/app/features/dashboard/dashgrid/PanelResizer.tsx b/public/app/features/dashboard/dashgrid/PanelResizer.tsx index 7d52076b54b..2a4bf8379a6 100644 --- a/public/app/features/dashboard/dashgrid/PanelResizer.tsx +++ b/public/app/features/dashboard/dashgrid/PanelResizer.tsx @@ -1,4 +1,4 @@ -import React, { PureComponent } from 'react'; +import React, { PureComponent } from 'react'; import { throttle } from 'lodash'; import Draggable from 'react-draggable'; @@ -6,7 +6,7 @@ import { PanelModel } from '../panel_model'; interface Props { isEditing: boolean; - render: (height: number | 'inherit') => JSX.Element; + render: (styles: object) => JSX.Element; panel: PanelModel; } @@ -19,6 +19,7 @@ export class PanelResizer extends PureComponent { prevEditorHeight: number; throttledChangeHeight: (height: number) => void; throttledResizeDone: () => void; + noStyles: object = {}; constructor(props) { super(props); @@ -65,7 +66,7 @@ export class PanelResizer extends PureComponent { return ( <> - {render(isEditing ? editorHeight : 'inherit')} + {render(isEditing ? {height: editorHeight} : this.noStyles)} {isEditing && (
diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 2bd43ee2a29..432d22fecdf 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -198,11 +198,10 @@ export class PanelCtrl { } calculatePanelHeight() { - if (this.panel.fullscreen) { - const docHeight = $('.react-grid-layout').height(); - const editHeight = Math.floor(docHeight * 0.35); - const fullscreenHeight = Math.floor(docHeight * 0.8); - this.containerHeight = this.panel.isEditing ? editHeight : fullscreenHeight; + if (this.panel.isEditing) { + this.containerHeight = $('.panel-wrapper--edit').height(); + } else if (this.panel.fullscreen) { + this.containerHeight = $('.panel-wrapper--view').height(); } else { this.containerHeight = this.panel.gridPos.h * GRID_CELL_HEIGHT + (this.panel.gridPos.h - 1) * GRID_CELL_VMARGIN; } diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 615307c8a83..bfc1c4bb9b5 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -14,7 +14,7 @@ &--view { flex: 1 1 0; - height: 80%; + height: 90%; margin: 0 $dashboard-padding; padding-top: $dashboard-padding; } From ce01bd696e69ef93864a84fe84b7456c0f67caba Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 2 Jan 2019 10:57:12 +0100 Subject: [PATCH 103/139] add alert in react instead of angular --- public/app/features/alerting/AlertTabCtrl.ts | 5 ++-- .../features/alerting/partials/alert_tab.html | 10 -------- .../features/dashboard/dashgrid/AlertTab.tsx | 23 ++++++++++++++++--- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/public/app/features/alerting/AlertTabCtrl.ts b/public/app/features/alerting/AlertTabCtrl.ts index 6d87c159d02..ffcb20472ad 100644 --- a/public/app/features/alerting/AlertTabCtrl.ts +++ b/public/app/features/alerting/AlertTabCtrl.ts @@ -45,6 +45,7 @@ export class AlertTabCtrl { this.noDataModes = alertDef.noDataModes; this.executionErrorModes = alertDef.executionErrorModes; this.appSubUrl = config.appSubUrl; + this.panelCtrl._enableAlert = this.enable; } $onInit() { @@ -353,11 +354,11 @@ export class AlertTabCtrl { }); } - enable() { + enable = () => { this.panel.alert = {}; this.initModel(); this.panel.alert.for = '5m'; //default value for new alerts. for existing alerts we use 0m to avoid breaking changes - } + }; evaluatorParamsChanged() { ThresholdMapper.alertToGraphThresholds(this.panel); diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index cbddb93207a..3a03b23a076 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -153,13 +153,3 @@
- -
-
-
Panel has no alert rule defined
- -
-
diff --git a/public/app/features/dashboard/dashgrid/AlertTab.tsx b/public/app/features/dashboard/dashgrid/AlertTab.tsx index 2a715536ca8..5ccfa791328 100644 --- a/public/app/features/dashboard/dashgrid/AlertTab.tsx +++ b/public/app/features/dashboard/dashgrid/AlertTab.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; - import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; import { EditorTabBody, EditorToolbarView, ToolbarButtonType } from './EditorTabBody'; +import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import appEvents from 'app/core/app_events'; import { PanelModel } from '../panel_model'; import 'app/features/alerting/AlertTabCtrl'; @@ -33,7 +33,7 @@ export class AlertTab extends PureComponent { } shouldLoadAlertTab() { - return this.props.angularPanel && this.element; + return this.props.angularPanel && this.element && !this.component; } componentWillUnmount() { @@ -93,6 +93,7 @@ export class AlertTab extends PureComponent { panel.thresholds = []; this.panelCtrl.alertState = null; this.panelCtrl.render(); + this.forceUpdate(); }, }); }, @@ -100,15 +101,31 @@ export class AlertTab extends PureComponent { }; }; + onAddAlert = () => { + this.panelCtrl._enableAlert(); + this.component.digest(); + this.forceUpdate(); + }; + render() { const { alert } = this.props.panel; const toolbarItems = alert ? [this.stateHistory(), this.deleteAlert()] : []; + const model = { + title: 'Panel has no alert rule defined', + icon: 'icon-gf icon-gf-alert', + onClick: this.onAddAlert, + buttonTitle: 'Create Alert', + }; + //TODO move add button react from angular and add condition to render angular view return ( -
(this.element = element)} /> + <> +
(this.element = element)} /> + {!alert && } + ); } From 757d6f322e211da19af70b9f7455e3dbeb667243 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Wed, 2 Jan 2019 11:32:12 +0000 Subject: [PATCH 104/139] s/initialDatasourceId/initialDatasource/ --- public/app/core/utils/explore.test.ts | 8 ++++---- public/app/core/utils/explore.ts | 2 +- public/app/features/explore/Explore.tsx | 14 +++++++------- public/app/types/explore.ts | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index fcd92681004..a3b08516d16 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -68,7 +68,7 @@ describe('state functions', () => { it('returns url parameter value for a state object', () => { const state = { ...DEFAULT_EXPLORE_STATE, - initialDatasourceId: 'foo', + initialDatasource: 'foo', range: { from: 'now-5h', to: 'now', @@ -93,7 +93,7 @@ describe('state functions', () => { it('returns url parameter value for a state object', () => { const state = { ...DEFAULT_EXPLORE_STATE, - initialDatasourceId: 'foo', + initialDatasource: 'foo', range: { from: 'now-5h', to: 'now', @@ -119,7 +119,7 @@ describe('state functions', () => { it('can parse the serialized state into the original state', () => { const state = { ...DEFAULT_EXPLORE_STATE, - initialDatasourceId: 'foo', + initialDatasource: 'foo', range: { from: 'now - 5h', to: 'now', @@ -143,7 +143,7 @@ describe('state functions', () => { const resultState = { ...rest, datasource: DEFAULT_EXPLORE_STATE.datasource, - initialDatasourceId: datasource, + initialDatasource: datasource, initialQueries: queries, }; diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index 1927c616d50..bea166075dc 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -105,7 +105,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState { export function serializeStateToUrlParam(state: ExploreState, compact?: boolean): string { const urlState: ExploreUrlState = { - datasource: state.initialDatasourceId, + datasource: state.initialDatasource, queries: state.initialQueries.map(clearQueryKeys), range: state.range, }; diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 11e9becb9bb..a905034f302 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -95,7 +95,7 @@ export class Explore extends React.PureComponent { /** * Set via URL or local storage */ - initialDatasourceId: string; + initialDatasource: string; /** * Current query expressions of the rows including their modifications, used for running queries. * Not kept in component state to prevent edit-render roundtrips. @@ -121,7 +121,7 @@ export class Explore extends React.PureComponent { initialQueries = splitState.initialQueries; } else { const { datasource, queries, range } = props.urlState as ExploreUrlState; - const initialDatasourceId = datasource || store.get(LAST_USED_DATASOURCE_KEY); + const initialDatasource = datasource || store.get(LAST_USED_DATASOURCE_KEY); initialQueries = ensureQueries(queries); const initialRange = { from: parseTime(range.from), to: parseTime(range.to) } || { ...DEFAULT_RANGE }; // Millies step for helper bar charts @@ -134,7 +134,7 @@ export class Explore extends React.PureComponent { exploreDatasources: [], graphInterval: initialGraphInterval, graphResult: [], - initialDatasourceId, + initialDatasource, initialQueries, history: [], logsResult: null, @@ -158,7 +158,7 @@ export class Explore extends React.PureComponent { async componentDidMount() { const { datasourceSrv } = this.props; - const { initialDatasourceId } = this.state; + const { initialDatasource } = this.state; if (!datasourceSrv) { throw new Error('No datasource service passed as props.'); } @@ -174,8 +174,8 @@ export class Explore extends React.PureComponent { this.setState({ datasourceLoading: true, exploreDatasources }); // Priority for datasource preselection: URL, localstorage, default datasource let datasource; - if (initialDatasourceId) { - datasource = await datasourceSrv.get(initialDatasourceId); + if (initialDatasource) { + datasource = await datasourceSrv.get(initialDatasource); } else { datasource = await datasourceSrv.get(); } @@ -260,7 +260,7 @@ export class Explore extends React.PureComponent { supportsLogs, supportsTable, datasourceLoading: false, - initialDatasourceId: datasource.name, + initialDatasource: datasource.name, initialQueries: nextQueries, logsHighlighterExpressions: undefined, showingStartPage: Boolean(StartPage), diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 7ea0462900d..c2c59d35f5b 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -159,7 +159,7 @@ export interface ExploreState { graphInterval: number; // in ms graphResult?: any[]; history: HistoryItem[]; - initialDatasourceId?: string; + initialDatasource?: string; initialQueries: DataQuery[]; logsHighlighterExpressions?: string[]; logsResult?: LogsModel; From e7d9bbf78138e91bbd940771f242e6659dca5285 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 2 Jan 2019 15:22:22 +0100 Subject: [PATCH 105/139] state history tab --- .../features/dashboard/dashgrid/AlertTab.tsx | 6 +- .../dashboard/dashgrid/PanelEditor.tsx | 2 +- .../dashboard/dashgrid/StateHistory.tsx | 85 +++++++++++++++++++ 3 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 public/app/features/dashboard/dashgrid/StateHistory.tsx diff --git a/public/app/features/dashboard/dashgrid/AlertTab.tsx b/public/app/features/dashboard/dashgrid/AlertTab.tsx index 5ccfa791328..127e6cd0239 100644 --- a/public/app/features/dashboard/dashgrid/AlertTab.tsx +++ b/public/app/features/dashboard/dashgrid/AlertTab.tsx @@ -2,12 +2,15 @@ import React, { PureComponent } from 'react'; import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; import { EditorTabBody, EditorToolbarView, ToolbarButtonType } from './EditorTabBody'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; +import StateHistory from './StateHistory'; import appEvents from 'app/core/app_events'; import { PanelModel } from '../panel_model'; import 'app/features/alerting/AlertTabCtrl'; +import { DashboardModel } from '../dashboard_model'; interface Props { angularPanel?: AngularComponent; + dashboard: DashboardModel; panel: PanelModel; } @@ -70,7 +73,7 @@ export class AlertTab extends PureComponent { return { title: 'State history', render: () => { - return
State history
; + return ; }, buttonType: ToolbarButtonType.View, }; @@ -119,7 +122,6 @@ export class AlertTab extends PureComponent { buttonTitle: 'Create Alert', }; - //TODO move add button react from angular and add condition to render angular view return ( <> diff --git a/public/app/features/dashboard/dashgrid/PanelEditor.tsx b/public/app/features/dashboard/dashgrid/PanelEditor.tsx index b3d94f43487..fbc683c2eb3 100644 --- a/public/app/features/dashboard/dashgrid/PanelEditor.tsx +++ b/public/app/features/dashboard/dashgrid/PanelEditor.tsx @@ -54,7 +54,7 @@ export class PanelEditor extends PureComponent { case 'queries': return ; case 'alert': - return ; + return ; case 'visualization': return ( { + state = { + stateHistoryItems: [], + }; + + componentDidMount(): void { + const { dashboard, panelId } = this.props; + + getBackendSrv() + .get(`/api/annotations?dashboardId=${dashboard.id}&panelId=${panelId}&limit=50&type=alert`) + .then(res => { + console.log(res); + const items = []; + res.map(item => { + items.push({ + stateModel: alertDef.getStateDisplayModel(item.newState), + time: dashboard.formatDate(item.time, 'MMM D, YYYY HH:mm:ss'), + info: alertDef.getAlertAnnotationInfo(item), + }); + }); + + this.setState({ + stateHistoryItems: items, + }); + }); + } + + render() { + const { stateHistoryItems } = this.state; + + return ( +
+
+
+ Last 50 state changes + +
+
    + {stateHistoryItems ? ( + stateHistoryItems.map((item, index) => { + return ( +
  1. +
    + +
    +
    +
    +

    {item.alertName}

    +
    + {item.stateModel.text} +
    +
    + {item.info} +
    +
    {item.time}
    +
  2. + ); + }) + ) : ( + No state changes recorded + )} +
+
+
+ ); + } +} + +export default StateHistory; From 6b5f9d58217be26761675509ebfa02e0e5ff994f Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 2 Jan 2019 16:07:29 +0100 Subject: [PATCH 106/139] clear history --- .../features/dashboard/dashgrid/AlertTab.tsx | 12 +++-- .../dashboard/dashgrid/StateHistory.tsx | 53 ++++++++++++++----- 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/AlertTab.tsx b/public/app/features/dashboard/dashgrid/AlertTab.tsx index 127e6cd0239..80706bf9b41 100644 --- a/public/app/features/dashboard/dashgrid/AlertTab.tsx +++ b/public/app/features/dashboard/dashgrid/AlertTab.tsx @@ -19,10 +19,6 @@ export class AlertTab extends PureComponent { component: AngularComponent; panelCtrl: any; - constructor(props) { - super(props); - } - componentDidMount() { if (this.shouldLoadAlertTab()) { this.loadAlertTab(); @@ -73,7 +69,13 @@ export class AlertTab extends PureComponent { return { title: 'State history', render: () => { - return ; + return ( + + ); }, buttonType: ToolbarButtonType.View, }; diff --git a/public/app/features/dashboard/dashgrid/StateHistory.tsx b/public/app/features/dashboard/dashgrid/StateHistory.tsx index d40afdd375a..c2998190841 100644 --- a/public/app/features/dashboard/dashgrid/StateHistory.tsx +++ b/public/app/features/dashboard/dashgrid/StateHistory.tsx @@ -1,11 +1,13 @@ import React, { PureComponent } from 'react'; import alertDef from '../../alerting/state/alertDef'; -import { getBackendSrv } from '../../../core/services/backend_srv'; +import { getBackendSrv } from 'app/core/services/backend_srv'; import { DashboardModel } from '../dashboard_model'; +import appEvents from '../../../core/app_events'; interface Props { dashboard: DashboardModel; panelId: number; + onRefresh: () => void; } interface State { @@ -23,14 +25,12 @@ class StateHistory extends PureComponent { getBackendSrv() .get(`/api/annotations?dashboardId=${dashboard.id}&panelId=${panelId}&limit=50&type=alert`) .then(res => { - console.log(res); - const items = []; - res.map(item => { - items.push({ + const items = res.map(item => { + return { stateModel: alertDef.getStateDisplayModel(item.newState), time: dashboard.formatDate(item.time, 'MMM D, YYYY HH:mm:ss'), info: alertDef.getAlertAnnotationInfo(item), - }); + }; }); this.setState({ @@ -39,20 +39,47 @@ class StateHistory extends PureComponent { }); } + clearHistory = () => { + const { dashboard, onRefresh, panelId } = this.props; + + appEvents.emit('confirm-modal', { + title: 'Delete Alert History', + text: 'Are you sure you want to remove all history & annotations for this alert?', + icon: 'fa-trash', + yesText: 'Yes', + onConfirm: () => { + getBackendSrv() + .post('/api/annotations/mass-delete', { + dashboardId: dashboard.id, + panelId: panelId, + }) + .then(() => { + onRefresh(); + }); + + this.setState({ + stateHistoryItems: [], + }); + }, + }); + }; + render() { const { stateHistoryItems } = this.state; return (
-
- Last 50 state changes - -
+ {stateHistoryItems.length > 0 && ( +
+ Last 50 state changes + +
+ )}
    - {stateHistoryItems ? ( + {stateHistoryItems.length > 0 ? ( stateHistoryItems.map((item, index) => { return (
  1. From 80e441f48b3b65d1125bb3b6f14016918e8b3eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 2 Jan 2019 19:17:35 +0100 Subject: [PATCH 107/139] Fixed new gotmetalinter warning --- pkg/services/dashboards/dashboard_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/dashboards/dashboard_service.go b/pkg/services/dashboards/dashboard_service.go index 7e334ff656f..d9ff1216966 100644 --- a/pkg/services/dashboards/dashboard_service.go +++ b/pkg/services/dashboards/dashboard_service.go @@ -76,7 +76,7 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO, return nil, models.ErrDashboardFolderCannotHaveParent } - if dash.IsFolder && strings.ToLower(dash.Title) == strings.ToLower(models.RootFolderName) { + if dash.IsFolder && strings.EqualFold(dash.Title, models.RootFolderName) { return nil, models.ErrDashboardFolderNameExists } From 4c51e8df201ce2a3860bb5f94a9c02653a3ea5a3 Mon Sep 17 00:00:00 2001 From: Sven Klemm Date: Wed, 2 Jan 2019 23:38:09 +0100 Subject: [PATCH 108/139] Don't cut off subsecond precision for postgres macros Change time format to RFC3339Nano for $__timeFilter, $__timeFrom and $__timeTo macros --- pkg/tsdb/postgres/macros.go | 6 +++--- pkg/tsdb/postgres/macros_test.go | 23 ++++++++++++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/pkg/tsdb/postgres/macros.go b/pkg/tsdb/postgres/macros.go index 26a3d1e53ee..0b3685c2610 100644 --- a/pkg/tsdb/postgres/macros.go +++ b/pkg/tsdb/postgres/macros.go @@ -86,11 +86,11 @@ func (m *postgresMacroEngine) evaluateMacro(name string, args []string) (string, return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s BETWEEN '%s' AND '%s'", args[0], m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339), m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil + return fmt.Sprintf("%s BETWEEN '%s' AND '%s'", args[0], m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339Nano), m.timeRange.GetToAsTimeUTC().Format(time.RFC3339Nano)), nil case "__timeFrom": - return fmt.Sprintf("'%s'", m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339)), nil + return fmt.Sprintf("'%s'", m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339Nano)), nil case "__timeTo": - return fmt.Sprintf("'%s'", m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil + return fmt.Sprintf("'%s'", m.timeRange.GetToAsTimeUTC().Format(time.RFC3339Nano)), nil case "__timeGroup": if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name) diff --git a/pkg/tsdb/postgres/macros_test.go b/pkg/tsdb/postgres/macros_test.go index 6a71caedeb9..90f6c6d879a 100644 --- a/pkg/tsdb/postgres/macros_test.go +++ b/pkg/tsdb/postgres/macros_test.go @@ -41,7 +41,7 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") So(err, ShouldBeNil) - So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339))) + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339Nano), to.Format(time.RFC3339Nano))) }) Convey("interpolate __timeFrom function", func() { @@ -138,7 +138,7 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") So(err, ShouldBeNil) - So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339))) + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339Nano), to.Format(time.RFC3339Nano))) }) Convey("interpolate __unixEpochFilter function", func() { @@ -158,7 +158,7 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") So(err, ShouldBeNil) - So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339))) + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339Nano), to.Format(time.RFC3339Nano))) }) Convey("interpolate __unixEpochFilter function", func() { @@ -168,5 +168,22 @@ func TestMacroEngine(t *testing.T) { So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix())) }) }) + + Convey("Given a time range between 1960-02-01 07:00:00.5 and 1980-02-03 08:00:00.5", func() { + from := time.Date(1960, 2, 1, 7, 0, 0, 500e6, time.UTC) + to := time.Date(1980, 2, 3, 8, 0, 0, 500e6, time.UTC) + timeRange := tsdb.NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10)) + + So(from.Format(time.RFC3339Nano), ShouldEqual, "1960-02-01T07:00:00.5Z") + So(to.Format(time.RFC3339Nano), ShouldEqual, "1980-02-03T08:00:00.5Z") + Convey("interpolate __timeFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339Nano), to.Format(time.RFC3339Nano))) + }) + + }) + }) } From 4892d3f54be27014560ed55b72b9845cdf64d16d Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Thu, 3 Jan 2019 14:21:58 +0100 Subject: [PATCH 109/139] Fixing issue with value color being wrong --- public/app/viz/Gauge.test.tsx | 55 +++++++++++++++++++++++++++++++++++ public/app/viz/Gauge.tsx | 14 +++++---- 2 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 public/app/viz/Gauge.test.tsx diff --git a/public/app/viz/Gauge.test.tsx b/public/app/viz/Gauge.test.tsx new file mode 100644 index 00000000000..91107a563e5 --- /dev/null +++ b/public/app/viz/Gauge.test.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { Gauge, Props } from './Gauge'; +import { BasicGaugeColor } from '../types'; +import { TimeSeriesVMs } from '@grafana/ui'; + +jest.mock('jquery', () => ({ + plot: jest.fn(), +})); + +const setup = (propOverrides?: object) => { + const props: Props = { + baseColor: BasicGaugeColor.Green, + maxValue: 100, + mappings: [], + minValue: 0, + prefix: '', + showThresholdMarkers: true, + showThresholdLabels: false, + suffix: '', + thresholds: [], + unit: 'none', + stat: 'avg', + height: 300, + width: 300, + timeSeries: {} as TimeSeriesVMs, + decimals: 0, + }; + + Object.assign(props, propOverrides); + + const wrapper = shallow(); + const instance = wrapper.instance() as Gauge; + + return { + instance, + wrapper, + }; +}; + +describe('Get font color', () => { + it('should get base color if no threshold', () => { + const { instance } = setup(); + + expect(instance.getFontColor(40)).toEqual(BasicGaugeColor.Green); + }); + + it('should be f2f2f2', () => { + const { instance } = setup({ + thresholds: [{ value: 59, color: '#f2f2f2' }], + }); + + expect(instance.getFontColor(58)).toEqual('#f2f2f2'); + }); +}); diff --git a/public/app/viz/Gauge.tsx b/public/app/viz/Gauge.tsx index 031d856f492..defeaf8cc8f 100644 --- a/public/app/viz/Gauge.tsx +++ b/public/app/viz/Gauge.tsx @@ -5,7 +5,7 @@ import { TimeSeriesVMs } from '@grafana/ui'; import config from '../core/config'; import kbn from '../core/utils/kbn'; -interface Props { +export interface Props { baseColor: string; decimals: number; height: number; @@ -96,12 +96,14 @@ export class Gauge extends PureComponent { getFontColor(value) { const { baseColor, maxValue, thresholds } = this.props; - const atThreshold = thresholds.filter(threshold => value <= threshold.value); + if (thresholds.length > 0) { + const atThreshold = thresholds.filter(threshold => value <= threshold.value); - if (atThreshold.length > 0) { - return atThreshold[0].color; - } else if (value <= maxValue) { - return BasicGaugeColor.Red; + if (atThreshold.length > 0) { + return atThreshold[0].color; + } else if (value <= maxValue) { + return BasicGaugeColor.Red; + } } return baseColor; From 44e2fd4b226e8e3000eba9c9485e57d4270f3de0 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Thu, 3 Jan 2019 14:31:45 +0100 Subject: [PATCH 110/139] Fix issue with value disappearing when selecting stat --- public/app/plugins/panel/gauge/module.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/panel/gauge/module.tsx b/public/app/plugins/panel/gauge/module.tsx index 245a17abe52..dccd424b416 100644 --- a/public/app/plugins/panel/gauge/module.tsx +++ b/public/app/plugins/panel/gauge/module.tsx @@ -38,8 +38,8 @@ export const defaultProps = { showThresholdLabels: false, suffix: '', decimals: 0, - stat: '', - unit: '', + stat: 'avg', + unit: 'none', mappings: [], thresholds: [], }, From 297241c4aba4c6db70181b7974f376a1b3a609b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 4 Jan 2019 09:35:16 +0100 Subject: [PATCH 111/139] Fixed timepicker css issue introduced by PR #14700 --- public/sass/components/_timepicker.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/sass/components/_timepicker.scss b/public/sass/components/_timepicker.scss index f424e42ded5..e4cb5ce9c21 100644 --- a/public/sass/components/_timepicker.scss +++ b/public/sass/components/_timepicker.scss @@ -20,9 +20,9 @@ display: flex; flex-direction: column; position: absolute; - left: 20px; right: 20px; top: $navbarHeight; + @include media-breakpoint-up(md) { width: 550px; } From 0ca0670e0039723f9beef4e77264370b42cf2851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 4 Jan 2019 09:58:24 +0100 Subject: [PATCH 112/139] Changed datasource list page default layout mode --- public/app/features/datasources/state/reducers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/datasources/state/reducers.ts b/public/app/features/datasources/state/reducers.ts index e8625aac0d2..6e86c304fa7 100644 --- a/public/app/features/datasources/state/reducers.ts +++ b/public/app/features/datasources/state/reducers.ts @@ -5,7 +5,7 @@ import { LayoutModes } from '../../../core/components/LayoutSelector/LayoutSelec const initialState: DataSourcesState = { dataSources: [] as DataSource[], dataSource: {} as DataSource, - layoutMode: LayoutModes.Grid, + layoutMode: LayoutModes.List, searchQuery: '', dataSourcesCount: 0, dataSourceTypes: [] as Plugin[], From 2eeba9dae186806ed5977d1e79815bfc5328169c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 4 Jan 2019 11:57:16 +0100 Subject: [PATCH 113/139] Updated alert tab layout & markup --- public/app/features/alerting/AlertTabCtrl.ts | 3 +- .../features/alerting/partials/alert_tab.html | 207 ++++++++++-------- 2 files changed, 112 insertions(+), 98 deletions(-) diff --git a/public/app/features/alerting/AlertTabCtrl.ts b/public/app/features/alerting/AlertTabCtrl.ts index ffcb20472ad..2be25e9df6a 100644 --- a/public/app/features/alerting/AlertTabCtrl.ts +++ b/public/app/features/alerting/AlertTabCtrl.ts @@ -115,7 +115,7 @@ export class AlertTabCtrl { } getNotifications() { - return Promise.resolve( + return this.$q.when( this.notifications.map(item => { return this.uiSegmentSrv.newSegment(item.name); }) @@ -148,6 +148,7 @@ export class AlertTabCtrl { // reset plus button this.addNotificationSegment.value = this.uiSegmentSrv.newPlusButton().value; this.addNotificationSegment.html = this.uiSegmentSrv.newPlusButton().html; + this.addNotificationSegment.fake = true; } removeNotification(index) { diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index 3a03b23a076..da862203da6 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -4,112 +4,121 @@
-
-
+
+

Rule

+
Name
-
-
- Evaluate every - -
-
- - - - If an alert rule has a configured For and the query violates the configured - threshold it - will first go from OK to Pending. - Going from OK to Pending Grafana will not send any notifications. Once the alert - rule - has - been firing for more than For duration, it will change to Alerting and send alert - notifications. - -
+
+ Evaluate every + +
+
+ + + + If an alert rule has a configured For and the query violates the configured + threshold it + will first go from OK to Pending. + Going from OK to Pending Grafana will not send any notifications. Once the alert + rule + has + been firing for more than For duration, it will change to Alerting and send alert + notifications. +
-
-
-
-
Conditions
-
-
-
- - WHEN -
-
- - - OF -
-
- - -
-
- - - - + +
+

Conditions

+
+
+ + WHEN +
+
+ + + OF +
+
+ + +
+
+ + + + +
+
+ +
+
-
-
- -
+
-
- If no data or all values are null - SET STATE TO -
- +

No Data & Error Handling

+
+
+ If no data or all values are null +
+
+ SET STATE TO +
+ +
-
- If execution error or timeout - SET STATE TO -
- +
+
+ If execution error or timeout +
+
+ SET STATE TO +
+ +
@@ -129,19 +138,23 @@
+
Notifications
-
+
Send to - -  {{nc.name}}  - - - +
+ +  {{nc.name}}  + + +
+
+
From f749ced36e05956e02ed0574a24e8350b64e5b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 4 Jan 2019 12:18:49 +0100 Subject: [PATCH 114/139] AlertTab style fixes --- .../dashboard/dashgrid/EditorTabBody.tsx | 2 +- .../dashboard/dashgrid/StateHistory.tsx | 64 +++++++++---------- public/sass/pages/_alerting.scss | 1 + 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index 2cd247ed704..822507c5c11 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -25,7 +25,7 @@ export interface EditorToolbarView { icon?: string; disabled?: boolean; onClick?: () => void; - render?: (closeFunction?: any) => JSX.Element | JSX.Element[]; + render?: () => JSX.Element; action?: () => void; buttonType: ToolbarButtonType; } diff --git a/public/app/features/dashboard/dashgrid/StateHistory.tsx b/public/app/features/dashboard/dashgrid/StateHistory.tsx index c2998190841..99229b41848 100644 --- a/public/app/features/dashboard/dashgrid/StateHistory.tsx +++ b/public/app/features/dashboard/dashgrid/StateHistory.tsx @@ -69,41 +69,39 @@ class StateHistory extends PureComponent { return (
-
- {stateHistoryItems.length > 0 && ( -
- Last 50 state changes - -
- )} -
    - {stateHistoryItems.length > 0 ? ( - stateHistoryItems.map((item, index) => { - return ( -
  1. -
    - -
    -
    -
    -

    {item.alertName}

    -
    - {item.stateModel.text} -
    + {stateHistoryItems.length > 0 && ( +
    + Last 50 state changes + +
    + )} +
      + {stateHistoryItems.length > 0 ? ( + stateHistoryItems.map((item, index) => { + return ( +
    1. +
      + +
      +
      +
      +

      {item.alertName}

      +
      + {item.stateModel.text}
      - {item.info}
      -
      {item.time}
      -
    2. - ); - }) - ) : ( - No state changes recorded - )} -
    -
    + {item.info} +
    +
    {item.time}
    +
  2. + ); + }) + ) : ( + No state changes recorded + )} +
); } diff --git a/public/sass/pages/_alerting.scss b/public/sass/pages/_alerting.scss index 77752be11bc..f285ab753ff 100644 --- a/public/sass/pages/_alerting.scss +++ b/public/sass/pages/_alerting.scss @@ -107,6 +107,7 @@ display: flex; flex-direction: column; flex-grow: 1; + justify-content: center; overflow: hidden; } From 35f6f50f4092b5bef5023e8a499f4cd1605a47bf Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 4 Jan 2019 12:24:22 +0100 Subject: [PATCH 115/139] Fixed timepicker css issue introduced by PR #14700 and remove hotfix from 297241c --- public/sass/components/_timepicker.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/sass/components/_timepicker.scss b/public/sass/components/_timepicker.scss index e4cb5ce9c21..6f075c4d92e 100644 --- a/public/sass/components/_timepicker.scss +++ b/public/sass/components/_timepicker.scss @@ -20,10 +20,12 @@ display: flex; flex-direction: column; position: absolute; + left: 20px; right: 20px; top: $navbarHeight; @include media-breakpoint-up(md) { + left: auto; width: 550px; } From e0c28ba7704246a70e4467f23109cadacb8f3fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 4 Jan 2019 12:38:50 +0100 Subject: [PATCH 116/139] Minor refactoring of EditorTabBody --- .../features/dashboard/dashgrid/AlertTab.tsx | 17 +++++++++++------ .../dashboard/dashgrid/EditorTabBody.tsx | 10 ++-------- .../features/dashboard/dashgrid/QueriesTab.tsx | 9 +++++---- .../dashboard/dashgrid/VisualizationTab.tsx | 6 ++++-- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/AlertTab.tsx b/public/app/features/dashboard/dashgrid/AlertTab.tsx index 80706bf9b41..20f7e90633e 100644 --- a/public/app/features/dashboard/dashgrid/AlertTab.tsx +++ b/public/app/features/dashboard/dashgrid/AlertTab.tsx @@ -1,12 +1,19 @@ +// Libraries import React, { PureComponent } from 'react'; + +// Services & Utils import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; -import { EditorTabBody, EditorToolbarView, ToolbarButtonType } from './EditorTabBody'; +import appEvents from 'app/core/app_events'; + +// Components +import { EditorTabBody, EditorToolbarView } from './EditorTabBody'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import StateHistory from './StateHistory'; -import appEvents from 'app/core/app_events'; -import { PanelModel } from '../panel_model'; import 'app/features/alerting/AlertTabCtrl'; + +// Types import { DashboardModel } from '../dashboard_model'; +import { PanelModel } from '../panel_model'; interface Props { angularPanel?: AngularComponent; @@ -77,7 +84,6 @@ export class AlertTab extends PureComponent { /> ); }, - buttonType: ToolbarButtonType.View, }; }; @@ -85,7 +91,7 @@ export class AlertTab extends PureComponent { const { panel } = this.props; return { title: 'Delete', - icon: 'fa fa-trash', + btnType: 'danger', onClick: () => { appEvents.emit('confirm-modal', { title: 'Delete Alert', @@ -102,7 +108,6 @@ export class AlertTab extends PureComponent { }, }); }, - buttonType: ToolbarButtonType.Action, }; }; diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index 822507c5c11..b7da81a23f8 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -13,21 +13,15 @@ interface Props { toolbarItems?: EditorToolbarView[]; } -export enum ToolbarButtonType { - Action = 'action', - View = 'view', -} - export interface EditorToolbarView { title?: string; heading?: string; - imgSrc?: string; icon?: string; disabled?: boolean; onClick?: () => void; render?: () => JSX.Element; action?: () => void; - buttonType: ToolbarButtonType; + btnType?: 'danger'; } interface State { @@ -87,7 +81,7 @@ export class EditorTabBody extends PureComponent { view.onClick(); } - if (view.buttonType !== ToolbarButtonType.Action) { + if (view.render) { this.onToggleToolBarView(view); } }; diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 98287e4888e..741e2cd9ac1 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -1,19 +1,22 @@ // Libraries import React, { PureComponent, SFC } from 'react'; import _ from 'lodash'; + // Components -import './../../panel/metrics_tab'; -import { EditorTabBody, EditorToolbarView, ToolbarButtonType } from './EditorTabBody'; +import 'app/features/panel/metrics_tab'; +import { EditorTabBody, EditorToolbarView} from './EditorTabBody'; import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; import { QueryInspector } from './QueryInspector'; import { QueryOptions } from './QueryOptions'; import { AngularQueryComponentScope } from 'app/features/panel/metrics_tab'; import { PanelOptionSection } from './PanelOptionSection'; + // Services import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { BackendSrv, getBackendSrv } from 'app/core/services/backend_srv'; import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; import config from 'app/core/config'; + // Types import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; @@ -204,14 +207,12 @@ export class QueriesTab extends PureComponent { const queryInspector: EditorToolbarView = { title: 'Query Inspector', render: this.renderQueryInspector, - buttonType: ToolbarButtonType.View, }; const dsHelp: EditorToolbarView = { heading: 'Help', icon: 'fa fa-question', render: this.renderHelp, - buttonType: ToolbarButtonType.View, }; return ( diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 8a57dd17190..bc7102f35dd 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -1,13 +1,16 @@ // Libraries import React, { PureComponent } from 'react'; + // Utils & Services import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; + // Components -import { EditorTabBody, EditorToolbarView, ToolbarButtonType } from './EditorTabBody'; +import { EditorTabBody, EditorToolbarView } from './EditorTabBody'; import { VizTypePicker } from './VizTypePicker'; import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp'; import { FadeIn } from 'app/core/components/Animations/FadeIn'; import { PanelOptionSection } from './PanelOptionSection'; + // Types import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; @@ -207,7 +210,6 @@ export class VisualizationTab extends PureComponent { heading: 'Help', icon: 'fa fa-question', render: this.renderHelp, - buttonType: ToolbarButtonType.View, }; return ( From 1eca81139b6b7f27fabd692a910c52d5f1335b55 Mon Sep 17 00:00:00 2001 From: Michael Inthilith Date: Fri, 4 Jan 2019 15:57:34 +0100 Subject: [PATCH 117/139] Fix stackdriver aggregation series merge Fixes #14581 --- public/app/plugins/datasource/stackdriver/constants.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/constants.ts b/public/app/plugins/datasource/stackdriver/constants.ts index b11f4a1bcb1..b77e1be7b4b 100644 --- a/public/app/plugins/datasource/stackdriver/constants.ts +++ b/public/app/plugins/datasource/stackdriver/constants.ts @@ -158,25 +158,25 @@ export const aggOptions = [ text: 'min', value: 'REDUCE_MIN', valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY], - metricKinds: [MetricKind.GAUGE, MetricKind.DELTA], + metricKinds: [MetricKind.GAUGE, MetricKind.DELTA, MetricKind.CUMULATIVE, MetricKind.METRIC_KIND_UNSPECIFIED], }, { text: 'max', value: 'REDUCE_MAX', valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY], - metricKinds: [MetricKind.GAUGE, MetricKind.DELTA], + metricKinds: [MetricKind.GAUGE, MetricKind.DELTA, MetricKind.CUMULATIVE, MetricKind.METRIC_KIND_UNSPECIFIED], }, { text: 'sum', value: 'REDUCE_SUM', valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY, ValueTypes.DISTRIBUTION], - metricKinds: [MetricKind.GAUGE, MetricKind.DELTA], + metricKinds: [MetricKind.GAUGE, MetricKind.DELTA, MetricKind.CUMULATIVE, MetricKind.METRIC_KIND_UNSPECIFIED], }, { text: 'std. dev.', value: 'REDUCE_STDDEV', valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY, ValueTypes.DISTRIBUTION], - metricKinds: [MetricKind.GAUGE, MetricKind.DELTA], + metricKinds: [MetricKind.GAUGE, MetricKind.DELTA, MetricKind.CUMULATIVE, MetricKind.METRIC_KIND_UNSPECIFIED], }, { text: 'count', From dd6f606cda65284a3d0ebf8ca3c700ea5f86f072 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Fri, 4 Jan 2019 16:30:19 +0100 Subject: [PATCH 118/139] docs: updated debian and centos repo. --- docs/sources/installation/debian.md | 21 ++++++--------------- docs/sources/installation/rpm.md | 16 +++++----------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index 7ed44572533..28b975e31e8 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -34,32 +34,23 @@ sudo dpkg -i grafana__amd64.deb Example: ```bash -wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.1.4_amd64.deb +wget https://dl.grafana.com/oss/release/grafana_5.4.2_amd64.deb sudo apt-get install -y adduser libfontconfig -sudo dpkg -i grafana_5.1.4_amd64.deb +sudo dpkg -i grafana_5.4.2_amd64.deb ``` ## APT Repository -Add the following line to your `/etc/apt/sources.list` file. +Create a file `/etc/apt/sources.list.d/grafana.list` and add the following to it. ```bash -deb https://packagecloud.io/grafana/stable/debian/ stretch main +deb https://packages.grafana.com/oss/deb stable main ``` -Use the above line even if you are on Ubuntu or another Debian version. -There is also a testing repository if you want beta or release -candidates. +Use the above line even if you are on Ubuntu or another Debian version. Then add our gpg key. This allows you to install signed packages. ```bash -deb https://packagecloud.io/grafana/testing/debian/ stretch main -``` - -Then add the [Package Cloud](https://packagecloud.io/grafana) key. This -allows you to install signed packages. - -```bash -curl https://packagecloud.io/gpg.key | sudo apt-key add - +curl https://packages.grafana.com/gpg.key | sudo apt-key add - ``` Update your Apt repositories and install Grafana diff --git a/docs/sources/installation/rpm.md b/docs/sources/installation/rpm.md index 5bf3b7ed745..559bb0a8ef4 100644 --- a/docs/sources/installation/rpm.md +++ b/docs/sources/installation/rpm.md @@ -32,7 +32,7 @@ $ sudo yum install Example: ```bash -$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.4-1.x86_64.rpm +$ sudo yum install https://dl.grafana.com/oss/release/grafana-5.4.2-1.x86_64.rpm ``` Or install manually using `rpm`. First execute @@ -44,7 +44,7 @@ $ wget Example: ```bash -$ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.4-1.x86_64.rpm +$ wget https://dl.grafana.com/oss/release/grafana-5.4.2-1.x86_64.rpm ``` ### On CentOS / Fedora / Redhat: @@ -67,21 +67,15 @@ Add the following to a new file at `/etc/yum.repos.d/grafana.repo` ```bash [grafana] name=grafana -baseurl=https://packagecloud.io/grafana/stable/el/7/$basearch +baseurl=https://packages.grafana.com/oss/rpm repo_gpgcheck=1 enabled=1 gpgcheck=1 -gpgkey=https://packagecloud.io/gpg.key https://grafanarel.s3.amazonaws.com/RPM-GPG-KEY-grafana +gpgkey=https://packages.grafana.com/gpg.key sslverify=1 sslcacert=/etc/pki/tls/certs/ca-bundle.crt ``` -There is also a testing repository if you want beta or release candidates. - -```bash -baseurl=https://packagecloud.io/grafana/testing/el/7/$basearch -``` - Then install Grafana via the `yum` command. ```bash @@ -91,7 +85,7 @@ $ sudo yum install grafana ### RPM GPG Key The RPMs are signed, you can verify the signature with this [public GPG -key](https://grafanarel.s3.amazonaws.com/RPM-GPG-KEY-grafana). +key](https://packages.grafana.com/gpg.key). ## Package details From 9759ee53ca4ac48202c8d1bedea92138c66f2945 Mon Sep 17 00:00:00 2001 From: Michael Inthilith Date: Fri, 4 Jan 2019 16:58:27 +0100 Subject: [PATCH 119/139] Add mean on distribution as well --- public/app/plugins/datasource/stackdriver/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/stackdriver/constants.ts b/public/app/plugins/datasource/stackdriver/constants.ts index b77e1be7b4b..49ac5131f0d 100644 --- a/public/app/plugins/datasource/stackdriver/constants.ts +++ b/public/app/plugins/datasource/stackdriver/constants.ts @@ -151,7 +151,7 @@ export const aggOptions = [ { text: 'mean', value: 'REDUCE_MEAN', - valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY], + valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY, ValueTypes.DISTRIBUTION], metricKinds: [MetricKind.GAUGE, MetricKind.DELTA], }, { From c39dc1fb1502a7a4a53570e91a00d48e762843b1 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Sat, 5 Jan 2019 15:19:54 +0100 Subject: [PATCH 120/139] Some cleanup --- public/app/features/datasources/settings/BasicSettings.tsx | 2 +- .../settings/__snapshots__/BasicSettings.test.tsx.snap | 2 +- public/app/features/explore/LogLabels.tsx | 1 - public/app/features/explore/Typeahead.tsx | 2 +- public/app/features/panel/metrics_tab.ts | 3 --- public/app/plugins/datasource/influxdb/datasource.ts | 2 +- .../plugins/datasource/loki/components/LokiQueryField.tsx | 1 - public/app/plugins/datasource/stackdriver/datasource.ts | 1 - public/app/plugins/datasource/stackdriver/query_ctrl.ts | 1 - public/app/plugins/panel/heatmap/rendering.ts | 7 ++----- public/emails/invited_to_org.html | 2 +- public/emails/new_user_invite.html | 2 +- scripts/webpack/webpack.common.js | 4 +--- scripts/webpack/webpack.prod.js | 1 - 14 files changed, 9 insertions(+), 22 deletions(-) diff --git a/public/app/features/datasources/settings/BasicSettings.tsx b/public/app/features/datasources/settings/BasicSettings.tsx index 569e0909c4d..120e002ac68 100644 --- a/public/app/features/datasources/settings/BasicSettings.tsx +++ b/public/app/features/datasources/settings/BasicSettings.tsx @@ -16,7 +16,7 @@ const BasicSettings: SFC = ({ dataSourceName, isDefault, onDefaultChange,