mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into tooling/storybook-poc
This commit is contained in:
commit
7ad430a6be
@ -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/...'
|
||||
name: Gometalinter tests
|
||||
command: './scripts/gometalinter.sh'
|
||||
|
||||
test-frontend:
|
||||
docker:
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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}
|
||||
|
@ -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}>
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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();',
|
||||
|
@ -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', () => {
|
||||
beforeEach(() => {
|
||||
additionalItems = ctrl.getAdditionalMenuItems();
|
||||
});
|
||||
|
||||
describe('and has no datasource set but user has access to explore', () => {
|
||||
it('should not return any items', () => {
|
||||
expect(additionalItems.length).toBe(0);
|
||||
const ctrl = setupController({ hasAccessToExplore: true });
|
||||
|
||||
expect(ctrl.getAdditionalMenuItems().length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and has datasource set that supports explore and user has powers', () => {
|
||||
beforeEach(() => {
|
||||
ctrl.contextSrv = { isEditor: true };
|
||||
ctrl.datasource = { meta: { explore: true } };
|
||||
additionalItems = ctrl.getAdditionalMenuItems();
|
||||
});
|
||||
|
||||
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(1);
|
||||
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 access to explore', () => {
|
||||
it('should return one item', () => {
|
||||
const ctrl = setupController({ hasAccessToExplore: true });
|
||||
ctrl.datasource = { meta: { explore: true } };
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -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
scripts/gometalinter.sh
Executable file
33
scripts/gometalinter.sh
Executable file
@ -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/...
|
Loading…
Reference in New Issue
Block a user