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

Merge branch 'master' into tooling/storybook-poc

Dominik Prokop 7 лет назад
Родитель
Сommit
7ad430a6be

+ 3 - 14
.circleci/config.yml

@@ -81,20 +81,9 @@ jobs:
     working_directory: /go/src/github.com/grafana/grafana
     steps:
       - checkout
-      - run: 'go get -u github.com/alecthomas/gometalinter'
-      - run: 'go get -u github.com/tsenart/deadcode'
-      - run: 'go get -u github.com/jgautheron/goconst/cmd/goconst'
-      - run: 'go get -u github.com/gordonklaus/ineffassign'
-      - run: 'go get -u honnef.co/go/tools/cmd/megacheck'
-      - run: 'go get -u github.com/opennota/check/cmd/structcheck'
-      - run: 'go get -u github.com/mdempsky/unconvert'
-      - run: 'go get -u github.com/opennota/check/cmd/varcheck'
-      - run:
-          name: run linters
-          command: 'gometalinter --enable-gc --vendor --deadline 10m --disable-all --enable=deadcode --enable=goconst --enable=gofmt --enable=ineffassign --enable=megacheck --enable=structcheck --enable=unconvert --enable=varcheck ./...'
-      - run:
-          name: run go vet
-          command: 'go vet ./pkg/...'
+      - run:
+          name: Gometalinter tests
+          command: './scripts/gometalinter.sh'
 
   test-frontend:
     docker:

+ 1 - 0
pkg/api/frontendsettings.go

@@ -165,6 +165,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
 		"externalUserMngInfo":        setting.ExternalUserMngInfo,
 		"externalUserMngLinkUrl":     setting.ExternalUserMngLinkUrl,
 		"externalUserMngLinkName":    setting.ExternalUserMngLinkName,
+		"viewersCanEdit":             setting.ViewersCanEdit,
 		"buildInfo": map[string]interface{}{
 			"version":       setting.BuildVersion,
 			"commit":        setting.BuildCommit,

+ 1 - 1
pkg/api/index.go

@@ -140,7 +140,7 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er
 		Children: dashboardChildNavs,
 	})
 
-	if setting.ExploreEnabled && (c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR) {
+	if setting.ExploreEnabled && (c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR || setting.ViewersCanEdit) {
 		data.NavTree = append(data.NavTree, &dtos.NavLink{
 			Text:     "Explore",
 			Id:       "explore",

+ 2 - 0
public/app/core/config.ts

@@ -34,6 +34,7 @@ export class Settings {
   disableUserSignUp: boolean;
   loginHint: any;
   loginError: any;
+  viewersCanEdit: boolean;
 
   constructor(options) {
     const defaults = {
@@ -50,6 +51,7 @@ export class Settings {
         env: 'production',
         isEnterprise: false,
       },
+      viewersCanEdit: false,
     };
 
     _.extend(this, defaults, options);

+ 4 - 0
public/app/core/services/context_srv.ts

@@ -61,6 +61,10 @@ export class ContextSrv {
     store.set('grafana.sidemenu', this.sidemenu);
   }
 
+  hasAccessToExplore() {
+    return (this.isEditor || config.viewersCanEdit) && config.exploreEnabled;
+  }
+
   getTheme(): ThemeName {
     return this.user.lightTheme ? ThemeNames.Light : ThemeNames.Dark;
   }

+ 3 - 3
public/app/core/services/keybindingSrv.ts

@@ -1,13 +1,13 @@
 import $ from 'jquery';
 import _ from 'lodash';
 
-import config from 'app/core/config';
 import coreModule from 'app/core/core_module';
 import appEvents from 'app/core/app_events';
 import { getExploreUrl } from 'app/core/utils/explore';
 
 import Mousetrap from 'mousetrap';
 import 'mousetrap-global-bind';
+import { ContextSrv } from './context_srv';
 
 export class KeybindingSrv {
   helpModal: boolean;
@@ -21,7 +21,7 @@ export class KeybindingSrv {
     private $timeout,
     private datasourceSrv,
     private timeSrv,
-    private contextSrv
+    private contextSrv: ContextSrv
   ) {
     // clear out all shortcuts on route change
     $rootScope.$on('$routeChangeSuccess', () => {
@@ -196,7 +196,7 @@ export class KeybindingSrv {
     });
 
     // jump to explore if permissions allow
-    if (this.contextSrv.isEditor && config.exploreEnabled) {
+    if (this.contextSrv.hasAccessToExplore()) {
       this.bind('x', async () => {
         if (dashboard.meta.focusPanelId) {
           const panel = dashboard.getPanelById(dashboard.meta.focusPanelId);

+ 1 - 1
public/app/features/dashboard/panel_editor/QueriesTab.tsx

@@ -197,7 +197,7 @@ export class QueriesTab extends PureComponent<Props, State> {
           <div className="query-editor-rows">
             {panel.targets.map((query, index) => (
               <QueryEditorRow
-                datasourceName={query.datasource || panel.datasource}
+                dataSourceValue={query.datasource || panel.datasource}
                 key={query.refId}
                 panel={panel}
                 query={query}

+ 8 - 6
public/app/features/dashboard/panel_editor/QueryEditorRow.tsx

@@ -18,11 +18,12 @@ interface Props {
   onAddQuery: (query?: DataQuery) => void;
   onRemoveQuery: (query: DataQuery) => void;
   onMoveQuery: (query: DataQuery, direction: number) => void;
-  datasourceName: string | null;
+  dataSourceValue: string | null;
   inMixedMode: boolean;
 }
 
 interface State {
+  loadedDataSourceValue: string | null | undefined;
   datasource: DataSourceApi | null;
   isCollapsed: boolean;
   angularScope: AngularQueryComponentScope | null;
@@ -36,6 +37,7 @@ export class QueryEditorRow extends PureComponent<Props, State> {
     datasource: null,
     isCollapsed: false,
     angularScope: null,
+    loadedDataSourceValue: undefined,
   };
 
   componentDidMount() {
@@ -61,14 +63,14 @@ export class QueryEditorRow extends PureComponent<Props, State> {
     const dataSourceSrv = getDatasourceSrv();
     const datasource = await dataSourceSrv.get(query.datasource || panel.datasource);
 
-    this.setState({ datasource });
+    this.setState({ datasource, loadedDataSourceValue: this.props.dataSourceValue });
   }
 
   componentDidUpdate() {
-    const { datasource } = this.state;
+    const { loadedDataSourceValue } = this.state;
 
     // check if we need to load another datasource
-    if (datasource && datasource.name !== this.props.datasourceName) {
+    if (loadedDataSourceValue !== this.props.dataSourceValue) {
       if (this.angularQueryEditor) {
         this.angularQueryEditor.destroy();
         this.angularQueryEditor = null;
@@ -178,7 +180,7 @@ export class QueryEditorRow extends PureComponent<Props, State> {
   }
 
   render() {
-    const { query, datasourceName, inMixedMode } = this.props;
+    const { query, inMixedMode } = this.props;
     const { datasource, isCollapsed } = this.state;
     const isDisabled = query.hide;
 
@@ -202,7 +204,7 @@ export class QueryEditorRow extends PureComponent<Props, State> {
             {isCollapsed && <i className="fa fa-caret-right" />}
             {!isCollapsed && <i className="fa fa-caret-down" />}
             <span>{query.refId}</span>
-            {inMixedMode && <em className="query-editor-row__context-info"> ({datasourceName})</em>}
+            {inMixedMode && <em className="query-editor-row__context-info"> ({datasource.name})</em>}
             {isDisabled && <em className="query-editor-row__context-info"> Disabled</em>}
           </div>
           <div className="query-editor-row__collapsed-text" onClick={this.onToggleEditMode}>

+ 15 - 3
public/app/features/dashboard/panel_model.ts

@@ -62,7 +62,7 @@ const mustKeepProps: { [str: string]: boolean } = {
 const defaults: any = {
   gridPos: { x: 0, y: 0, h: 3, w: 6 },
   datasource: null,
-  targets: [{}],
+  targets: [{ refId: 'A' }],
   cachedPluginOptions: {},
   transparent: false,
 };
@@ -83,7 +83,7 @@ export class PanelModel {
   collapsed?: boolean;
   panels?: any;
   soloMode?: boolean;
-  targets: any[];
+  targets: DataQuery[];
   datasource: string;
   thresholds?: any;
 
@@ -118,6 +118,18 @@ export class PanelModel {
 
     // defaults
     _.defaultsDeep(this, _.cloneDeep(defaults));
+    // queries must have refId
+    this.ensureQueryIds();
+  }
+
+  ensureQueryIds() {
+    if (this.targets) {
+      for (const query of this.targets) {
+        if (!query.refId) {
+          query.refId = this.getNextQueryLetter();
+        }
+      }
+    }
   }
 
   getOptions(panelDefaults) {
@@ -243,7 +255,7 @@ export class PanelModel {
   addQuery(query?: Partial<DataQuery>) {
     query = query || { refId: 'A' };
     query.refId = this.getNextQueryLetter();
-    this.targets.push(query);
+    this.targets.push(query as DataQuery);
   }
 
   getNextQueryLetter(): string {

+ 8 - 0
public/app/features/dashboard/specs/panel_model.test.ts

@@ -9,6 +9,10 @@ describe('PanelModel', () => {
       model = new PanelModel({
         type: 'table',
         showColumns: true,
+        targets: [
+          {refId: 'A'},
+          {noRefId: true}
+        ]
       });
     });
 
@@ -20,6 +24,10 @@ describe('PanelModel', () => {
       expect(model.showColumns).toBe(true);
     });
 
+    it('should add missing refIds', () => {
+      expect(model.targets[1].refId).toBe('B');
+    });
+
     it('getSaveModel should remove defaults', () => {
       const saveModel = model.getSaveModel();
       expect(saveModel.gridPos).toBe(undefined);

+ 3 - 3
public/app/features/panel/metrics_panel_ctrl.ts

@@ -1,18 +1,18 @@
 import _ from 'lodash';
 
 import kbn from 'app/core/utils/kbn';
-import config from 'app/core/config';
 
 import { PanelCtrl } from 'app/features/panel/panel_ctrl';
 import { getExploreUrl } from 'app/core/utils/explore';
 import { applyPanelTimeOverrides, getResolution } from 'app/features/dashboard/utils/panel';
+import { ContextSrv } from 'app/core/services/context_srv';
 
 class MetricsPanelCtrl extends PanelCtrl {
   scope: any;
   datasource: any;
   $q: any;
   $timeout: any;
-  contextSrv: any;
+  contextSrv: ContextSrv;
   datasourceSrv: any;
   timeSrv: any;
   templateSrv: any;
@@ -231,7 +231,7 @@ class MetricsPanelCtrl extends PanelCtrl {
 
   getAdditionalMenuItems() {
     const items = [];
-    if (config.exploreEnabled && this.contextSrv.isEditor && this.datasource) {
+    if (this.contextSrv.hasAccessToExplore() && this.datasource) {
       items.push({
         text: 'Explore',
         click: 'ctrl.explore();',

+ 18 - 20
public/app/features/panel/specs/metrics_panel_ctrl.test.ts

@@ -1,7 +1,6 @@
 jest.mock('app/core/core', () => ({}));
 jest.mock('app/core/config', () => {
   return {
-    exploreEnabled: true,
     panels: {
       test: {
         id: 'test',
@@ -16,46 +15,45 @@ import { PanelModel } from 'app/features/dashboard/panel_model';
 import { MetricsPanelCtrl } from '../metrics_panel_ctrl';
 
 describe('MetricsPanelCtrl', () => {
-  let ctrl;
-
-  beforeEach(() => {
-    ctrl = setupController();
-  });
-
   describe('when getting additional menu items', () => {
-    let additionalItems;
+    describe('and has no datasource set but user has access to explore', () => {
+      it('should not return any items', () => {
+        const ctrl = setupController({ hasAccessToExplore: true });
 
-    describe('and has no datasource set', () => {
-      beforeEach(() => {
-        additionalItems = ctrl.getAdditionalMenuItems();
+        expect(ctrl.getAdditionalMenuItems().length).toBe(0);
       });
+    });
 
+    describe('and has datasource set that supports explore and user does not have access to explore', () => {
       it('should not return any items', () => {
-        expect(additionalItems.length).toBe(0);
+        const ctrl = setupController({ hasAccessToExplore: false });
+        ctrl.datasource = { meta: { explore: true } };
+
+        expect(ctrl.getAdditionalMenuItems().length).toBe(0);
       });
     });
 
-    describe('and has datasource set that supports explore and user has powers', () => {
-      beforeEach(() => {
-        ctrl.contextSrv = { isEditor: true };
+    describe('and has datasource set that supports explore and user has access to explore', () => {
+      it('should return one item', () => {
+        const ctrl = setupController({ hasAccessToExplore: true });
         ctrl.datasource = { meta: { explore: true } };
-        additionalItems = ctrl.getAdditionalMenuItems();
-      });
 
-      it('should not return any items', () => {
-        expect(additionalItems.length).toBe(1);
+        expect(ctrl.getAdditionalMenuItems().length).toBe(1);
       });
     });
   });
 });
 
-function setupController() {
+function setupController({ hasAccessToExplore } = { hasAccessToExplore: false }) {
   const injectorStub = {
     get: type => {
       switch (type) {
         case '$q': {
           return q;
         }
+        case 'contextSrv': {
+          return { hasAccessToExplore: () => hasAccessToExplore };
+        }
         default: {
           return jest.fn();
         }

+ 2 - 1
public/app/routes/routes.ts

@@ -16,6 +16,7 @@ import UsersListPage from 'app/features/users/UsersListPage';
 import DataSourceDashboards from 'app/features/datasources/DataSourceDashboards';
 import DataSourceSettingsPage from '../features/datasources/settings/DataSourceSettingsPage';
 import OrgDetailsPage from '../features/org/OrgDetailsPage';
+import config from 'app/core/config';
 
 /** @ngInject */
 export function setupAngularRoutes($routeProvider, $locationProvider) {
@@ -129,7 +130,7 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
       template: '<react-container />',
       reloadOnSearch: false,
       resolve: {
-        roles: () => ['Editor', 'Admin'],
+        roles: () => (config.viewersCanEdit ? [] : ['Editor', 'Admin']),
         component: () => import(/* webpackChunkName: "explore" */ 'app/features/explore/Wrapper'),
       },
     })

+ 33 - 0
scripts/gometalinter.sh

@@ -0,0 +1,33 @@
+#!/bin/bash
+
+function exit_if_fail {
+    command=$@
+    echo "Executing '$command'"
+    eval $command
+    rc=$?
+    if [ $rc -ne 0 ]; then
+        echo "'$command' returned $rc."
+        exit $rc
+    fi
+}
+
+go get -u github.com/alecthomas/gometalinter
+go get -u github.com/tsenart/deadcode
+go get -u github.com/jgautheron/goconst/cmd/goconst
+go get -u github.com/gordonklaus/ineffassign
+go get -u github.com/opennota/check/cmd/structcheck
+go get -u github.com/mdempsky/unconvert
+go get -u github.com/opennota/check/cmd/varcheck
+go get -u honnef.co/go/tools/cmd/staticcheck
+
+exit_if_fail gometalinter --enable-gc --vendor --deadline 10m --disable-all \
+  --enable=deadcode \
+  --enable=goconst \
+  --enable=gofmt \
+  --enable=ineffassign \
+  --enable=structcheck \
+  --enable=unconvert \
+  --enable=varcheck \
+  --enable=staticcheck
+
+exit_if_fail go vet ./pkg/...