Просмотр исходного кода

Merge branch '15217-panels-without-queries' into move-error-boundry

Peter Holmberg 6 лет назад
Родитель
Сommit
f52aa4de4e
100 измененных файлов с 770 добавлено и 639 удалено
  1. 7 1
      CHANGELOG.md
  2. 0 3
      devenv/docker/blocks/elastic5/docker-compose.yaml
  3. 1 1
      package.json
  4. 2 2
      packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx
  5. 2 2
      packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss
  6. 3 3
      packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.test.tsx
  7. 8 9
      packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx
  8. 1 1
      packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss
  9. 6 2
      packages/grafana-ui/src/utils/namedColorsPalette.test.ts
  10. 2 1
      packages/grafana-ui/src/utils/namedColorsPalette.ts
  11. 9 2
      pkg/metrics/metrics.go
  12. 1 1
      public/app/core/components/EmptyListCTA/EmptyListCTA.tsx
  13. 1 1
      public/app/core/components/EmptyListCTA/__snapshots__/EmptyListCTA.test.tsx.snap
  14. 1 1
      public/app/core/components/OrgActionBar/OrgActionBar.tsx
  15. 1 1
      public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap
  16. 1 1
      public/app/core/components/PermissionList/AddPermission.tsx
  17. 1 1
      public/app/core/components/SharedPreferences/SharedPreferences.tsx
  18. 5 8
      public/app/core/components/manage_dashboards/manage_dashboards.html
  19. 4 2
      public/app/core/utils/explore.test.ts
  20. 4 1
      public/app/core/utils/explore.ts
  21. 1 1
      public/app/features/admin/partials/edit_org.html
  22. 4 4
      public/app/features/admin/partials/edit_user.html
  23. 1 1
      public/app/features/admin/partials/new_user.html
  24. 1 1
      public/app/features/admin/partials/orgs.html
  25. 1 1
      public/app/features/admin/partials/users.html
  26. 1 1
      public/app/features/alerting/partials/notification_edit.html
  27. 1 2
      public/app/features/alerting/partials/notifications_list.html
  28. 4 4
      public/app/features/annotations/partials/editor.html
  29. 1 1
      public/app/features/annotations/partials/event_editor.html
  30. 3 3
      public/app/features/api-keys/ApiKeysPage.tsx
  31. 1 1
      public/app/features/api-keys/__snapshots__/ApiKeysPage.test.tsx.snap
  32. 1 1
      public/app/features/dashboard/components/DashExportModal/template.html
  33. 4 4
      public/app/features/dashboard/components/DashLinks/editor.html
  34. 1 3
      public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx
  35. 3 3
      public/app/features/dashboard/components/DashboardSettings/template.html
  36. 1 1
      public/app/features/dashboard/components/ExportDataModal/template.html
  37. 1 1
      public/app/features/dashboard/components/RowOptions/template.html
  38. 1 1
      public/app/features/dashboard/components/SaveModals/SaveDashboardAsModalCtrl.ts
  39. 2 2
      public/app/features/dashboard/components/SaveModals/SaveDashboardModalCtrl.ts
  40. 1 1
      public/app/features/dashboard/components/SaveModals/SaveProvisionedDashboardModalCtrl.ts
  41. 1 1
      public/app/features/dashboard/components/ShareModal/template.html
  42. 3 3
      public/app/features/dashboard/components/TimePicker/template.html
  43. 1 1
      public/app/features/dashboard/components/UnsavedChangesModal/UnsavedChangesModalCtrl.ts
  44. 1 1
      public/app/features/dashboard/components/VersionHistory/template.html
  45. 1 2
      public/app/features/dashboard/dashgrid/DataPanel.tsx
  46. 38 24
      public/app/features/dashboard/dashgrid/PanelChrome.tsx
  47. 1 0
      public/app/features/datasources/settings/ButtonRow.test.tsx
  48. 8 2
      public/app/features/datasources/settings/ButtonRow.tsx
  49. 52 43
      public/app/features/datasources/settings/DataSourceSettingsPage.tsx
  50. 9 2
      public/app/features/datasources/settings/__snapshots__/ButtonRow.test.tsx.snap
  51. 4 0
      public/app/features/datasources/settings/__snapshots__/DataSourceSettingsPage.test.tsx.snap
  52. 1 1
      public/app/features/explore/GraphContainer.tsx
  53. 14 13
      public/app/features/explore/Logs.tsx
  54. 26 4
      public/app/features/explore/LogsContainer.tsx
  55. 1 1
      public/app/features/explore/TableContainer.tsx
  56. 9 0
      public/app/features/explore/state/actionTypes.ts
  57. 41 12
      public/app/features/explore/state/actions.ts
  58. 11 4
      public/app/features/explore/state/reducers.ts
  59. 9 3
      public/app/features/folders/FolderPermissions.tsx
  60. 1 1
      public/app/features/folders/FolderSettingsPage.tsx
  61. 2 2
      public/app/features/folders/__snapshots__/FolderSettingsPage.test.tsx.snap
  62. 1 1
      public/app/features/folders/partials/create_folder.html
  63. 1 1
      public/app/features/manage-dashboards/components/MoveToFolderModal/template.html
  64. 1 1
      public/app/features/manage-dashboards/components/UploadDashboard/uploadDashboardDirective.ts
  65. 1 1
      public/app/features/manage-dashboards/partials/dashboard_import.html
  66. 1 1
      public/app/features/org/OrgProfile.tsx
  67. 1 1
      public/app/features/org/__snapshots__/OrgProfile.test.tsx.snap
  68. 1 1
      public/app/features/org/partials/invite.html
  69. 1 1
      public/app/features/org/partials/newOrg.html
  70. 1 1
      public/app/features/org/partials/select_org.html
  71. 3 0
      public/app/features/panel/specs/metrics_panel_ctrl.test.ts
  72. 2 2
      public/app/features/playlist/partials/playlist.html
  73. 1 2
      public/app/features/playlist/partials/playlists.html
  74. 2 2
      public/app/features/plugins/partials/plugin_edit.html
  75. 1 1
      public/app/features/profile/partials/change_password.html
  76. 1 1
      public/app/features/profile/partials/profile.html
  77. 3 3
      public/app/features/teams/TeamGroupSync.tsx
  78. 3 3
      public/app/features/teams/TeamList.tsx
  79. 2 2
      public/app/features/teams/TeamMembers.tsx
  80. 1 1
      public/app/features/teams/TeamPages.tsx
  81. 1 1
      public/app/features/teams/TeamSettings.tsx
  82. 4 4
      public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap
  83. 273 277
      public/app/features/teams/__snapshots__/TeamList.test.tsx.snap
  84. 3 3
      public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap
  85. 5 17
      public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap
  86. 1 1
      public/app/features/teams/__snapshots__/TeamSettings.test.tsx.snap
  87. 1 1
      public/app/features/teams/partials/create_team.html
  88. 4 4
      public/app/features/templating/partials/editor.html
  89. 2 2
      public/app/features/users/UsersActionBar.tsx
  90. 2 2
      public/app/features/users/__snapshots__/UsersActionBar.test.tsx.snap
  91. 1 1
      public/app/partials/confirm_modal.html
  92. 1 1
      public/app/partials/edit_json.html
  93. 1 1
      public/app/partials/login.html
  94. 3 3
      public/app/partials/reset_password.html
  95. 1 1
      public/app/partials/signup_invited.html
  96. 1 1
      public/app/partials/signup_step2.html
  97. 7 1
      public/app/types/explore.ts
  98. 49 37
      public/sass/_variables.dark.scss
  99. 54 46
      public/sass/_variables.light.scss
  100. 0 8
      public/sass/base/_type.scss

+ 7 - 1
CHANGELOG.md

@@ -1,4 +1,6 @@
-# 6.0.0-beta2 (unreleased)
+# 6.0.0-beta3 (unreleased)
+
+# 6.0.0-beta2 (2019-02-11)
 
 ### New Features
 * **AzureMonitor**: Enable alerting by converting Azure Monitor API to Go [#14623](https://github.com/grafana/grafana/issues/14623)
@@ -19,6 +21,10 @@
 * **AzureMonitor**: improve autocomplete for Log Analytics and App Insights editor [#15131](https://github.com/grafana/grafana/issues/15131)
 * **LDAP**: Fix IPA/FreeIPA v4.6.4 does not allow LDAP searches with empty attributes [#14432](https://github.com/grafana/grafana/issues/14432)
 
+### Breaking changes
+
+* **Internal Metrics** Edition has been added to the build_info metric. This will break any Graphite queries using this metric. Edition will be a new label for the Prometheus metric. [#15363](https://github.com/grafana/grafana/pull/15363)
+
 ### 6.0.0-beta1 fixes
 
 * **Postgres**: Fix default port not added when port not configured [#15189](https://github.com/grafana/grafana/issues/15189)

+ 0 - 3
devenv/docker/blocks/elastic5/docker-compose.yaml

@@ -1,6 +1,3 @@
-# You need to run 'sysctl -w vm.max_map_count=262144' on the host machine
-version: '2'
-services:
   elasticsearch5:
     image: elasticsearch:5
     command: elasticsearch

+ 1 - 1
package.json

@@ -5,7 +5,7 @@
     "company": "Grafana Labs"
   },
   "name": "grafana",
-  "version": "6.0.0-prebeta2",
+  "version": "6.0.0-pre3",
   "repository": {
     "type": "git",
     "url": "http://github.com/grafana/grafana.git"

+ 2 - 2
packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx

@@ -69,8 +69,8 @@ export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSel
   }
 
   render() {
-    const leftButtonClass = this.state.yaxis === 1 ? 'btn-success' : 'btn-inverse';
-    const rightButtonClass = this.state.yaxis === 2 ? 'btn-success' : 'btn-inverse';
+    const leftButtonClass = this.state.yaxis === 1 ? 'btn-primary' : 'btn-inverse';
+    const rightButtonClass = this.state.yaxis === 2 ? 'btn-primary' : 'btn-inverse';
 
     return (
       <div className="p-b-1">

+ 2 - 2
packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss

@@ -29,14 +29,14 @@
 
   &:hover {
     .panel-options-group__add-circle {
-      background-color: $btn-success-bg;
+      background-color: $btn-primary-bg;
       color: $white;
     }
   }
 }
 
 .panel-options-group__add-circle {
-  @include gradientBar($btn-success-bg, $btn-success-bg-hl);
+  @include gradientBar($btn-success-bg, $btn-success-bg-hl, #fff);
 
   border-radius: 50px;
   width: 20px;

+ 3 - 3
packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.test.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { ChangeEvent } from 'react';
 import { shallow } from 'enzyme';
 
 import { ThresholdsEditor, Props } from './ThresholdsEditor';
@@ -118,7 +118,7 @@ describe('change threshold value', () => {
     ];
     const instance = setup({ thresholds });
 
-    const mockEvent = { target: { value: 12 } };
+    const mockEvent = ({ target: { value: '12' } } as any) as ChangeEvent<HTMLInputElement>;
 
     instance.onChangeThresholdValue(mockEvent, thresholds[0]);
 
@@ -137,7 +137,7 @@ describe('change threshold value', () => {
       thresholds,
     };
 
-    const mockEvent = { target: { value: 78 } };
+    const mockEvent = ({ target: { value: '78' } } as any) as ChangeEvent<HTMLInputElement>;
 
     instance.onChangeThresholdValue(mockEvent, thresholds[1]);
 

+ 8 - 9
packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx

@@ -1,4 +1,4 @@
-import React, { PureComponent } from 'react';
+import React, { PureComponent, ChangeEvent } from 'react';
 import { Threshold } from '../../types';
 import { ColorPicker } from '../ColorPicker/ColorPicker';
 import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup';
@@ -94,14 +94,15 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
     );
   };
 
-  onChangeThresholdValue = (event: any, threshold: Threshold) => {
+  onChangeThresholdValue = (event: ChangeEvent<HTMLInputElement>, threshold: Threshold) => {
     if (threshold.index === 0) {
       return;
     }
 
     const { thresholds } = this.state;
-    const parsedValue = parseInt(event.target.value, 10);
-    const value = isNaN(parsedValue) ? null : parsedValue;
+    const cleanValue = event.target.value.replace(/,/g, '.');
+    const parsedValue = parseFloat(cleanValue);
+    const value = isNaN(parsedValue) ? '' : parsedValue;
 
     const newThresholds = thresholds.map(t => {
       if (t === threshold && t.index !== 0) {
@@ -164,16 +165,14 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
         <div className="thresholds-row-input-inner-color">
           {threshold.color && (
             <div className="thresholds-row-input-inner-color-colorpicker">
-              <ColorPicker
-                color={threshold.color}
-                onChange={color => this.onChangeThresholdColor(threshold, color)}
-              />
+              <ColorPicker color={threshold.color} onChange={color => this.onChangeThresholdColor(threshold, color)} />
             </div>
           )}
         </div>
         <div className="thresholds-row-input-inner-value">
           <input
-            type="text"
+            type="number"
+            step="0.0001"
             onChange={event => this.onChangeThresholdValue(event, threshold)}
             value={value}
             onBlur={this.onBlur}

+ 1 - 1
packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss

@@ -21,7 +21,7 @@
 }
 
 .thresholds-row-add-button {
-  @include buttonBackground($btn-success-bg, $btn-success-bg-hl);
+  @include buttonBackground($btn-success-bg, $btn-success-bg-hl, #fff);
 
   align-self: center;
   margin-right: 5px;

+ 6 - 2
packages/grafana-ui/src/utils/namedColorsPalette.test.ts

@@ -44,8 +44,8 @@ describe('colors', () => {
   });
 
   describe('getColorFromHexRgbOrName', () => {
-    it('returns undefined for unknown color', () => {
-      expect(() => getColorFromHexRgbOrName('aruba-sunshine')).toThrow();
+    it('returns black for unknown color', () => {
+      expect(getColorFromHexRgbOrName('aruba-sunshine')).toBe("#000000");
     });
 
     it('returns dark hex variant for known color if theme not specified', () => {
@@ -64,5 +64,9 @@ describe('colors', () => {
       expect(getColorFromHexRgbOrName('rgb(0,0,0)')).toBe('rgb(0,0,0)');
       expect(getColorFromHexRgbOrName('rgba(0,0,0,1)')).toBe('rgba(0,0,0,1)');
     });
+
+    it('returns hex for named color that is not a part of named colors palette', () => {
+      expect(getColorFromHexRgbOrName('lime')).toBe('#00ff00');
+    });
   });
 });

+ 2 - 1
packages/grafana-ui/src/utils/namedColorsPalette.ts

@@ -1,5 +1,6 @@
 import { flatten } from 'lodash';
 import { GrafanaThemeType } from '../types';
+import tinycolor from 'tinycolor2';
 
 type Hue = 'green' | 'yellow' | 'red' | 'blue' | 'orange' | 'purple';
 
@@ -106,7 +107,7 @@ export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaThemeType
   const colorDefinition = getColorByName(color);
 
   if (!colorDefinition) {
-    throw new Error('Unknown color');
+    return new tinycolor(color).toHexString();
   }
 
   return theme ? colorDefinition.variants[theme] : colorDefinition.variants.dark;

+ 9 - 2
pkg/metrics/metrics.go

@@ -3,6 +3,8 @@ package metrics
 import (
 	"runtime"
 
+	"github.com/grafana/grafana/pkg/setting"
+
 	"github.com/prometheus/client_golang/prometheus"
 )
 
@@ -282,7 +284,7 @@ func init() {
 		Name:      "build_info",
 		Help:      "A metric with a constant '1' value labeled by version, revision, branch, and goversion from which Grafana was built.",
 		Namespace: exporterName,
-	}, []string{"version", "revision", "branch", "goversion"})
+	}, []string{"version", "revision", "branch", "goversion", "edition"})
 }
 
 // SetBuildInformation sets the build information for this binary
@@ -291,8 +293,13 @@ func SetBuildInformation(version, revision, branch string) {
 	// Once this have been released for some time we should be able to remote `M_Grafana_Version`
 	// The reason we added a new one is that its common practice in the prometheus community
 	// to name this metric `*_build_info` so its easy to do aggregation on all programs.
+	edition := "oss"
+	if setting.IsEnterprise {
+		edition = "enterprise"
+	}
+
 	M_Grafana_Version.WithLabelValues(version).Set(1)
-	grafanaBuildVersion.WithLabelValues(version, revision, branch, runtime.Version()).Set(1)
+	grafanaBuildVersion.WithLabelValues(version, revision, branch, runtime.Version(), edition).Set(1)
 }
 
 func initMetricVars() {

+ 1 - 1
public/app/core/components/EmptyListCTA/EmptyListCTA.tsx

@@ -20,7 +20,7 @@ class EmptyListCTA extends Component<Props, any> {
     return (
       <div className="empty-list-cta">
         <div className="empty-list-cta__title">{title}</div>
-        <a onClick={onClick} href={buttonLink} className="empty-list-cta__button btn btn-xlarge btn-success">
+        <a onClick={onClick} href={buttonLink} className="empty-list-cta__button btn btn-xlarge btn-primary">
           <i className={buttonIcon} />
           {buttonTitle}
         </a>

+ 1 - 1
public/app/core/components/EmptyListCTA/__snapshots__/EmptyListCTA.test.tsx.snap

@@ -10,7 +10,7 @@ exports[`EmptyListCTA renders correctly 1`] = `
     Title
   </div>
   <a
-    className="empty-list-cta__button btn btn-xlarge btn-success"
+    className="empty-list-cta__button btn btn-xlarge btn-primary"
     href="http://url/to/destination"
     onClick={[MockFunction]}
   >

+ 1 - 1
public/app/core/components/OrgActionBar/OrgActionBar.tsx

@@ -35,7 +35,7 @@ export default class OrgActionBar extends PureComponent<Props> {
           <LayoutSelector mode={layoutMode} onLayoutModeChanged={(mode: LayoutMode) => onSetLayoutMode(mode)} />
         </div>
         <div className="page-action-bar__spacer" />
-        <a className="btn btn-success" {...linkProps}>
+        <a className="btn btn-primary" {...linkProps}>
           {linkButton.title}
         </a>
       </div>

+ 1 - 1
public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap

@@ -29,7 +29,7 @@ exports[`Render should render component 1`] = `
     className="page-action-bar__spacer"
   />
   <a
-    className="btn btn-success"
+    className="btn btn-primary"
     href="some/url"
     target="_blank"
   >

+ 1 - 1
public/app/core/components/PermissionList/AddPermission.tsx

@@ -130,7 +130,7 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> {
             </div>
 
             <div className="gf-form">
-              <button data-save-permission className="btn btn-success" type="submit" disabled={!isValid}>
+              <button data-save-permission className="btn btn-primary" type="submit" disabled={!isValid}>
                 Save
               </button>
             </div>

+ 1 - 1
public/app/core/components/SharedPreferences/SharedPreferences.tsx

@@ -126,7 +126,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
           />
         </div>
         <div className="gf-form-button-row">
-          <button type="submit" className="btn btn-success">
+          <button type="submit" className="btn btn-primary">
             Save
           </button>
         </div>

+ 5 - 8
public/app/core/components/manage_dashboards/manage_dashboards.html

@@ -5,16 +5,13 @@
       <i class="gf-form-input-icon fa fa-search"></i>
     </label>
     <div class="page-action-bar__spacer"></div>
-    <a class="btn btn-success" ng-href="{{ctrl.createDashboardUrl()}}" ng-if="ctrl.hasEditPermissionInFolders || ctrl.canSave">
-      <i class="fa fa-plus"></i>
-      Dashboard
+    <a class="btn btn-primary" ng-href="{{ctrl.createDashboardUrl()}}" ng-if="ctrl.hasEditPermissionInFolders || ctrl.canSave">
+      New Dashboard
     </a>
-    <a class="btn btn-success" href="dashboards/folder/new" ng-if="!ctrl.folderId && ctrl.isEditor">
-      <i class="fa fa-plus"></i>
-      Folder
+    <a class="btn btn-inverse" href="dashboards/folder/new" ng-if="!ctrl.folderId && ctrl.isEditor">
+      New Folder
     </a>
-    <a class="btn btn-success" href="{{ctrl.importDashboardUrl()}}" ng-if="ctrl.hasEditPermissionInFolders || ctrl.canSave">
-      <i class="fa fa-plus"></i>
+    <a class="btn btn-inverse" href="{{ctrl.importDashboardUrl()}}" ng-if="ctrl.hasEditPermissionInFolders || ctrl.canSave">
       Import
     </a>
   </div>

+ 4 - 2
public/app/core/utils/explore.test.ts

@@ -8,6 +8,7 @@ import {
 } from './explore';
 import { ExploreUrlState } from 'app/types/explore';
 import store from 'app/core/store';
+import { LogsDedupStrategy } from 'app/core/logs_model';
 
 const DEFAULT_EXPLORE_STATE: ExploreUrlState = {
   datasource: null,
@@ -17,6 +18,7 @@ const DEFAULT_EXPLORE_STATE: ExploreUrlState = {
     showingGraph: true,
     showingTable: true,
     showingLogs: true,
+    dedupStrategy: LogsDedupStrategy.none,
   }
 };
 
@@ -78,7 +80,7 @@ describe('state functions', () => {
       expect(serializeStateToUrlParam(state)).toBe(
         '{"datasource":"foo","queries":[{"expr":"metric{test=\\"a/b\\"}"},' +
           '{"expr":"super{foo=\\"x/z\\"}"}],"range":{"from":"now-5h","to":"now"},' +
-          '"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true}}'
+          '"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true,"dedupStrategy":"none"}}'
       );
     });
 
@@ -100,7 +102,7 @@ describe('state functions', () => {
         },
       };
       expect(serializeStateToUrlParam(state, true)).toBe(
-        '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"},{"ui":[true,true,true]}]'
+        '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"},{"ui":[true,true,true,"none"]}]'
       );
     });
   });

+ 4 - 1
public/app/core/utils/explore.ts

@@ -21,6 +21,7 @@ import {
   QueryIntervals,
   QueryOptions,
 } from 'app/types/explore';
+import { LogsDedupStrategy } from 'app/core/logs_model';
 
 export const DEFAULT_RANGE = {
   from: 'now-6h',
@@ -31,6 +32,7 @@ export const DEFAULT_UI_STATE = {
   showingTable: true,
   showingGraph: true,
   showingLogs: true,
+  dedupStrategy: LogsDedupStrategy.none,
 };
 
 const MAX_HISTORY_ITEMS = 100;
@@ -183,6 +185,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
               showingGraph: segment.ui[0],
               showingLogs: segment.ui[1],
               showingTable: segment.ui[2],
+              dedupStrategy: segment.ui[3],
             };
           }
         });
@@ -204,7 +207,7 @@ export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: bo
       urlState.range.to,
       urlState.datasource,
       ...urlState.queries,
-      { ui: [!!urlState.ui.showingGraph, !!urlState.ui.showingLogs, !!urlState.ui.showingTable] },
+      { ui: [!!urlState.ui.showingGraph, !!urlState.ui.showingLogs, !!urlState.ui.showingTable, urlState.ui.dedupStrategy] },
     ]);
   }
   return JSON.stringify(urlState);

+ 1 - 1
public/app/features/admin/partials/edit_org.html

@@ -10,7 +10,7 @@
 		</div>
 
 		<div class="gf-form-button-row">
-			<button type="submit" class="btn btn-success" ng-click="update()" ng-show="!createMode">Update</button>
+			<button type="submit" class="btn btn-primary" ng-click="update()" ng-show="!createMode">Update</button>
 		</div>
 	</form>
 

+ 4 - 4
public/app/features/admin/partials/edit_user.html

@@ -21,7 +21,7 @@
 			</div>
 
 			<div class="gf-form-button-row">
-				<button type="submit" class="btn btn-success" ng-click="update()" ng-show="!createMode">Update</button>
+				<button type="submit" class="btn btn-primary" ng-click="update()" ng-show="!createMode">Update</button>
 			</div>
 	</form>
 
@@ -34,7 +34,7 @@
 		</div>
 
 		<div class="gf-form-button-row">
-			<button type="submit" class="btn btn-success" ng-click="setPassword()">Update</button>
+			<button type="submit" class="btn btn-primary" ng-click="setPassword()">Update</button>
 		</div>
 	</form>
 
@@ -46,7 +46,7 @@
 		</div>
 
 		<div class="gf-form-button-row">
-			<button type="submit" class="btn btn-success" ng-click="updatePermissions()">Update</button>
+			<button type="submit" class="btn btn-primary" ng-click="updatePermissions()">Update</button>
 		</div>
 	</form>
 
@@ -65,7 +65,7 @@
         	</span>
 			</div>
 			<div class="gf-form">
-				<button class="btn btn-success gf-form-btn" ng-click="addOrgUser()">Add</button>
+				<button class="btn btn-primary gf-form-btn" ng-click="addOrgUser()">Add</button>
 			</div>
 		</div>
 	</form>

+ 1 - 1
public/app/features/admin/partials/new_user.html

@@ -24,7 +24,7 @@
 		</div>
 
 		<div class="gf-form-button-row">
-			<button type="submit" class="btn btn-success" ng-click="create()">Create</button>
+			<button type="submit" class="btn btn-primary" ng-click="create()">Create</button>
 		</div>
 	</form>
 </div>

+ 1 - 1
public/app/features/admin/partials/orgs.html

@@ -3,7 +3,7 @@
 <div class="page-container page-body">
   <div class="page-action-bar">
     <div class="page-action-bar__spacer"></div>
-    <a class="page-header__cta btn btn-success" href="org/new">
+    <a class="page-header__cta btn btn-primary" href="org/new">
       <i class="fa fa-plus"></i>
       New Org
     </a>

+ 1 - 1
public/app/features/admin/partials/users.html

@@ -7,7 +7,7 @@
       <i class="gf-form-input-icon fa fa-search"></i>
     </label>
     <div class="page-action-bar__spacer"></div>
-    <a class="btn btn-success" href="admin/users/create">
+    <a class="btn btn-primary" href="admin/users/create">
       <i class="fa fa-plus"></i>
       Add new user
     </a>

+ 1 - 1
public/app/features/alerting/partials/notification_edit.html

@@ -68,7 +68,7 @@
     </div>
 
     <div class="gf-form-group gf-form-button-row">
-			<button type="submit" ng-click="ctrl.save()" class="btn btn-success width-7">Save</button>
+			<button type="submit" ng-click="ctrl.save()" class="btn btn-primary width-7">Save</button>
 			<button type="submit" ng-click="ctrl.testNotification()" class="btn btn-secondary width-7">Send Test</button>
 			<a href="alerting/notifications" class="btn btn-inverse">Back</a>
     </div>

+ 1 - 2
public/app/features/alerting/partials/notifications_list.html

@@ -7,8 +7,7 @@
       <div class="page-action-bar__spacer">
       </div>
 
-      <a href="alerting/notification/new" class="btn btn-success">
-        <i class="fa fa-plus"></i>
+      <a href="alerting/notification/new" class="btn btn-primary">
         New Channel
       </a>
     </div>

+ 4 - 4
public/app/features/annotations/partials/editor.html

@@ -9,7 +9,7 @@
 	<div ng-if="ctrl.mode === 'list'">
 		<div class="page-action-bar" ng-if="ctrl.annotations.length > 1">
 			<div class="page-action-bar__spacer"></div>
-			<a type="button" class="btn btn-success" ng-click="ctrl.setupNew();"><i class="fa fa-plus" ></i> New</a>
+			<a type="button" class="btn btn-primary" ng-click="ctrl.setupNew();"><i class="fa fa-plus" ></i> New</a>
 		</div>
 
 		<table class="filter-table filter-table--hover">
@@ -48,7 +48,7 @@
 		<div ng-if="ctrl.annotations.length === 1" class="p-t-2">
 			<div class="empty-list-cta">
 				<div class="empty-list-cta__title">There are no custom annotation queries added yet</div>
-				<a ng-click="ctrl.setupNew()" class="empty-list-cta__button btn btn-xlarge btn-success">
+				<a ng-click="ctrl.setupNew()" class="empty-list-cta__button btn btn-xlarge btn-primary">
 					<i class="gicon gicon-add-annotation"></i>
 					Add Annotation Query
 				</a>
@@ -105,8 +105,8 @@
 
 		<div class="gf-form">
 			<div class="gf-form-button-row p-y-0">
-				<button ng-show="ctrl.mode === 'new'" type="button" class="btn gf-form-button btn-success" ng-click="ctrl.add()">Add</button>
-				<button ng-show="ctrl.mode === 'edit'" type="button" class="btn btn-success pull-left" ng-click="ctrl.update()">Update</button>
+				<button ng-show="ctrl.mode === 'new'" type="button" class="btn gf-form-button btn-primary" ng-click="ctrl.add()">Add</button>
+				<button ng-show="ctrl.mode === 'edit'" type="button" class="btn btn-primary pull-left" ng-click="ctrl.update()">Update</button>
 			</div>
 		</div>
 	</div>

+ 1 - 1
public/app/features/annotations/partials/event_editor.html

@@ -26,7 +26,7 @@
 			</div>
 
 			<div class="gf-form-button-row">
-				<button type="submit" class="btn btn-success" ng-click="ctrl.save()">Save</button>
+				<button type="submit" class="btn btn-primary" ng-click="ctrl.save()">Save</button>
 				<button ng-if="ctrl.event.id" type="submit" class="btn btn-danger" ng-click="ctrl.delete()">Delete</button>
 				<a class="btn-text" ng-click="ctrl.close();">Cancel</a>
 			</div>

+ 3 - 3
public/app/features/api-keys/ApiKeysPage.tsx

@@ -169,7 +169,7 @@ export class ApiKeysPage extends PureComponent<Props, any> {
                 </span>
               </div>
               <div className="gf-form">
-                <button className="btn gf-form-btn btn-success">Add</button>
+                <button className="btn gf-form-btn btn-primary">Add</button>
               </div>
             </div>
           </form>
@@ -199,8 +199,8 @@ export class ApiKeysPage extends PureComponent<Props, any> {
           </div>
 
           <div className="page-action-bar__spacer" />
-          <button className="btn btn-success pull-right" onClick={this.onToggleAdding} disabled={isAdding}>
-            <i className="fa fa-plus" /> Add API Key
+          <button className="btn btn-primary pull-right" onClick={this.onToggleAdding} disabled={isAdding}>
+            Add API Key
           </button>
         </div>
 

+ 1 - 1
public/app/features/api-keys/__snapshots__/ApiKeysPage.test.tsx.snap

@@ -134,7 +134,7 @@ exports[`Render should render CTA if there are no API keys 1`] = `
               className="gf-form"
             >
               <button
-                className="btn gf-form-btn btn-success"
+                className="btn gf-form-btn btn-primary"
               >
                 Add
               </button>

+ 1 - 1
public/app/features/dashboard/components/DashExportModal/template.html

@@ -12,7 +12,7 @@
 		</gf-form-switch>
 
 		<div class="gf-form-button-row">
-			<button type="button" class="btn gf-form-btn width-10 btn-success" ng-click="ctrl.saveDashboardAsFile()">
+			<button type="button" class="btn gf-form-btn width-10 btn-primary" ng-click="ctrl.saveDashboardAsFile()">
 				<i class="fa fa-save"></i> Save to file
 			</button>
 			<button type="button" class="btn gf-form-btn width-10 btn-secondary" ng-click="ctrl.viewJson()">

+ 4 - 4
public/app/features/dashboard/components/DashLinks/editor.html

@@ -10,7 +10,7 @@
       <div class="empty-list-cta__title">
         There are no dashboard links added yet
       </div>
-      <a ng-click="ctrl.setupNew()" class="empty-list-cta__button btn btn-xlarge btn-success">
+      <a ng-click="ctrl.setupNew()" class="empty-list-cta__button btn btn-xlarge btn-primary">
         <i class="gicon gicon-add-link"></i>
         Add Dashboard Link
       </a>
@@ -26,7 +26,7 @@
   <div ng-if="ctrl.dashboard.links.length > 0">
     <div class="page-action-bar">
       <div class="page-action-bar__spacer"></div>
-      <a type="button" class="btn btn-success" ng-click="ctrl.setupNew()">
+      <a type="button" class="btn btn-primary" ng-click="ctrl.setupNew()">
         <i class="fa fa-plus"></i> New</a>
     </div>
     <table class="filter-table filter-table--hover">
@@ -126,10 +126,10 @@
       </div>
     </div>
   </div>
-  <button class="btn btn-success" ng-if="ctrl.mode == 'new'" ng-click="ctrl.addLink()">
+  <button class="btn btn-primary" ng-if="ctrl.mode == 'new'" ng-click="ctrl.addLink()">
     Add
   </button>
-  <button class="btn btn-success" ng-if="ctrl.mode == 'edit'" ng-click="ctrl.saveLink()">
+  <button class="btn btn-primary" ng-if="ctrl.mode == 'edit'" ng-click="ctrl.saveLink()">
     Update
   </button>
 </div>

+ 1 - 3
public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx

@@ -76,9 +76,7 @@ export class DashboardPermissions extends PureComponent<Props, State> {
               </div>
             </Tooltip>
             <div className="page-action-bar__spacer" />
-            <button className="btn btn-success pull-right" onClick={this.onOpenAddPermissions} disabled={isAdding}>
-              <i className="fa fa-plus" /> Add Permission
-            </button>
+            <button className="btn btn-primary pull-right" onClick={this.onOpenAddPermissions} disabled={isAdding}>Add Permission</button>
           </div>
         </div>
         <SlideDown in={isAdding}>

+ 3 - 3
public/app/features/dashboard/components/DashboardSettings/template.html

@@ -10,7 +10,7 @@
 	</a>
 
 	<div class="dashboard-settings__aside-actions">
-    <button class="btn btn-success" ng-click="ctrl.saveDashboard()" ng-show="ctrl.canSave">
+    <button class="btn btn-primary" ng-click="ctrl.saveDashboard()" ng-show="ctrl.canSave">
 			<i class="fa fa-save"></i> Save
 		</button>
 		<button class="btn btn-inverse" ng-click="ctrl.openSaveAsModal()" ng-show="ctrl.canSaveAs">
@@ -100,7 +100,7 @@
 	</div>
 
   <div class="gf-form-button-row">
-    <button class="btn btn-success" ng-click="ctrl.saveDashboardJson()" ng-show="ctrl.canSave">
+    <button class="btn btn-primary" ng-click="ctrl.saveDashboardJson()" ng-show="ctrl.canSave">
       <i class="fa fa-save"></i> Save Changes
     </button>
   </div>
@@ -128,7 +128,7 @@
 <div class="dashboard-settings__content" ng-if="ctrl.viewId === 'make_editable'">
   <h3 class="dashboard-settings__header">Make Editable</h3>
 
-  <button class="btn btn-success" ng-click="ctrl.makeEditable()">
+  <button class="btn btn-primary" ng-click="ctrl.makeEditable()">
     Make Editable
   </button>
 </div>

+ 1 - 1
public/app/features/dashboard/components/ExportDataModal/template.html

@@ -29,7 +29,7 @@
     </div>
 
     <div class="gf-form-button-row text-center">
-      <a class="btn btn-success" ng-click="ctrl.export();">Export</a>
+      <a class="btn btn-primary" ng-click="ctrl.export();">Export</a>
       <a class="btn-text" ng-click="ctrl.dismiss();">Cancel</a>
     </div>
   </div>

+ 1 - 1
public/app/features/dashboard/components/RowOptions/template.html

@@ -22,7 +22,7 @@
 			</div>
 
 			<div class="gf-form-button-row">
-				<button type="submit" class="btn btn-success" ng-click="ctrl.update()">Update</button>
+				<button type="submit" class="btn btn-primary" ng-click="ctrl.update()">Update</button>
 				<button type="button" class="btn btn-inverse" ng-click="ctrl.dismiss()">Cancel</button>
 			</div>
 		</div>

+ 1 - 1
public/app/features/dashboard/components/SaveModals/SaveDashboardAsModalCtrl.ts

@@ -32,7 +32,7 @@ const template = `
 		</div>
 
 		<div class="gf-form-button-row text-center">
-			<button type="submit" class="btn btn-success" ng-click="ctrl.save()" ng-disabled="!ctrl.isValidFolderSelection">Save</button>
+			<button type="submit" class="btn btn-primary" ng-click="ctrl.save()" ng-disabled="!ctrl.isValidFolderSelection">Save</button>
 			<a class="btn-text" ng-click="ctrl.dismiss();">Cancel</a>
 		</div>
 	</form>

+ 2 - 2
public/app/features/dashboard/components/SaveModals/SaveDashboardModalCtrl.ts

@@ -52,8 +52,8 @@ const template = `
       <button
         id="saveBtn"
         type="submit"
-        class="btn btn-success"
-        ng-class="{'btn-success--processing': ctrl.isSaving}"
+        class="btn btn-primary"
+        ng-class="{'btn-primary--processing': ctrl.isSaving}"
         ng-disabled="ctrl.saveForm.$invalid || ctrl.isSaving"
       >
         <span ng-if="!ctrl.isSaving">Save</span>

+ 1 - 1
public/app/features/dashboard/components/SaveModals/SaveProvisionedDashboardModalCtrl.ts

@@ -26,7 +26,7 @@ const template = `
         <code-editor content="ctrl.dashboardJson" data-mode="json" data-max-lines=15></code-editor>
       </div>
       <div class="gf-form-button-row">
-        <button class="btn btn-success" clipboard-button="ctrl.getJsonForClipboard()">
+        <button class="btn btn-primary" clipboard-button="ctrl.getJsonForClipboard()">
           <i class="fa fa-clipboard"></i>&nbsp;Copy JSON to Clipboard
         </button>
         <button class="btn btn-secondary" clipboard-button="ctrl.save()">

+ 1 - 1
public/app/features/dashboard/components/ShareModal/template.html

@@ -163,7 +163,7 @@
 				</div>
 
 				<div ng-if="step === 1" class="gf-form-button-row">
-					<button class="btn gf-form-btn width-10 btn-success" ng-click="createSnapshot()" ng-disabled="loading">
+					<button class="btn gf-form-btn width-10 btn-primary" ng-click="createSnapshot()" ng-disabled="loading">
 						<i class="fa fa-save"></i>
 						Local Snapshot
 					</button>

+ 3 - 3
public/app/features/dashboard/components/TimePicker/template.html

@@ -48,7 +48,7 @@
           <input type="text" class="gf-form-input input-large" ng-model="ctrl.editTimeRaw.from" input-datetime>
         </div>
         <div class="gf-form">
-          <button class="btn gf-form-btn btn-primary" type="button" ng-click="openFromPicker=!openFromPicker">
+          <button class="btn gf-form-btn btn-secondary" type="button" ng-click="openFromPicker=!openFromPicker">
             <i class="fa fa-calendar"></i>
           </button>
         </div>
@@ -65,7 +65,7 @@
           <input type="text" class="gf-form-input input-large" ng-model="ctrl.editTimeRaw.to" input-datetime>
         </div>
         <div class="gf-form">
-          <button class="btn gf-form-btn btn-primary" type="button" ng-click="openToPicker=!openToPicker">
+          <button class="btn gf-form-btn btn-secondary" type="button" ng-click="openToPicker=!openToPicker">
             <i class="fa fa-calendar"></i>
           </button>
         </div>
@@ -81,7 +81,7 @@
           <select ng-model="ctrl.refresh.value" class="gf-form-input input-medium" ng-options="f.value as f.text for f in ctrl.refresh.options"></select>
         </div>
         <div class="gf-form">
-          <button type="submit" class="btn gf-form-btn btn-secondary" ng-click="ctrl.applyCustom();" ng-disabled="!timeForm.$valid">Apply</button>
+          <button type="submit" class="btn gf-form-btn btn-primary" ng-click="ctrl.applyCustom();" ng-disabled="!timeForm.$valid">Apply</button>
         </div>
       </div>
     </form>

+ 1 - 1
public/app/features/dashboard/components/UnsavedChangesModal/UnsavedChangesModalCtrl.ts

@@ -20,7 +20,7 @@ const template = `
     </div>
 
     <div class="confirm-modal-buttons">
-      <button type="button" class="btn btn-success" ng-click="ctrl.save()">Save</button>
+      <button type="button" class="btn btn-primary" ng-click="ctrl.save()">Save</button>
       <button type="button" class="btn btn-danger" ng-click="ctrl.discard()">Discard</button>
       <button type="button" class="btn btn-inverse" ng-click="ctrl.dismiss()">Cancel</button>
     </div>

+ 1 - 1
public/app/features/dashboard/components/VersionHistory/template.html

@@ -64,7 +64,7 @@
             Show more versions
           </button>
           <button type="button"
-                  class="btn btn-success"
+                  class="btn btn-primary"
                   ng-if="ctrl.revisions.length > 1"
                   ng-disabled="!ctrl.canCompare"
                   ng-click="ctrl.getDiff(ctrl.diff)"

+ 1 - 2
public/app/features/dashboard/dashgrid/DataPanel.tsx

@@ -24,7 +24,7 @@ interface RenderProps {
 export interface Props {
   datasource: string | null;
   queries: any[];
-  panelId?: number;
+  panelId: number;
   dashboardId?: number;
   isVisible?: boolean;
   timeRange?: TimeRange;
@@ -46,7 +46,6 @@ export interface State {
 export class DataPanel extends Component<Props, State> {
   static defaultProps = {
     isVisible: true,
-    panelId: 1,
     dashboardId: 1,
   };
 

+ 38 - 24
public/app/features/dashboard/dashgrid/PanelChrome.tsx

@@ -100,6 +100,25 @@ export class PanelChrome extends PureComponent<Props, State> {
     return !this.props.dashboard.otherPanelInFullscreen(this.props.panel);
   }
 
+  get hasPanelSnapshot() {
+    const { panel } = this.props;
+    return panel.snapshotData && panel.snapshotData.length;
+  }
+
+  get hasDataPanel() {
+    return !this.props.plugin.noQueries && !this.hasPanelSnapshot;
+  }
+
+  get getDataForPanel() {
+    const { panel, plugin } = this.props;
+
+    if (plugin.noQueries) {
+      return null;
+    }
+
+    return this.hasPanelSnapshot ? snapshotDataToPanelData(panel) : null;
+  }
+
   renderPanelPlugin(loading: LoadingState, panelData: PanelData, width: number, height: number): JSX.Element {
     const { panel, plugin } = this.props;
     const { timeRange, renderCounter } = this.state;
@@ -127,34 +146,29 @@ export class PanelChrome extends PureComponent<Props, State> {
     );
   }
 
-  renderHelper = (width: number, height: number): JSX.Element => {
-    const { panel, plugin } = this.props;
+  renderPanelBody = (width: number, height: number): JSX.Element => {
+    const { panel } = this.props;
     const { refreshCounter, timeRange } = this.state;
     const { datasource, targets } = panel;
     return (
       <>
-        {panel.snapshotData && panel.snapshotData.length > 0 ? (
-          this.renderPanelPlugin(LoadingState.Done, snapshotDataToPanelData(panel), width, height)
+        {this.hasDataPanel ? (
+          <DataPanel
+            panelId={panel.id}
+            datasource={datasource}
+            queries={targets}
+            timeRange={timeRange}
+            isVisible={this.isVisible}
+            widthPixels={width}
+            refreshCounter={refreshCounter}
+            onDataResponse={this.onDataResponse}
+          >
+            {({ loading, panelData }) => {
+              return this.renderPanelPlugin(loading, panelData, width, height);
+            }}
+          </DataPanel>
         ) : (
-          <>
-            {plugin.noQueries ? (
-              this.renderPanelPlugin(LoadingState.Done, null, width, height)
-            ) : (
-              <DataPanel
-                datasource={datasource}
-                queries={targets}
-                timeRange={timeRange}
-                isVisible={this.isVisible}
-                widthPixels={width}
-                refreshCounter={refreshCounter}
-                onDataResponse={this.onDataResponse}
-              >
-                {({ loading, panelData }) => {
-                  return this.renderPanelPlugin(loading, panelData, width, height);
-                }}
-              </DataPanel>
-            )}
-          </>
+          this.renderPanelPlugin(LoadingState.Done, this.getDataForPanel, width, height)
         )}
       </>
     );
@@ -199,7 +213,7 @@ export class PanelChrome extends PureComponent<Props, State> {
                     this.onError(error.message || DEFAULT_PLUGIN_ERROR);
                     return null;
                   }
-                  return this.renderHelper(width, height);
+                  return this.renderPanelBody(width, height);
                 }}
               </ErrorBoundary>
             </div>

+ 1 - 0
public/app/features/datasources/settings/ButtonRow.test.tsx

@@ -7,6 +7,7 @@ const setup = (propOverrides?: object) => {
     isReadOnly: true,
     onSubmit: jest.fn(),
     onDelete: jest.fn(),
+    onTest: jest.fn(),
   };
 
   Object.assign(props, propOverrides);

+ 8 - 2
public/app/features/datasources/settings/ButtonRow.tsx

@@ -4,14 +4,20 @@ export interface Props {
   isReadOnly: boolean;
   onDelete: () => void;
   onSubmit: (event) => void;
+  onTest: (event) => void;
 }
 
-const ButtonRow: FC<Props> = ({ isReadOnly, onDelete, onSubmit }) => {
+const ButtonRow: FC<Props> = ({ isReadOnly, onDelete, onSubmit, onTest }) => {
   return (
     <div className="gf-form-button-row">
-      <button type="submit" className="btn btn-success" disabled={isReadOnly} onClick={event => onSubmit(event)}>
+      <button type="submit" className="btn btn-primary" disabled={isReadOnly} onClick={event => onSubmit(event)}>
         Save &amp; Test
       </button>
+      {isReadOnly && (
+        <button type="submit" className="btn btn-success" onClick={onTest}>
+          Test
+        </button>
+      )}
       <button type="submit" className="btn btn-danger" disabled={isReadOnly} onClick={onDelete}>
         Delete
       </button>

+ 52 - 43
public/app/features/datasources/settings/DataSourceSettingsPage.tsx

@@ -72,6 +72,12 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
     this.testDataSource();
   };
 
+  onTest = async (evt: React.FormEvent<HTMLFormElement>) => {
+    evt.preventDefault();
+
+    this.testDataSource();
+  };
+
   onDelete = () => {
     appEvents.emit('confirm-modal', {
       title: 'Delete',
@@ -180,52 +186,55 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
     return (
       <Page navModel={navModel}>
         <Page.Contents isLoading={!this.hasDataSource}>
-          {this.hasDataSource && <div className="page-container page-body">
-            <div>
-              <form onSubmit={this.onSubmit}>
-                {this.isReadOnly() && this.renderIsReadOnlyMessage()}
-                {this.shouldRenderInfoBox() && <div className="grafana-info-box">{this.getInfoText()}</div>}
-
-                <BasicSettings
-                  dataSourceName={dataSource.name}
-                  isDefault={dataSource.isDefault}
-                  onDefaultChange={state => setIsDefault(state)}
-                  onNameChange={name => setDataSourceName(name)}
-                />
-
-                {dataSourceMeta.module && (
-                  <PluginSettings
-                    dataSource={dataSource}
-                    dataSourceMeta={dataSourceMeta}
-                    onModelChange={this.onModelChange}
+          {this.hasDataSource && (
+            <div className="page-container page-body">
+              <div>
+                <form onSubmit={this.onSubmit}>
+                  {this.isReadOnly() && this.renderIsReadOnlyMessage()}
+                  {this.shouldRenderInfoBox() && <div className="grafana-info-box">{this.getInfoText()}</div>}
+
+                  <BasicSettings
+                    dataSourceName={dataSource.name}
+                    isDefault={dataSource.isDefault}
+                    onDefaultChange={state => setIsDefault(state)}
+                    onNameChange={name => setDataSourceName(name)}
                   />
-                )}
-
-                <div className="gf-form-group section">
-                  {testingMessage && (
-                    <div className={`alert-${testingStatus} alert`}>
-                      <div className="alert-icon">
-                        {testingStatus === 'error' ? (
-                          <i className="fa fa-exclamation-triangle" />
-                        ) : (
-                          <i className="fa fa-check" />
-                        )}
-                      </div>
-                      <div className="alert-body">
-                        <div className="alert-title">{testingMessage}</div>
-                      </div>
-                    </div>
+
+                  {dataSourceMeta.module && (
+                    <PluginSettings
+                      dataSource={dataSource}
+                      dataSourceMeta={dataSourceMeta}
+                      onModelChange={this.onModelChange}
+                    />
                   )}
-                </div>
-
-                <ButtonRow
-                  onSubmit={event => this.onSubmit(event)}
-                  isReadOnly={this.isReadOnly()}
-                  onDelete={this.onDelete}
-                />
-              </form>
+
+                  <div className="gf-form-group section">
+                    {testingMessage && (
+                      <div className={`alert-${testingStatus} alert`}>
+                        <div className="alert-icon">
+                          {testingStatus === 'error' ? (
+                            <i className="fa fa-exclamation-triangle" />
+                          ) : (
+                            <i className="fa fa-check" />
+                          )}
+                        </div>
+                        <div className="alert-body">
+                          <div className="alert-title">{testingMessage}</div>
+                        </div>
+                      </div>
+                    )}
+                  </div>
+
+                  <ButtonRow
+                    onSubmit={event => this.onSubmit(event)}
+                    isReadOnly={this.isReadOnly()}
+                    onDelete={this.onDelete}
+                    onTest={event => this.onTest(event)}
+                  />
+                </form>
+              </div>
             </div>
-          </div>}
+          )}
         </Page.Contents>
       </Page>
     );

+ 9 - 2
public/app/features/datasources/settings/__snapshots__/ButtonRow.test.tsx.snap

@@ -5,13 +5,20 @@ exports[`Render should render component 1`] = `
   className="gf-form-button-row"
 >
   <button
-    className="btn btn-success"
+    className="btn btn-primary"
     disabled={true}
     onClick={[Function]}
     type="submit"
   >
     Save & Test
   </button>
+  <button
+    className="btn btn-success"
+    onClick={[MockFunction]}
+    type="submit"
+  >
+    Test
+  </button>
   <button
     className="btn btn-danger"
     disabled={true}
@@ -34,7 +41,7 @@ exports[`Render should render with buttons enabled 1`] = `
   className="gf-form-button-row"
 >
   <button
-    className="btn btn-success"
+    className="btn btn-primary"
     disabled={false}
     onClick={[Function]}
     type="submit"

+ 4 - 0
public/app/features/datasources/settings/__snapshots__/DataSourceSettingsPage.test.tsx.snap

@@ -97,6 +97,7 @@ exports[`Render should render alpha info text 1`] = `
             isReadOnly={false}
             onDelete={[Function]}
             onSubmit={[Function]}
+            onTest={[Function]}
           />
         </form>
       </div>
@@ -202,6 +203,7 @@ exports[`Render should render beta info text 1`] = `
             isReadOnly={false}
             onDelete={[Function]}
             onSubmit={[Function]}
+            onTest={[Function]}
           />
         </form>
       </div>
@@ -302,6 +304,7 @@ exports[`Render should render component 1`] = `
             isReadOnly={false}
             onDelete={[Function]}
             onSubmit={[Function]}
+            onTest={[Function]}
           />
         </form>
       </div>
@@ -407,6 +410,7 @@ exports[`Render should render is ready only message 1`] = `
             isReadOnly={true}
             onDelete={[Function]}
             onSubmit={[Function]}
+            onTest={[Function]}
           />
         </form>
       </div>

+ 1 - 1
public/app/features/explore/GraphContainer.tsx

@@ -25,7 +25,7 @@ interface GraphContainerProps {
 
 export class GraphContainer extends PureComponent<GraphContainerProps> {
   onClickGraphButton = () => {
-    this.props.toggleGraph(this.props.exploreId);
+    this.props.toggleGraph(this.props.exploreId, this.props.showingGraph);
   };
 
   onChangeTime = (timeRange: TimeRange) => {

+ 14 - 13
public/app/features/explore/Logs.tsx

@@ -58,14 +58,15 @@ interface Props {
   range?: RawTimeRange;
   scanning?: boolean;
   scanRange?: RawTimeRange;
+  dedupStrategy: LogsDedupStrategy;
   onChangeTime?: (range: RawTimeRange) => void;
   onClickLabel?: (label: string, value: string) => void;
   onStartScanning?: () => void;
   onStopScanning?: () => void;
+  onDedupStrategyChange: (dedupStrategy: LogsDedupStrategy) => void;
 }
 
 interface State {
-  dedup: LogsDedupStrategy;
   deferLogs: boolean;
   hiddenLogLevels: Set<LogLevel>;
   renderAll: boolean;
@@ -79,7 +80,6 @@ export default class Logs extends PureComponent<Props, State> {
   renderAllTimer: NodeJS.Timer;
 
   state = {
-    dedup: LogsDedupStrategy.none,
     deferLogs: true,
     hiddenLogLevels: new Set(),
     renderAll: false,
@@ -112,12 +112,11 @@ export default class Logs extends PureComponent<Props, State> {
   }
 
   onChangeDedup = (dedup: LogsDedupStrategy) => {
-    this.setState(prevState => {
-      if (prevState.dedup === dedup) {
-        return { dedup: LogsDedupStrategy.none };
-      }
-      return { dedup };
-    });
+    const { onDedupStrategyChange } = this.props;
+    if (this.props.dedupStrategy === dedup) {
+      return onDedupStrategyChange(LogsDedupStrategy.none);
+    }
+    return onDedupStrategyChange(dedup);
   };
 
   onChangeLabels = (event: React.SyntheticEvent) => {
@@ -173,17 +172,19 @@ export default class Logs extends PureComponent<Props, State> {
       return null;
     }
 
-    const { dedup, deferLogs, hiddenLogLevels, renderAll, showLocalTime, showUtc } = this.state;
+    const { deferLogs, hiddenLogLevels, renderAll, showLocalTime, showUtc,  } = this.state;
     let { showLabels } = this.state;
+    const { dedupStrategy } = this.props;
     const hasData = data && data.rows && data.rows.length > 0;
-    const showDuplicates = dedup !== LogsDedupStrategy.none;
+    const showDuplicates = dedupStrategy !== LogsDedupStrategy.none;
 
     // Filtering
     const filteredData = filterLogLevels(data, hiddenLogLevels);
-    const dedupedData = dedupLogRows(filteredData, dedup);
+    const dedupedData = dedupLogRows(filteredData, dedupStrategy);
     const dedupCount = dedupedData.rows.reduce((sum, row) => sum + row.duplicates, 0);
     const meta = [...data.meta];
-    if (dedup !== LogsDedupStrategy.none) {
+
+    if (dedupStrategy !== LogsDedupStrategy.none) {
       meta.push({
         label: 'Dedup count',
         value: dedupCount,
@@ -236,7 +237,7 @@ export default class Logs extends PureComponent<Props, State> {
                   key={i}
                   value={dedupType}
                   onChange={this.onChangeDedup}
-                  selected={dedup === dedupType}
+                  selected={dedupStrategy === dedupType}
                   tooltip={LogsDedupDescription[dedupType]}
                 >
                   {dedupType}

+ 26 - 4
public/app/features/explore/LogsContainer.tsx

@@ -4,10 +4,10 @@ import { connect } from 'react-redux';
 import { RawTimeRange, TimeRange } from '@grafana/ui';
 
 import { ExploreId, ExploreItemState } from 'app/types/explore';
-import { LogsModel } from 'app/core/logs_model';
+import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model';
 import { StoreState } from 'app/types';
 
-import { toggleLogs } from './state/actions';
+import { toggleLogs, changeDedupStrategy } from './state/actions';
 import Logs from './Logs';
 import Panel from './Panel';
 
@@ -25,12 +25,18 @@ interface LogsContainerProps {
   scanRange?: RawTimeRange;
   showingLogs: boolean;
   toggleLogs: typeof toggleLogs;
+  changeDedupStrategy: typeof changeDedupStrategy;
+  dedupStrategy: LogsDedupStrategy;
   width: number;
 }
 
 export class LogsContainer extends PureComponent<LogsContainerProps> {
   onClickLogsButton = () => {
-    this.props.toggleLogs(this.props.exploreId);
+    this.props.toggleLogs(this.props.exploreId, this.props.showingLogs);
+  };
+
+  handleDedupStrategyChange = (dedupStrategy: LogsDedupStrategy) => {
+    this.props.changeDedupStrategy(this.props.exploreId, dedupStrategy);
   };
 
   render() {
@@ -53,6 +59,7 @@ export class LogsContainer extends PureComponent<LogsContainerProps> {
     return (
       <Panel label="Logs" loading={loading} isOpen={showingLogs} onToggle={this.onClickLogsButton}>
         <Logs
+          dedupStrategy={this.props.dedupStrategy || LogsDedupStrategy.none}
           data={logsResult}
           exploreId={exploreId}
           key={logsResult && logsResult.id}
@@ -62,6 +69,7 @@ export class LogsContainer extends PureComponent<LogsContainerProps> {
           onClickLabel={onClickLabel}
           onStartScanning={onStartScanning}
           onStopScanning={onStopScanning}
+          onDedupStrategyChange={this.handleDedupStrategyChange}
           range={range}
           scanning={scanning}
           scanRange={scanRange}
@@ -72,11 +80,23 @@ export class LogsContainer extends PureComponent<LogsContainerProps> {
   }
 }
 
+const selectItemUIState = (itemState: ExploreItemState) => {
+  const { showingGraph, showingLogs, showingTable, showingStartPage, dedupStrategy } = itemState;
+  return {
+    showingGraph,
+    showingLogs,
+    showingTable,
+    showingStartPage,
+    dedupStrategy,
+  };
+};
 function mapStateToProps(state: StoreState, { exploreId }) {
   const explore = state.explore;
   const item: ExploreItemState = explore[exploreId];
-  const { logsHighlighterExpressions, logsResult, queryTransactions, scanning, scanRange, showingLogs, range } = item;
+  const { logsHighlighterExpressions, logsResult, queryTransactions, scanning, scanRange, range } = item;
   const loading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done);
+  const {showingLogs, dedupStrategy} = selectItemUIState(item);
+
   return {
     loading,
     logsHighlighterExpressions,
@@ -85,11 +105,13 @@ function mapStateToProps(state: StoreState, { exploreId }) {
     scanRange,
     showingLogs,
     range,
+    dedupStrategy,
   };
 }
 
 const mapDispatchToProps = {
   toggleLogs,
+  changeDedupStrategy,
 };
 
 export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(LogsContainer));

+ 1 - 1
public/app/features/explore/TableContainer.tsx

@@ -21,7 +21,7 @@ interface TableContainerProps {
 
 export class TableContainer extends PureComponent<TableContainerProps> {
   onClickTableButton = () => {
-    this.props.toggleTable(this.props.exploreId);
+    this.props.toggleTable(this.props.exploreId, this.props.showingTable);
   };
 
   render() {

+ 9 - 0
public/app/features/explore/state/actionTypes.ts

@@ -192,6 +192,10 @@ export interface ToggleLogsPayload {
   exploreId: ExploreId;
 }
 
+export interface UpdateUIStatePayload extends Partial<ExploreUIState>{
+  exploreId: ExploreId;
+}
+
 export interface UpdateDatasourceInstancePayload {
   exploreId: ExploreId;
   datasourceInstance: DataSourceApi;
@@ -366,6 +370,11 @@ export const splitCloseAction = noPayloadActionCreatorFactory('explore/SPLIT_CLO
 export const splitOpenAction = actionCreatorFactory<SplitOpenPayload>('explore/SPLIT_OPEN').create();
 export const stateSaveAction = noPayloadActionCreatorFactory('explore/STATE_SAVE').create();
 
+/**
+ * Update state of Explores UI elements (panels visiblity and deduplication  strategy)
+ */
+export const updateUIStateAction = actionCreatorFactory<UpdateUIStatePayload>('explore/UPDATE_UI_STATE').create();
+
 /**
  * Expand/collapse the table result viewer. When collapsed, table queries won't be run.
  */

+ 41 - 12
public/app/features/explore/state/actions.ts

@@ -67,14 +67,26 @@ import {
   ToggleGraphPayload,
   ToggleLogsPayload,
   ToggleTablePayload,
+  updateUIStateAction,
 } from './actionTypes';
 import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory';
+import { LogsDedupStrategy } from 'app/core/logs_model';
 
 type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>;
 
-// /**
-//  * Adds a query row after the row with the given index.
-//  */
+/**
+ * Updates UI state and save it to the URL
+ */
+const updateExploreUIState = (exploreId, uiStateFragment: Partial<ExploreUIState>) => {
+  return dispatch => {
+    dispatch(updateUIStateAction({ exploreId, ...uiStateFragment }));
+    dispatch(stateSave());
+  };
+};
+
+/**
+ * Adds a query row after the row with the given index.
+ */
 export function addQueryRow(exploreId: ExploreId, index: number): ActionOf<AddQueryRowPayload> {
   const query = generateEmptyQuery(index + 1);
   return addQueryRowAction({ exploreId, index, query });
@@ -669,6 +681,7 @@ export function stateSave() {
         showingGraph: left.showingGraph,
         showingLogs: left.showingLogs,
         showingTable: left.showingTable,
+        dedupStrategy: left.dedupStrategy,
       },
     };
     urlStates.left = serializeStateToUrlParam(leftUrlState, true);
@@ -677,7 +690,12 @@ export function stateSave() {
         datasource: right.datasourceInstance.name,
         queries: right.queries.map(clearQueryKeys),
         range: right.range,
-        ui: { showingGraph: right.showingGraph, showingLogs: right.showingLogs, showingTable: right.showingTable },
+        ui: {
+          showingGraph: right.showingGraph,
+          showingLogs: right.showingLogs,
+          showingTable: right.showingTable,
+          dedupStrategy: right.dedupStrategy,
+        },
       };
 
       urlStates.right = serializeStateToUrlParam(rightUrlState, true);
@@ -696,24 +714,26 @@ const togglePanelActionCreator = (
     | ActionCreator<ToggleGraphPayload>
     | ActionCreator<ToggleLogsPayload>
     | ActionCreator<ToggleTablePayload>
-) => (exploreId: ExploreId) => {
-  return (dispatch, getState) => {
-    let shouldRunQueries;
-    dispatch(actionCreator({ exploreId }));
-    dispatch(stateSave());
+) => (exploreId: ExploreId, isPanelVisible: boolean) => {
+  return dispatch => {
+    let uiFragmentStateUpdate: Partial<ExploreUIState>;
+    const shouldRunQueries = !isPanelVisible;
 
     switch (actionCreator.type) {
       case toggleGraphAction.type:
-        shouldRunQueries = getState().explore[exploreId].showingGraph;
+        uiFragmentStateUpdate = { showingGraph: !isPanelVisible };
         break;
       case toggleLogsAction.type:
-        shouldRunQueries = getState().explore[exploreId].showingLogs;
+        uiFragmentStateUpdate = { showingLogs: !isPanelVisible };
         break;
       case toggleTableAction.type:
-        shouldRunQueries = getState().explore[exploreId].showingTable;
+        uiFragmentStateUpdate = { showingTable: !isPanelVisible };
         break;
     }
 
+    dispatch(actionCreator({ exploreId }));
+    dispatch(updateExploreUIState(exploreId, uiFragmentStateUpdate));
+
     if (shouldRunQueries) {
       dispatch(runQueries(exploreId));
     }
@@ -734,3 +754,12 @@ export const toggleLogs = togglePanelActionCreator(toggleLogsAction);
  * Expand/collapse the table result viewer. When collapsed, table queries won't be run.
  */
 export const toggleTable = togglePanelActionCreator(toggleTableAction);
+
+/**
+ * Change logs deduplication strategy and update URL.
+ */
+export const changeDedupStrategy = (exploreId, dedupStrategy: LogsDedupStrategy) => {
+  return dispatch => {
+    dispatch(updateExploreUIState(exploreId, { dedupStrategy }));
+  };
+};

+ 11 - 4
public/app/features/explore/state/reducers.ts

@@ -37,6 +37,7 @@ import {
   toggleLogsAction,
   toggleTableAction,
   queriesImportedAction,
+  updateUIStateAction,
 } from './actionTypes';
 
 export const DEFAULT_RANGE = {
@@ -406,6 +407,12 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
       };
     },
   })
+  .addMapper({
+    filter: updateUIStateAction,
+    mapper: (state, action): ExploreItemState => {
+      return { ...state, ...action.payload };
+    },
+  })
   .addMapper({
     filter: toggleGraphAction,
     mapper: (state): ExploreItemState => {
@@ -415,7 +422,7 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
         // Discard transactions related to Graph query
         nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph');
       }
-      return { ...state, queryTransactions: nextQueryTransactions, showingGraph };
+      return { ...state, queryTransactions: nextQueryTransactions };
     },
   })
   .addMapper({
@@ -427,7 +434,7 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
         // Discard transactions related to Logs query
         nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs');
       }
-      return { ...state, queryTransactions: nextQueryTransactions, showingLogs };
+      return { ...state, queryTransactions: nextQueryTransactions };
     },
   })
   .addMapper({
@@ -435,7 +442,7 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
     mapper: (state): ExploreItemState => {
       const showingTable = !state.showingTable;
       if (showingTable) {
-        return { ...state, showingTable, queryTransactions: state.queryTransactions };
+        return { ...state, queryTransactions: state.queryTransactions };
       }
 
       // Toggle off needs discarding of table queries and results
@@ -446,7 +453,7 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
         state.queryIntervals.intervalMs
       );
 
-      return { ...state, ...results, queryTransactions: nextQueryTransactions, showingTable };
+      return { ...state, ...results, queryTransactions: nextQueryTransactions };
     },
   })
   .addMapper({

+ 9 - 3
public/app/features/folders/FolderPermissions.tsx

@@ -73,7 +73,13 @@ export class FolderPermissions extends PureComponent<Props, State> {
     const { isAdding } = this.state;
 
     if (folder.id === 0) {
-      return <Page navModel={navModel}><Page.Contents isLoading={true}><span></span></Page.Contents></Page>;
+      return (
+        <Page navModel={navModel}>
+          <Page.Contents isLoading={true}>
+            <span />
+          </Page.Contents>
+        </Page>
+      );
     }
 
     const folderInfo = { title: folder.title, url: folder.url, id: folder.id };
@@ -89,8 +95,8 @@ export class FolderPermissions extends PureComponent<Props, State> {
               </div>
             </Tooltip>
             <div className="page-action-bar__spacer" />
-            <button className="btn btn-success pull-right" onClick={this.onOpenAddPermissions} disabled={isAdding}>
-              <i className="fa fa-plus" /> Add Permission
+            <button className="btn btn-primary pull-right" onClick={this.onOpenAddPermissions} disabled={isAdding}>
+              Add Permission
             </button>
           </div>
           <SlideDown in={isAdding}>

+ 1 - 1
public/app/features/folders/FolderSettingsPage.tsx

@@ -82,7 +82,7 @@ export class FolderSettingsPage extends PureComponent<Props, State> {
                   />
                 </div>
                 <div className="gf-form-button-row">
-                  <button type="submit" className="btn btn-success" disabled={!folder.canSave || !folder.hasChanged}>
+                  <button type="submit" className="btn btn-primary" disabled={!folder.canSave || !folder.hasChanged}>
                     <i className="fa fa-save" /> Save
                   </button>
                   <button className="btn btn-danger" onClick={this.onDelete} disabled={!folder.canSave}>

+ 2 - 2
public/app/features/folders/__snapshots__/FolderSettingsPage.test.tsx.snap

@@ -41,7 +41,7 @@ exports[`Render should enable save button 1`] = `
             className="gf-form-button-row"
           >
             <button
-              className="btn btn-success"
+              className="btn btn-primary"
               disabled={false}
               type="submit"
             >
@@ -109,7 +109,7 @@ exports[`Render should render component 1`] = `
             className="gf-form-button-row"
           >
             <button
-              className="btn btn-success"
+              className="btn btn-primary"
               disabled={true}
               type="submit"
             >

+ 1 - 1
public/app/features/folders/partials/create_folder.html

@@ -25,7 +25,7 @@
 		</div>
 
 		<div class="gf-form-button-row">
-			<button type="submit" class="btn btn-success width-12" ng-disabled="!ctrl.titleTouched || ctrl.hasValidationError">
+			<button type="submit" class="btn btn-primary width-12" ng-disabled="!ctrl.titleTouched || ctrl.hasValidationError">
 				<i class="fa fa-save"></i> Create
 			</button>
 		</div>

+ 1 - 1
public/app/features/manage-dashboards/components/MoveToFolderModal/template.html

@@ -26,7 +26,7 @@
       </div>
     </div>
     <div class="gf-form-button-row text-center">
-      <button type="submit" class="btn btn-success" ng-disabled="ctrl.saveForm.$invalid || !ctrl.isValidFolderSelection">Move</button>
+      <button type="submit" class="btn btn-primary" ng-disabled="ctrl.saveForm.$invalid || !ctrl.isValidFolderSelection">Move</button>
       <a class="btn-text" ng-click="ctrl.dismiss();">Cancel</a>
     </div>
   </form>

+ 1 - 1
public/app/features/manage-dashboards/components/UploadDashboard/uploadDashboardDirective.ts

@@ -4,7 +4,7 @@ import angular from 'angular';
 
 const template = `
 <input type="file" id="dashupload" name="dashupload" class="hide" onchange="angular.element(this).scope().file_selected"/>
-<label class="btn btn-success" for="dashupload">
+<label class="btn btn-primary" for="dashupload">
   <i class="fa fa-upload"></i>
   {{btnText}}
 </label>

+ 1 - 1
public/app/features/manage-dashboards/partials/dashboard_import.html

@@ -146,7 +146,7 @@
     </div>
 
     <div class="gf-form-button-row">
-      <button type="button" class="btn btn-success width-12" ng-click="ctrl.saveDashboard()" ng-hide="ctrl.nameExists || ctrl.uidExists" ng-disabled="!ctrl.isValid()">
+      <button type="button" class="btn btn-primary width-12" ng-click="ctrl.saveDashboard()" ng-hide="ctrl.nameExists || ctrl.uidExists" ng-disabled="!ctrl.isValid()">
         <i class="fa fa-save"></i> Import
       </button>
       <button type="button" class="btn btn-danger width-12" ng-click="ctrl.saveDashboard()" ng-show="ctrl.nameExists || ctrl.uidExists" ng-disabled="!ctrl.isValid()">

+ 1 - 1
public/app/features/org/OrgProfile.tsx

@@ -32,7 +32,7 @@ const OrgProfile: FC<Props> = ({ onSubmit, onOrgNameChange, orgName }) => {
           </div>
         </div>
         <div className="gf-form-button-row">
-          <button type="submit" className="btn btn-success">
+          <button type="submit" className="btn btn-primary">
             Save
           </button>
         </div>

+ 1 - 1
public/app/features/org/__snapshots__/OrgProfile.test.tsx.snap

@@ -35,7 +35,7 @@ exports[`Render should render component 1`] = `
       className="gf-form-button-row"
     >
       <button
-        className="btn btn-success"
+        className="btn btn-primary"
         type="submit"
       >
         Save

+ 1 - 1
public/app/features/org/partials/invite.html

@@ -28,7 +28,7 @@
 			<gf-form-switch class="gf-form" label="Send invite email" checked="ctrl.invite.sendEmail" label-class="width-10"></gf-form-switch>
 
 			<div class="gf-form-button-row">
-				<button type="submit" class="btn btn-success" ng-click="ctrl.sendInvite();">Invite</button>
+				<button type="submit" class="btn btn-primary" ng-click="ctrl.sendInvite();">Invite</button>
 				<a class="btn btn-inverse" href="org/users">Back</a>
 			</div>
 	</form>

+ 1 - 1
public/app/features/org/partials/newOrg.html

@@ -15,7 +15,7 @@
 			</div>
 			<br>
 			<div class="gf-form-buttons-row">
-				<button type="submit" class="btn btn-success" ng-click="createOrg()">Create</button>
+				<button type="submit" class="btn btn-primary" ng-click="createOrg()">Create</button>
 			</div>
 		</div>
 	</form>

+ 1 - 1
public/app/features/org/partials/select_org.html

@@ -14,7 +14,7 @@
 
 			<div style="display: inline-block; width: 400px; margin: 30px 0">
 				<div ng-repeat="org in orgs">
-					<a ng-click="setUsingOrg(org)" class="btn btn-success">
+					<a ng-click="setUsingOrg(org)" class="btn btn-primary">
 						{{org.name}} ({{org.role}})
 					</a>
 				</div>

+ 3 - 0
public/app/features/panel/specs/metrics_panel_ctrl.test.ts

@@ -1,6 +1,9 @@
 jest.mock('app/core/core', () => ({}));
 jest.mock('app/core/config', () => {
   return {
+    bootData: {
+      user: {},
+    },
     panels: {
       test: {
         id: 'test',

+ 2 - 2
public/app/features/playlist/partials/playlist.html

@@ -95,8 +95,8 @@
 	<div class="clearfix"></div>
 
 	<div class="gf-form-button-row">
-		<a class="btn btn-success" ng-show="ctrl.isNew" ng-disabled="ctrl.playlistEditForm.$invalid || ctrl.isPlaylistEmpty()" ng-click="ctrl.savePlaylist(ctrl.playlist, ctrl.playlistItems)">Create</a>
-		<a class="btn btn-success" ng-show="!ctrl.isNew" ng-disabled="ctrl.playlistEditForm.$invalid || ctrl.isPlaylistEmpty()" ng-click="ctrl.savePlaylist(ctrl.playlist, ctrl.playlistItems)">Save</a>
+		<a class="btn btn-primary" ng-show="ctrl.isNew" ng-disabled="ctrl.playlistEditForm.$invalid || ctrl.isPlaylistEmpty()" ng-click="ctrl.savePlaylist(ctrl.playlist, ctrl.playlistItems)">Create</a>
+		<a class="btn btn-primary" ng-show="!ctrl.isNew" ng-disabled="ctrl.playlistEditForm.$invalid || ctrl.isPlaylistEmpty()" ng-click="ctrl.savePlaylist(ctrl.playlist, ctrl.playlistItems)">Save</a>
 		<a class="btn-text" ng-click="ctrl.backToList()">Cancel</a>
 	</div>
 </div>

+ 1 - 2
public/app/features/playlist/partials/playlists.html

@@ -4,8 +4,7 @@
   <div ng-if="ctrl.playlists.length > 0">
     <div class="page-action-bar">
       <div class="page-action-bar__spacer"></div>
-      <a class="btn btn-success pull-right" href="playlists/create">
-        <i class="fa fa-plus"></i>
+      <a class="btn btn-primary pull-right" href="playlists/create">
         New Playlist
       </a>
     </div>

+ 2 - 2
public/app/features/plugins/partials/plugin_edit.html

@@ -13,8 +13,8 @@
           <plugin-component type="app-config-ctrl"></plugin-component>
 
           <div class="gf-form-button-row">
-            <button type="submit" class="btn btn-success" ng-click="ctrl.enable()" ng-show="!ctrl.model.enabled">Enable</button>
-            <button type="submit" class="btn btn-success" ng-click="ctrl.update()" ng-show="ctrl.model.enabled">Update</button>
+            <button type="submit" class="btn btn-primary" ng-click="ctrl.enable()" ng-show="!ctrl.model.enabled">Enable</button>
+            <button type="submit" class="btn btn-primary" ng-click="ctrl.update()" ng-show="ctrl.model.enabled">Update</button>
             <button type="submit" class="btn btn-danger" ng-click="ctrl.disable()" ng-show="ctrl.model.enabled">Disable</button>
           </div>
         </div>

+ 1 - 1
public/app/features/profile/partials/change_password.html

@@ -26,7 +26,7 @@
 		</div>
 
 		<div class="gf-form-button-row">
-			<button type="submit" class="btn btn-success" ng-click="changePassword()">Change Password</button>
+			<button type="submit" class="btn btn-primary" ng-click="changePassword()">Change Password</button>
 			<a class="btn-text" href="profile">Cancel</a>
 		</div>
 	</form>

+ 1 - 1
public/app/features/profile/partials/profile.html

@@ -20,7 +20,7 @@
       <i ng-if="ctrl.readonlyLoginFields" class="fa fa-lock gf-form-icon--right-absolute" bs-tooltip="'Login Details Locked - managed in another system.'"></i>
     </div>
     <div class="gf-form-button-row">
-      <button type="submit" class="btn btn-success" ng-click="ctrl.update()">Save</button>
+      <button type="submit" class="btn btn-primary" ng-click="ctrl.update()">Save</button>
     </div>
   </form>
 

+ 3 - 3
public/app/features/teams/TeamGroupSync.tsx

@@ -84,7 +84,7 @@ export class TeamGroupSync extends PureComponent<Props, State> {
           </Tooltip>
           <div className="page-action-bar__spacer" />
           {groups.length > 0 && (
-            <button className="btn btn-success pull-right" onClick={this.onToggleAdding}>
+            <button className="btn btn-primary pull-right" onClick={this.onToggleAdding}>
               <i className="fa fa-plus" /> Add group
             </button>
           )}
@@ -108,7 +108,7 @@ export class TeamGroupSync extends PureComponent<Props, State> {
               </div>
 
               <div className="gf-form">
-                <button className="btn btn-success gf-form-btn" type="submit" disabled={!this.isNewGroupValid()}>
+                <button className="btn btn-primary gf-form-btn" type="submit" disabled={!this.isNewGroupValid()}>
                   Add group
                 </button>
               </div>
@@ -120,7 +120,7 @@ export class TeamGroupSync extends PureComponent<Props, State> {
           !isAdding && (
             <div className="empty-list-cta">
               <div className="empty-list-cta__title">There are no external groups to sync with</div>
-              <button onClick={this.onToggleAdding} className="empty-list-cta__button btn btn-xlarge btn-success">
+              <button onClick={this.onToggleAdding} className="empty-list-cta__button btn btn-xlarge btn-primary">
                 <i className="gicon gicon-add-team" />
                 Add Group
               </button>

+ 3 - 3
public/app/features/teams/TeamList.tsx

@@ -86,7 +86,7 @@ export class TeamList extends PureComponent<Props, any> {
     const { teams, searchQuery } = this.props;
 
     return (
-      <div className="page-container page-body">
+      <>
         <div className="page-action-bar">
           <div className="gf-form gf-form--grow">
             <label className="gf-form--has-input-icon gf-form--grow">
@@ -103,7 +103,7 @@ export class TeamList extends PureComponent<Props, any> {
 
           <div className="page-action-bar__spacer" />
 
-          <a className="btn btn-success" href="org/teams/new">
+          <a className="btn btn-primary" href="org/teams/new">
             New team
           </a>
         </div>
@@ -122,7 +122,7 @@ export class TeamList extends PureComponent<Props, any> {
             <tbody>{teams.map(team => this.renderTeam(team))}</tbody>
           </table>
         </div>
-      </div>
+      </>
     );
   }
 

+ 2 - 2
public/app/features/teams/TeamMembers.tsx

@@ -103,7 +103,7 @@ export class TeamMembers extends PureComponent<Props, State> {
 
           <div className="page-action-bar__spacer" />
 
-          <button className="btn btn-success pull-right" onClick={this.onToggleAdding} disabled={isAdding}>
+          <button className="btn btn-primary pull-right" onClick={this.onToggleAdding} disabled={isAdding}>
             <i className="fa fa-plus" /> Add a member
           </button>
         </div>
@@ -117,7 +117,7 @@ export class TeamMembers extends PureComponent<Props, State> {
             <div className="gf-form-inline">
               <UserPicker onSelected={this.onUserSelected} className="min-width-30" />
               {this.state.newTeamMember && (
-                <button className="btn btn-success gf-form-btn" type="submit" onClick={this.onAddUserToTeam}>
+                <button className="btn btn-primary gf-form-btn" type="submit" onClick={this.onAddUserToTeam}>
                   Add to team
                 </button>
               )}

+ 1 - 1
public/app/features/teams/TeamPages.tsx

@@ -84,7 +84,7 @@ export class TeamPages extends PureComponent<Props, State> {
     return (
       <Page navModel={navModel}>
         <Page.Contents isLoading={this.state.isLoading}>
-          {team && Object.keys(team).length !== 0 && <div className="page-container page-body">{this.renderPage()}</div>}
+          {team && Object.keys(team).length !== 0 && this.renderPage()}
         </Page.Contents>
       </Page>
     );

+ 1 - 1
public/app/features/teams/TeamSettings.tsx

@@ -75,7 +75,7 @@ export class TeamSettings extends React.Component<Props, State> {
           </div>
 
           <div className="gf-form-button-row">
-            <button type="submit" className="btn btn-success">
+            <button type="submit" className="btn btn-primary">
               Update
             </button>
           </div>

+ 4 - 4
public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap

@@ -62,7 +62,7 @@ exports[`Render should render component 1`] = `
           className="gf-form"
         >
           <button
-            className="btn btn-success gf-form-btn"
+            className="btn btn-primary gf-form-btn"
             disabled={true}
             type="submit"
           >
@@ -81,7 +81,7 @@ exports[`Render should render component 1`] = `
       There are no external groups to sync with
     </div>
     <button
-      className="empty-list-cta__button btn btn-xlarge btn-success"
+      className="empty-list-cta__button btn btn-xlarge btn-primary"
       onClick={[Function]}
     >
       <i
@@ -135,7 +135,7 @@ exports[`Render should render groups table 1`] = `
       className="page-action-bar__spacer"
     />
     <button
-      className="btn btn-success pull-right"
+      className="btn btn-primary pull-right"
       onClick={[Function]}
     >
       <i
@@ -180,7 +180,7 @@ exports[`Render should render groups table 1`] = `
           className="gf-form"
         >
           <button
-            className="btn btn-success gf-form-btn"
+            className="btn btn-primary gf-form-btn"
             disabled={true}
             type="submit"
           >

+ 273 - 277
public/app/features/teams/__snapshots__/TeamList.test.tsx.snap

@@ -36,320 +36,316 @@ exports[`Render should render teams table 1`] = `
     isLoading={false}
   >
     <div
-      className="page-container page-body"
+      className="page-action-bar"
     >
       <div
-        className="page-action-bar"
+        className="gf-form gf-form--grow"
       >
-        <div
-          className="gf-form gf-form--grow"
+        <label
+          className="gf-form--has-input-icon gf-form--grow"
         >
-          <label
-            className="gf-form--has-input-icon gf-form--grow"
-          >
-            <input
-              className="gf-form-input"
-              onChange={[Function]}
-              placeholder="Search teams"
-              type="text"
-              value=""
-            />
-            <i
-              className="gf-form-input-icon fa fa-search"
-            />
-          </label>
-        </div>
-        <div
-          className="page-action-bar__spacer"
-        />
-        <a
-          className="btn btn-success"
-          href="org/teams/new"
-        >
-          New team
-        </a>
+          <input
+            className="gf-form-input"
+            onChange={[Function]}
+            placeholder="Search teams"
+            type="text"
+            value=""
+          />
+          <i
+            className="gf-form-input-icon fa fa-search"
+          />
+        </label>
       </div>
       <div
-        className="admin-list-table"
+        className="page-action-bar__spacer"
+      />
+      <a
+        className="btn btn-primary"
+        href="org/teams/new"
       >
-        <table
-          className="filter-table filter-table--hover form-inline"
-        >
-          <thead>
-            <tr>
-              <th />
-              <th>
-                Name
-              </th>
-              <th>
-                Email
-              </th>
-              <th>
-                Members
-              </th>
-              <th
-                style={
-                  Object {
-                    "width": "1%",
-                  }
+        New team
+      </a>
+    </div>
+    <div
+      className="admin-list-table"
+    >
+      <table
+        className="filter-table filter-table--hover form-inline"
+      >
+        <thead>
+          <tr>
+            <th />
+            <th>
+              Name
+            </th>
+            <th>
+              Email
+            </th>
+            <th>
+              Members
+            </th>
+            <th
+              style={
+                Object {
+                  "width": "1%",
                 }
-              />
-            </tr>
-          </thead>
-          <tbody>
-            <tr
-              key="1"
+              }
+            />
+          </tr>
+        </thead>
+        <tbody>
+          <tr
+            key="1"
+          >
+            <td
+              className="width-4 text-center link-td"
             >
-              <td
-                className="width-4 text-center link-td"
+              <a
+                href="org/teams/edit/1"
               >
-                <a
-                  href="org/teams/edit/1"
-                >
-                  <img
-                    className="filter-table__avatar"
-                    src="some/url/"
-                  />
-                </a>
-              </td>
-              <td
-                className="link-td"
+                <img
+                  className="filter-table__avatar"
+                  src="some/url/"
+                />
+              </a>
+            </td>
+            <td
+              className="link-td"
+            >
+              <a
+                href="org/teams/edit/1"
               >
-                <a
-                  href="org/teams/edit/1"
-                >
-                  test-1
-                </a>
-              </td>
-              <td
-                className="link-td"
+                test-1
+              </a>
+            </td>
+            <td
+              className="link-td"
+            >
+              <a
+                href="org/teams/edit/1"
               >
-                <a
-                  href="org/teams/edit/1"
-                >
-                  test-1@test.com
-                </a>
-              </td>
-              <td
-                className="link-td"
+                test-1@test.com
+              </a>
+            </td>
+            <td
+              className="link-td"
+            >
+              <a
+                href="org/teams/edit/1"
               >
-                <a
-                  href="org/teams/edit/1"
-                >
-                  1
-                </a>
-              </td>
-              <td
-                className="text-right"
+                1
+              </a>
+            </td>
+            <td
+              className="text-right"
+            >
+              <DeleteButton
+                onConfirm={[Function]}
+              />
+            </td>
+          </tr>
+          <tr
+            key="2"
+          >
+            <td
+              className="width-4 text-center link-td"
+            >
+              <a
+                href="org/teams/edit/2"
               >
-                <DeleteButton
-                  onConfirm={[Function]}
+                <img
+                  className="filter-table__avatar"
+                  src="some/url/"
                 />
-              </td>
-            </tr>
-            <tr
-              key="2"
+              </a>
+            </td>
+            <td
+              className="link-td"
             >
-              <td
-                className="width-4 text-center link-td"
-              >
-                <a
-                  href="org/teams/edit/2"
-                >
-                  <img
-                    className="filter-table__avatar"
-                    src="some/url/"
-                  />
-                </a>
-              </td>
-              <td
-                className="link-td"
+              <a
+                href="org/teams/edit/2"
               >
-                <a
-                  href="org/teams/edit/2"
-                >
-                  test-2
-                </a>
-              </td>
-              <td
-                className="link-td"
+                test-2
+              </a>
+            </td>
+            <td
+              className="link-td"
+            >
+              <a
+                href="org/teams/edit/2"
               >
-                <a
-                  href="org/teams/edit/2"
-                >
-                  test-2@test.com
-                </a>
-              </td>
-              <td
-                className="link-td"
+                test-2@test.com
+              </a>
+            </td>
+            <td
+              className="link-td"
+            >
+              <a
+                href="org/teams/edit/2"
               >
-                <a
-                  href="org/teams/edit/2"
-                >
-                  2
-                </a>
-              </td>
-              <td
-                className="text-right"
+                2
+              </a>
+            </td>
+            <td
+              className="text-right"
+            >
+              <DeleteButton
+                onConfirm={[Function]}
+              />
+            </td>
+          </tr>
+          <tr
+            key="3"
+          >
+            <td
+              className="width-4 text-center link-td"
+            >
+              <a
+                href="org/teams/edit/3"
               >
-                <DeleteButton
-                  onConfirm={[Function]}
+                <img
+                  className="filter-table__avatar"
+                  src="some/url/"
                 />
-              </td>
-            </tr>
-            <tr
-              key="3"
+              </a>
+            </td>
+            <td
+              className="link-td"
             >
-              <td
-                className="width-4 text-center link-td"
-              >
-                <a
-                  href="org/teams/edit/3"
-                >
-                  <img
-                    className="filter-table__avatar"
-                    src="some/url/"
-                  />
-                </a>
-              </td>
-              <td
-                className="link-td"
+              <a
+                href="org/teams/edit/3"
               >
-                <a
-                  href="org/teams/edit/3"
-                >
-                  test-3
-                </a>
-              </td>
-              <td
-                className="link-td"
+                test-3
+              </a>
+            </td>
+            <td
+              className="link-td"
+            >
+              <a
+                href="org/teams/edit/3"
               >
-                <a
-                  href="org/teams/edit/3"
-                >
-                  test-3@test.com
-                </a>
-              </td>
-              <td
-                className="link-td"
+                test-3@test.com
+              </a>
+            </td>
+            <td
+              className="link-td"
+            >
+              <a
+                href="org/teams/edit/3"
               >
-                <a
-                  href="org/teams/edit/3"
-                >
-                  3
-                </a>
-              </td>
-              <td
-                className="text-right"
+                3
+              </a>
+            </td>
+            <td
+              className="text-right"
+            >
+              <DeleteButton
+                onConfirm={[Function]}
+              />
+            </td>
+          </tr>
+          <tr
+            key="4"
+          >
+            <td
+              className="width-4 text-center link-td"
+            >
+              <a
+                href="org/teams/edit/4"
               >
-                <DeleteButton
-                  onConfirm={[Function]}
+                <img
+                  className="filter-table__avatar"
+                  src="some/url/"
                 />
-              </td>
-            </tr>
-            <tr
-              key="4"
+              </a>
+            </td>
+            <td
+              className="link-td"
             >
-              <td
-                className="width-4 text-center link-td"
+              <a
+                href="org/teams/edit/4"
               >
-                <a
-                  href="org/teams/edit/4"
-                >
-                  <img
-                    className="filter-table__avatar"
-                    src="some/url/"
-                  />
-                </a>
-              </td>
-              <td
-                className="link-td"
-              >
-                <a
-                  href="org/teams/edit/4"
-                >
-                  test-4
-                </a>
-              </td>
-              <td
-                className="link-td"
+                test-4
+              </a>
+            </td>
+            <td
+              className="link-td"
+            >
+              <a
+                href="org/teams/edit/4"
               >
-                <a
-                  href="org/teams/edit/4"
-                >
-                  test-4@test.com
-                </a>
-              </td>
-              <td
-                className="link-td"
+                test-4@test.com
+              </a>
+            </td>
+            <td
+              className="link-td"
+            >
+              <a
+                href="org/teams/edit/4"
               >
-                <a
-                  href="org/teams/edit/4"
-                >
-                  4
-                </a>
-              </td>
-              <td
-                className="text-right"
+                4
+              </a>
+            </td>
+            <td
+              className="text-right"
+            >
+              <DeleteButton
+                onConfirm={[Function]}
+              />
+            </td>
+          </tr>
+          <tr
+            key="5"
+          >
+            <td
+              className="width-4 text-center link-td"
+            >
+              <a
+                href="org/teams/edit/5"
               >
-                <DeleteButton
-                  onConfirm={[Function]}
+                <img
+                  className="filter-table__avatar"
+                  src="some/url/"
                 />
-              </td>
-            </tr>
-            <tr
-              key="5"
+              </a>
+            </td>
+            <td
+              className="link-td"
             >
-              <td
-                className="width-4 text-center link-td"
-              >
-                <a
-                  href="org/teams/edit/5"
-                >
-                  <img
-                    className="filter-table__avatar"
-                    src="some/url/"
-                  />
-                </a>
-              </td>
-              <td
-                className="link-td"
-              >
-                <a
-                  href="org/teams/edit/5"
-                >
-                  test-5
-                </a>
-              </td>
-              <td
-                className="link-td"
+              <a
+                href="org/teams/edit/5"
               >
-                <a
-                  href="org/teams/edit/5"
-                >
-                  test-5@test.com
-                </a>
-              </td>
-              <td
-                className="link-td"
+                test-5
+              </a>
+            </td>
+            <td
+              className="link-td"
+            >
+              <a
+                href="org/teams/edit/5"
               >
-                <a
-                  href="org/teams/edit/5"
-                >
-                  5
-                </a>
-              </td>
-              <td
-                className="text-right"
+                test-5@test.com
+              </a>
+            </td>
+            <td
+              className="link-td"
+            >
+              <a
+                href="org/teams/edit/5"
               >
-                <DeleteButton
-                  onConfirm={[Function]}
-                />
-              </td>
-            </tr>
-          </tbody>
-        </table>
-      </div>
+                5
+              </a>
+            </td>
+            <td
+              className="text-right"
+            >
+              <DeleteButton
+                onConfirm={[Function]}
+              />
+            </td>
+          </tr>
+        </tbody>
+      </table>
     </div>
   </PageContents>
 </Page>

+ 3 - 3
public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap

@@ -27,7 +27,7 @@ exports[`Render should render component 1`] = `
       className="page-action-bar__spacer"
     />
     <button
-      className="btn btn-success pull-right"
+      className="btn btn-primary pull-right"
       disabled={false}
       onClick={[Function]}
     >
@@ -121,7 +121,7 @@ exports[`Render should render team members 1`] = `
       className="page-action-bar__spacer"
     />
     <button
-      className="btn btn-success pull-right"
+      className="btn btn-primary pull-right"
       disabled={false}
       onClick={[Function]}
     >
@@ -341,7 +341,7 @@ exports[`Render should render team members when sync enabled 1`] = `
       className="page-action-bar__spacer"
     />
     <button
-      className="btn btn-success pull-right"
+      className="btn btn-primary pull-right"
       disabled={false}
       onClick={[Function]}
     >

+ 5 - 17
public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap

@@ -17,11 +17,7 @@ exports[`Render should render group sync page 1`] = `
   <PageContents
     isLoading={true}
   >
-    <div
-      className="page-container page-body"
-    >
-      <Connect(TeamGroupSync) />
-    </div>
+    <Connect(TeamGroupSync) />
   </PageContents>
 </Page>
 `;
@@ -33,13 +29,9 @@ exports[`Render should render member page if team not empty 1`] = `
   <PageContents
     isLoading={true}
   >
-    <div
-      className="page-container page-body"
-    >
-      <Connect(TeamMembers)
-        syncEnabled={true}
-      />
-    </div>
+    <Connect(TeamMembers)
+      syncEnabled={true}
+    />
   </PageContents>
 </Page>
 `;
@@ -51,11 +43,7 @@ exports[`Render should render settings and preferences page 1`] = `
   <PageContents
     isLoading={true}
   >
-    <div
-      className="page-container page-body"
-    >
-      <Connect(TeamSettings) />
-    </div>
+    <Connect(TeamSettings) />
   </PageContents>
 </Page>
 `;

+ 1 - 1
public/app/features/teams/__snapshots__/TeamSettings.test.tsx.snap

@@ -46,7 +46,7 @@ exports[`Render should render component 1`] = `
       className="gf-form-button-row"
     >
       <button
-        className="btn btn-success"
+        className="btn btn-primary"
         type="submit"
       >
         Update

+ 1 - 1
public/app/features/teams/partials/create_team.html

@@ -18,7 +18,7 @@
 			<input class="gf-form-input max-width-22" type="email" ng-model="ctrl.email" placeholder="email@test.com">
 		</div>
 		<div class="gf-form-button-row">
-			<button type="submit" class="btn btn-success width-12">
+			<button type="submit" class="btn btn-primary width-12">
 				<i class="fa fa-save"></i> Create
 			</button>
 		</div>

+ 4 - 4
public/app/features/templating/partials/editor.html

@@ -11,7 +11,7 @@
 		<div ng-if="variables.length === 0">
 			<div class="empty-list-cta">
 				<div class="empty-list-cta__title">There are no variables added yet</div>
-				<a ng-click="setMode('new')" class="empty-list-cta__button btn btn-xlarge btn-success">
+				<a ng-click="setMode('new')" class="empty-list-cta__button btn btn-xlarge btn-primary">
 					<i class="gicon gicon-add-variable"></i>
 					Add variable
 				</a>
@@ -34,7 +34,7 @@
 		<div ng-if="variables.length">
 			<div class="page-action-bar">
 				<div class="page-action-bar__spacer"></div>
-				<a type="button" class="btn btn-success" ng-click="setMode('new');"><i class="fa fa-plus"></i> New</a>
+				<a type="button" class="btn btn-primary" ng-click="setMode('new');"><i class="fa fa-plus"></i> New</a>
 			</div>
 
 			<table class="filter-table filter-table--hover">
@@ -317,8 +317,8 @@
 		</div>
 
 		<div class="gf-form-button-row p-y-0">
-			<button type="submit" class="btn btn-success" ng-show="mode === 'edit'" ng-click="update();">Update</button>
-			<button type="submit" class="btn btn-success" ng-show="mode === 'new'" ng-click="add();">Add</button>
+			<button type="submit" class="btn btn-primary" ng-show="mode === 'edit'" ng-click="update();">Update</button>
+			<button type="submit" class="btn btn-primary" ng-show="mode === 'new'" ng-click="add();">Add</button>
 		</div>
 
 	</form>

+ 2 - 2
public/app/features/users/UsersActionBar.tsx

@@ -65,12 +65,12 @@ export class UsersActionBar extends PureComponent<Props> {
           )}
           <div className="page-action-bar__spacer" />
           {canInvite && (
-            <a className="btn btn-success" href="org/users/invite">
+            <a className="btn btn-primary" href="org/users/invite">
               <span>Invite</span>
             </a>
           )}
           {externalUserMngLinkUrl && (
-            <a className="btn btn-success" href={externalUserMngLinkUrl} target="_blank">
+            <a className="btn btn-primary" href={externalUserMngLinkUrl} target="_blank">
               <i className="fa fa-external-link-square" /> {externalUserMngLinkName}
             </a>
           )}

+ 2 - 2
public/app/features/users/__snapshots__/UsersActionBar.test.tsx.snap

@@ -105,7 +105,7 @@ exports[`Render should show external user management button 1`] = `
       className="page-action-bar__spacer"
     />
     <a
-      className="btn btn-success"
+      className="btn btn-primary"
       href="some/url"
       target="_blank"
     >
@@ -143,7 +143,7 @@ exports[`Render should show invite button 1`] = `
       className="page-action-bar__spacer"
     />
     <a
-      className="btn btn-success"
+      className="btn btn-primary"
       href="org/users/invite"
     >
       <span>

+ 1 - 1
public/app/partials/confirm_modal.html

@@ -26,7 +26,7 @@
 		</div>
 
 		<div class="confirm-modal-buttons">
-			<button ng-show="onAltAction" type="button" class="btn btn-success" ng-click="dismiss();onAltAction();">{{altActionText}}</button>
+			<button ng-show="onAltAction" type="button" class="btn btn-primary" ng-click="dismiss();onAltAction();">{{altActionText}}</button>
 			<button type="button" class="btn btn-danger" ng-click="onConfirm();dismiss();" ng-disabled="!confirmTextValid" give-focus="true">{{yesText}}</button>
 			<button type="button" class="btn btn-inverse" ng-click="dismiss()">{{noText}}</button>
 		</div>

+ 1 - 1
public/app/partials/edit_json.html

@@ -15,7 +15,7 @@
 		</div>
 
 		<div class="gf-form-button-row">
-			<button type="button" class="btn btn-success" ng-show="canUpdate" ng-click="update(); dismiss();">Update</button>
+			<button type="button" class="btn btn-primary" ng-show="canUpdate" ng-click="update(); dismiss();">Update</button>
 			<button class="btn btn-secondary" ng-if="canCopy" clipboard-button="getContentForClipboard()">
 				<i class="fa fa-clipboard"></i>&nbsp;Copy to Clipboard
 			</button>

+ 1 - 1
public/app/partials/login.html

@@ -99,7 +99,7 @@
                 If you skip you will be prompted to change password next time you login.
               </info-popover>
             </a>
-            <button type="submit" class="btn btn-large p-x-2" ng-click="changePassword();" ng-class="{'btn-inverse': !loginForm.$valid, 'btn-success': loginForm.$valid}">
+            <button type="submit" class="btn btn-large p-x-2" ng-click="changePassword();" ng-class="{'btn-inverse': !loginForm.$valid, 'btn-primary': loginForm.$valid}">
               Save
             </button>
           </div>

+ 3 - 3
public/app/partials/reset_password.html

@@ -16,7 +16,7 @@
 					<input type="text" name="username" class="gf-form-input max-width-14" required ng-model='formModel.userOrEmail' placeholder="email or username">
 			</div>
 			<div class="gf-form-button-row">
-				<button type="submit" class="btn btn-success" ng-click="sendResetEmail();" ng-disabled="!sendResetForm.$valid">
+				<button type="submit" class="btn btn-primary" ng-click="sendResetEmail();" ng-disabled="!sendResetForm.$valid">
 					Reset Password
 				</button>
 				<a href="login" class="btn btn-inverse">
@@ -29,7 +29,7 @@
 			An email with a reset link has been sent to the email address. <br>
 			You should receive it shortly.
 			<div class="p-t-1">
-				<a href="login" class="btn btn-success p-t-1">
+				<a href="login" class="btn btn-primary p-t-1">
 					Login
 				</a>
 			</div>
@@ -47,7 +47,7 @@
 				<password-strength password="formModel.newPassword"></password-strength>
 			</div>
 			<div class="gf-form-button-row">
-				<button type="submit" class="btn btn-success" ng-click="submitReset();" ng-disabled="!resetForm.$valid">
+				<button type="submit" class="btn btn-primary" ng-click="submitReset();" ng-disabled="!resetForm.$valid">
 					Reset Password
 				</button>
 			</div>

+ 1 - 1
public/app/partials/signup_invited.html

@@ -30,7 +30,7 @@
 		</div>
 
 		<div class="gf-form-button-row">
-			<button type="submit" class="btn btn-success" ng-click="submit();" ng-disable="!inviteForm.$valid">
+			<button type="submit" class="btn btn-primary" ng-click="submit();" ng-disable="!inviteForm.$valid">
 				Sign Up
 			</button>
 		</div>

+ 1 - 1
public/app/partials/signup_step2.html

@@ -37,7 +37,7 @@
       </div>
 
       <div class="gf-form-button-row p-t-3">
-        <button type="submit" class="btn btn-success" ng-click="ctrl.submit();" ng-disabled="!signUpForm.$valid">
+        <button type="submit" class="btn btn-primary" ng-click="ctrl.submit();" ng-disabled="!signUpForm.$valid">
           Sign Up
         </button>
         <a href="login" class="btn btn-inverse">

+ 7 - 1
public/app/types/explore.ts

@@ -11,7 +11,7 @@ import {
 } from '@grafana/ui';
 
 import { Emitter } from 'app/core/core';
-import { LogsModel } from 'app/core/logs_model';
+import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model';
 import TableModel from 'app/core/table_model';
 
 export interface CompletionItem {
@@ -237,12 +237,18 @@ export interface ExploreItemState {
    * React keys for rendering of QueryRows
    */
   queryKeys: string[];
+
+  /**
+   * Current logs deduplication strategy
+   */
+  dedupStrategy?: LogsDedupStrategy;
 }
 
 export interface ExploreUIState {
   showingTable: boolean;
   showingGraph: boolean;
   showingLogs: boolean;
+  dedupStrategy?: LogsDedupStrategy;
 }
 
 export interface ExploreUrlState {

+ 49 - 37
public/sass/_variables.dark.scss

@@ -3,6 +3,21 @@
 
 $theme-name: dark;
 
+// New Colors
+// -------------------------
+$sapphire-faint: #041126;
+$sapphire-light: #5794F2;
+$sapphire-base: #3274D9;
+$sapphire-shade: #1F60C4;
+$lobster-base: #E02F44;
+$lobster-shade: #C4162A;
+$forest-light: #96D98D;
+$forest-base: #37872D;
+$forest-shade: #19730E;
+$green-base: #299C46;
+$green-shade: #23843B;
+
+
 // Grays
 // -------------------------
 $black: #000;
@@ -26,30 +41,29 @@ $white: #fff;
 // Accent colors
 // -------------------------
 $blue: #33b5e5;
-$blue-dark: #005f81;
 $green: #299c46;
-$red: #d44a3a;
+$red: $lobster-base;
 $yellow: #ecbb13;
 $purple: #9933cc;
 $variable: #32d1df;
 $orange: #eb7b18;
 
 $brand-primary: $orange;
-$brand-success: $green;
+$brand-success: $green-base;
 $brand-warning: $brand-primary;
-$brand-danger: $red;
+$brand-danger: $lobster-base;
 
-$query-red: #e24d42;
-$query-green: #74e680;
+$query-red: $lobster-base;
+$query-green: $forest-light;
 $query-purple: #fe85fc;
 $query-keyword: #66d9ef;
 $query-orange: $orange;
 
 // Status colors
 // -------------------------
-$online: #10a345;
+$online: $green-base;
 $warn: #f79520;
-$critical: #ed2e18;
+$critical: $lobster-base;
 
 // Scaffolding
 // -------------------------
@@ -82,7 +96,7 @@ $edit-gradient: linear-gradient(180deg, rgb(22, 23, 25) 50%, #090909);
 $link-color: darken($white, 11%);
 $link-color-disabled: darken($link-color, 30%);
 $link-hover-color: $white;
-$external-link-color: $blue;
+$external-link-color: $sapphire-light;
 
 // Typography
 // -------------------------
@@ -144,20 +158,18 @@ $table-bg-hover: $dark-3;
 
 // Buttons
 // -------------------------
-$btn-primary-bg: #ff6600;
-$btn-primary-bg-hl: #bc3e06;
 
-$btn-secondary-bg-hl: lighten($blue-dark, 5%);
-$btn-secondary-bg: $blue-dark;
+$btn-secondary-bg: $sapphire-base;
+$btn-secondary-bg-hl: $sapphire-shade;
 
-$btn-success-bg: $green;
-$btn-success-bg-hl: darken($green, 6%);
+$btn-primary-bg: $green-base;
+$btn-primary-bg-hl: $green-shade;
 
-$btn-warning-bg: $brand-warning;
-$btn-warning-bg-hl: lighten($brand-warning, 8%);
+$btn-success-bg: $green-base;
+$btn-success-bg-hl: $green-shade;
 
-$btn-danger-bg: $red;
-$btn-danger-bg-hl: darken($red, 8%);
+$btn-danger-bg: $lobster-base;
+$btn-danger-bg-hl: $lobster-shade;
 
 $btn-inverse-bg: $dark-3;
 $btn-inverse-bg-hl: lighten($dark-3, 4%);
@@ -257,13 +269,12 @@ $toolbar-bg: $input-black;
 // -------------------------
 $warning-text-color: $warn;
 $error-text-color: #e84d4d;
-$success-text-color: #12d95a;
-$info-text-color: $blue-dark;
+$success-text-color: $forest-light;
 
-$alert-error-bg: linear-gradient(90deg, #d44939, #e0603d);
-$alert-success-bg: linear-gradient(90deg, #3aa655, #47b274);
-$alert-warning-bg: linear-gradient(90deg, #d44939, #e0603d);
-$alert-info-bg: linear-gradient(100deg, #1a4552, #00374a);
+$alert-error-bg: linear-gradient(90deg, $lobster-base, $lobster-shade);
+$alert-success-bg: linear-gradient(90deg, $green-base, $green-shade);
+$alert-warning-bg: linear-gradient(90deg, $lobster-base, $lobster-shade);
+$alert-info-bg: linear-gradient(100deg, $sapphire-base, $sapphire-shade);
 
 // popover
 $popover-bg: $page-bg;
@@ -292,7 +303,7 @@ $tooltipBackgroundError: $brand-danger;
 $checkboxImageUrl: '../img/checkbox.png';
 
 // info box
-$info-box-border-color: darken($blue, 12%);
+$info-box-border-color: $sapphire-base;
 
 // footer
 $footer-link-color: $gray-2;
@@ -323,8 +334,8 @@ $diff-arrow-color: $white;
 $diff-json-bg: $dark-4;
 $diff-json-fg: $gray-5;
 
-$diff-json-added: #457740;
-$diff-json-deleted: #a04338;
+$diff-json-added: $sapphire-shade;
+$diff-json-deleted: $lobster-shade;
 
 $diff-json-old: #a04338;
 $diff-json-new: #457740;
@@ -335,21 +346,21 @@ $diff-json-changed-num: $text-color;
 $diff-json-icon: $gray-7;
 
 //Submenu
-$variable-option-bg: $blue-dark;
+$variable-option-bg: $dropdownLinkBackgroundHover;
 
 //Switch Slider
 // -------------------------
 $switch-bg: $input-bg;
 $switch-slider-color: $dark-2;
 $switch-slider-off-bg: $gray-1;
-$switch-slider-on-bg: linear-gradient(90deg, $orange, $red);
+$switch-slider-on-bg: linear-gradient(90deg, #eb7b18, #d44a3a);
 $switch-slider-shadow: 0 0 3px black;
 
 //Checkbox
 // -------------------------
 $checkbox-bg: $dark-1;
 $checkbox-border: 1px solid $gray-1;
-$checkbox-checked-bg: linear-gradient(0deg, $orange, $red);
+$checkbox-checked-bg: linear-gradient(0deg, #eb7b18, #d44a3a);
 $checkbox-color: $dark-1;
 
 //Panel Edit
@@ -358,23 +369,24 @@ $panel-editor-shadow: 0 0 20px black;
 $panel-editor-side-menu-shadow: drop-shadow(0 0 10px $black);
 $panel-editor-viz-item-shadow: 0 0 8px $dark-5;
 $panel-editor-viz-item-border: 1px solid $dark-5;
-$panel-editor-viz-item-shadow-hover: 0 0 4px $blue;
-$panel-editor-viz-item-border-hover: 1px solid $blue;
+$panel-editor-viz-item-shadow-hover: 0 0 4px $sapphire-light;
+$panel-editor-viz-item-border-hover: 1px solid $sapphire-light;
 $panel-editor-viz-item-bg: $input-black;
 $panel-editor-tabs-line-color: #e3e3e3;
-$panel-editor-viz-item-bg-hover: darken($blue, 47%);
+
+$panel-editor-viz-item-bg-hover: darken($sapphire-base, 46%);
 
 $panel-options-group-border: none;
 $panel-options-group-header-bg: $gray-blue;
 
-$panel-grid-placeholder-bg: darken($blue, 47%);
-$panel-grid-placeholder-shadow: 0 0 4px $blue;
+$panel-grid-placeholder-bg: $sapphire-faint;
+$panel-grid-placeholder-shadow: 0 0 4px $sapphire-shade;
 
 // logs
 $logs-color-unkown: $gray-2;
 
 // toggle-group
-$button-toggle-group-btn-active-bg: linear-gradient(90deg, $orange, $red);
+$button-toggle-group-btn-active-bg: linear-gradient(90deg, #eb7b18, #d44a3a);
 $button-toggle-group-btn-active-shadow: inset 0 0 4px $black;
 $button-toggle-group-btn-seperator-border: 1px solid $page-bg;
 

+ 54 - 46
public/sass/_variables.light.scss

@@ -3,6 +3,21 @@
 
 $theme-name: light;
 
+// New Colors
+// -------------------------
+$sapphire-faint: #F5F9FF;
+$sapphire-light: #A8CAFF;
+$sapphire-base: #3274D9;
+$sapphire-shade: #1F60C4;
+$lobster-base: #E02F44;
+$lobster-shade: #C4162A;
+$green-base: #37872D;
+$green-shade: #19730E;
+$green-base: #3EB15B;
+$green-shade: #369B4F;
+$purple-shade: #8F3BB8;
+$yellow-base: #F2CC0C;
+
 // Grays
 // -------------------------
 $black: #000;
@@ -25,28 +40,28 @@ $white: #fff;
 $blue: #0083b3;
 $blue-light: #00a8e6;
 $green: #3aa655;
-$red: #d44939;
+$red: $lobster-base;
 $yellow: #ff851b;
 $orange: #ff7941;
 $purple: #9954bb;
-$variable: $blue;
+$variable: $purple-shade;
 
 $brand-primary: $orange;
 $brand-success: $green;
 $brand-warning: $orange;
-$brand-danger: $red;
+$brand-danger: $lobster-base;
 
-$query-red: $red;
+$query-red: $lobster-base;
 $query-green: $green;
 $query-purple: $purple;
 $query-orange: $orange;
-$query-keyword: $blue;
+$query-keyword: $sapphire-base;
 
 // Status colors
 // -------------------------
-$online: #01a64f;
+$online: $green-shade;
 $warn: #f79520;
-$critical: #ec2128;
+$critical: $lobster-shade;
 
 // Scaffolding
 // -------------------------
@@ -61,7 +76,6 @@ $text-color-faint: $gray-4;
 $text-color-emphasis: $dark-5;
 
 $text-shadow-faint: none;
-$textShadow: none;
 
 // gradients
 $brand-gradient: linear-gradient(
@@ -79,7 +93,7 @@ $edit-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%);
 $link-color: $gray-1;
 $link-color-disabled: lighten($link-color, 30%);
 $link-hover-color: darken($link-color, 20%);
-$external-link-color: $blue-light;
+$external-link-color: $sapphire-shade;
 
 // Typography
 // -------------------------
@@ -141,20 +155,17 @@ $table-bg-hover: $gray-5;
 
 // Buttons
 // -------------------------
-$btn-primary-bg: $brand-primary;
-$btn-primary-bg-hl: lighten($brand-primary, 8%);
+$btn-primary-bg: $green-base;
+$btn-primary-bg-hl: $green-shade;
 
-$btn-secondary-bg: $blue;
-$btn-secondary-bg-hl: lighten($blue, 4%);
+$btn-secondary-bg: $sapphire-base;
+$btn-secondary-bg-hl: $sapphire-shade;
 
-$btn-success-bg: lighten($green, 3%);
-$btn-success-bg-hl: darken($green, 3%);
+$btn-success-bg: $green-base;
+$btn-success-bg-hl: $green-shade;
 
-$btn-warning-bg: lighten($orange, 3%);
-$btn-warning-bg-hl: darken($orange, 3%);
-
-$btn-danger-bg: lighten($red, 3%);
-$btn-danger-bg-hl: darken($red, 3%);
+$btn-danger-bg: $lobster-base;
+$btn-danger-bg-hl: $lobster-shade;
 
 $btn-inverse-bg: $gray-6;
 $btn-inverse-bg-hl: darken($gray-6, 5%);
@@ -178,8 +189,8 @@ $input-bg-disabled: $gray-5;
 $input-color: $dark-3;
 $input-border-color: $gray-5;
 $input-box-shadow: none;
-$input-border-focus: $blue !default;
-$input-box-shadow-focus: $blue !default;
+$input-border-focus: $sapphire-light !default;
+$input-box-shadow-focus: $sapphire-light !default;
 $input-color-placeholder: $gray-4 !default;
 $input-label-bg: $gray-5;
 $input-label-border-color: $gray-5;
@@ -253,14 +264,13 @@ $toolbar-bg: white;
 // Form states and alerts
 // -------------------------
 $warning-text-color: lighten($orange, 10%);
-$error-text-color: lighten($red, 10%);
+$error-text-color: $lobster-shade;
 $success-text-color: lighten($green, 10%);
-$info-text-color: $blue;
 
-$alert-error-bg: linear-gradient(90deg, #d44939, #e04d3d);
-$alert-success-bg: linear-gradient(90deg, #3aa655, #47b274);
-$alert-warning-bg: linear-gradient(90deg, #d44939, #e04d3d);
-$alert-info-bg: $blue;
+$alert-error-bg: linear-gradient(90deg, $lobster-base, $lobster-shade);
+$alert-success-bg: linear-gradient(90deg, $green-base, $green-shade);
+$alert-warning-bg: linear-gradient(90deg, $lobster-base, $lobster-shade);
+$alert-info-bg: $sapphire-base;
 
 // popover
 $popover-bg: $page-bg;
@@ -268,7 +278,7 @@ $popover-color: $text-color;
 $popover-border-color: $gray-5;
 $popover-shadow: 0 0 20px $white;
 
-$popover-help-bg: $blue;
+$popover-help-bg: $sapphire-base;
 $popover-help-color: $gray-6;
 
 $popover-error-bg: $btn-danger-bg;
@@ -289,7 +299,7 @@ $tooltipBackgroundError: $brand-danger;
 $checkboxImageUrl: '../img/checkbox_white.png';
 
 // info box
-$info-box-border-color: lighten($blue, 20%);
+$info-box-border-color: $sapphire-base;
 
 // footer
 $footer-link-color: $gray-3;
@@ -298,16 +308,16 @@ $footer-link-hover: $dark-5;
 // json explorer
 $json-explorer-default-color: black;
 $json-explorer-string-color: green;
-$json-explorer-number-color: blue;
-$json-explorer-boolean-color: red;
+$json-explorer-number-color: $sapphire-base;
+$json-explorer-boolean-color: $lobster-base;
 $json-explorer-null-color: #855a00;
 $json-explorer-undefined-color: rgb(202, 11, 105);
 $json-explorer-function-color: #ff20ed;
 $json-explorer-rotate-time: 100ms;
 $json-explorer-toggler-opacity: 0.6;
-$json-explorer-bracket-color: blue;
+$json-explorer-bracket-color: $sapphire-base;
 $json-explorer-key-color: #00008b;
-$json-explorer-url-color: blue;
+$json-explorer-url-color: $sapphire-base;
 
 // Changelog and diff
 // -------------------------
@@ -318,35 +328,35 @@ $diff-arrow-color: $dark-3;
 $diff-group-bg: $gray-7;
 
 $diff-json-bg: $gray-5;
-$diff-json-fg: $gray-2;
+$diff-json-fg: $gray-1;
 
-$diff-json-added: lighten(desaturate($green, 30%), 10%);
-$diff-json-deleted: desaturate($red, 35%);
+$diff-json-added: $sapphire-shade;
+$diff-json-deleted: $lobster-shade;
 
 $diff-json-old: #5a372a;
 $diff-json-new: #664e33;
 
-$diff-json-changed-fg: $gray-6;
+$diff-json-changed-fg: $gray-7;
 $diff-json-changed-num: $gray-4;
 
 $diff-json-icon: $gray-4;
 
 //Submenu
-$variable-option-bg: $blue-light;
+$variable-option-bg: $dropdownLinkBackgroundHover;
 
 //Switch Slider
 // -------------------------
 $switch-bg: $white;
 $switch-slider-color: $gray-7;
 $switch-slider-off-bg: $gray-5;
-$switch-slider-on-bg: linear-gradient(90deg, $yellow, $red);
+$switch-slider-on-bg: linear-gradient(90deg, #FF9830, #E55400);
 $switch-slider-shadow: 0 0 3px $dark-5;
 
 //Checkbox
 // -------------------------
 $checkbox-bg: $gray-6;
 $checkbox-border: 1px solid $gray-3;
-$checkbox-checked-bg: linear-gradient(0deg, $yellow, $red);
+$checkbox-checked-bg: linear-gradient(0deg, #FF9830, #E55400);
 $checkbox-color: $gray-7;
 
 //Panel Edit
@@ -359,13 +369,11 @@ $panel-editor-viz-item-shadow-hover: 0 0 4px $blue-light;
 $panel-editor-viz-item-border-hover: 1px solid $blue-light;
 $panel-editor-viz-item-bg: $white;
 $panel-editor-tabs-line-color: $dark-5;
-$panel-editor-viz-item-bg-hover: lighten($blue, 62%);
-
-$panel-options-group-border: none;
+$panel-editor-viz-item-bg-hover: lighten($blue, 62%);$panel-options-group-border: none;
 $panel-options-group-header-bg: $gray-5;
 
-$panel-grid-placeholder-bg: lighten($blue, 62%);
-$panel-grid-placeholder-shadow: 0 0 4px $blue-light;
+$panel-grid-placeholder-bg: $sapphire-faint;
+$panel-grid-placeholder-shadow: 0 0 4px $sapphire-light;
 
 // logs
 $logs-color-unkown: $gray-5;

+ 0 - 8
public/sass/base/_type.scss

@@ -59,14 +59,6 @@ a.text-error:focus {
   color: darken($error-text-color, 10%);
 }
 
-.text-info {
-  color: $info-text-color;
-}
-a.text-info:hover,
-a.text-info:focus {
-  color: darken($info-text-color, 10%);
-}
-
 .text-success {
   color: $success-text-color;
 }

Некоторые файлы не были показаны из-за большого количества измененных файлов