mirror of https://github.com/grafana/grafana
commit
fb3c510178
@ -0,0 +1,27 @@ |
||||
server: |
||||
http_listen_port: 9080 |
||||
grpc_listen_port: 0 |
||||
|
||||
positions: |
||||
filename: /tmp/positions.yaml |
||||
|
||||
client: |
||||
url: http://loki:3100/api/prom/push |
||||
|
||||
scrape_configs: |
||||
- job_name: system |
||||
entry_parser: raw |
||||
static_configs: |
||||
- targets: |
||||
- localhost |
||||
labels: |
||||
job: varlogs |
||||
__path__: /var/log/*log |
||||
- job_name: grafana |
||||
entry_parser: raw |
||||
static_configs: |
||||
- targets: |
||||
- localhost |
||||
labels: |
||||
job: grafana |
||||
__path__: /var/log/grafana/*log |
@ -1,26 +1,38 @@ |
||||
// Libraries
|
||||
import React, { SFC } from 'react'; |
||||
import React, { FunctionComponent } from 'react'; |
||||
|
||||
interface Props { |
||||
title?: string; |
||||
onClose?: () => void; |
||||
children: JSX.Element | JSX.Element[]; |
||||
children: JSX.Element | JSX.Element[] | boolean; |
||||
onAdd?: () => void; |
||||
} |
||||
|
||||
export const PanelOptionsGroup: SFC<Props> = props => { |
||||
export const PanelOptionsGroup: FunctionComponent<Props> = props => { |
||||
return ( |
||||
<div className="panel-options-group"> |
||||
{props.title && ( |
||||
{props.onAdd ? ( |
||||
<div className="panel-options-group__header"> |
||||
{props.title} |
||||
{props.onClose && ( |
||||
<button className="btn btn-link" onClick={props.onClose}> |
||||
<i className="fa fa-remove" /> |
||||
</button> |
||||
)} |
||||
<button className="panel-options-group__add-btn" onClick={props.onAdd}> |
||||
<div className="panel-options-group__add-circle"> |
||||
<i className="fa fa-plus" /> |
||||
</div> |
||||
<span className="panel-options-group__title">{props.title}</span> |
||||
</button> |
||||
</div> |
||||
) : ( |
||||
props.title && ( |
||||
<div className="panel-options-group__header"> |
||||
<span className="panel-options-group__title">{props.title}</span> |
||||
{props.onClose && ( |
||||
<button className="btn btn-link" onClick={props.onClose}> |
||||
<i className="fa fa-remove" /> |
||||
</button> |
||||
)} |
||||
</div> |
||||
) |
||||
)} |
||||
<div className="panel-options-group__body">{props.children}</div> |
||||
{props.children && <div className="panel-options-group__body">{props.children}</div>} |
||||
</div> |
||||
); |
||||
}; |
||||
|
@ -1,25 +0,0 @@ |
||||
package util |
||||
|
||||
import ( |
||||
"net" |
||||
) |
||||
|
||||
// SplitIPPort splits the ip string and port.
|
||||
func SplitIPPort(ipStr string, portDefault string) (ip string, port string, err error) { |
||||
ipAddr := net.ParseIP(ipStr) |
||||
|
||||
if ipAddr == nil { |
||||
// Port was included
|
||||
ip, port, err = net.SplitHostPort(ipStr) |
||||
|
||||
if err != nil { |
||||
return "", "", err |
||||
} |
||||
} else { |
||||
// No port was included
|
||||
ip = ipAddr.String() |
||||
port = portDefault |
||||
} |
||||
|
||||
return ip, port, nil |
||||
} |
@ -1,43 +0,0 @@ |
||||
package util |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
. "github.com/smartystreets/goconvey/convey" |
||||
) |
||||
|
||||
func TestSplitIPPort(t *testing.T) { |
||||
|
||||
Convey("When parsing an IPv4 without explicit port", t, func() { |
||||
ip, port, err := SplitIPPort("1.2.3.4", "5678") |
||||
|
||||
So(err, ShouldEqual, nil) |
||||
So(ip, ShouldEqual, "1.2.3.4") |
||||
So(port, ShouldEqual, "5678") |
||||
}) |
||||
|
||||
Convey("When parsing an IPv6 without explicit port", t, func() { |
||||
ip, port, err := SplitIPPort("::1", "5678") |
||||
|
||||
So(err, ShouldEqual, nil) |
||||
So(ip, ShouldEqual, "::1") |
||||
So(port, ShouldEqual, "5678") |
||||
}) |
||||
|
||||
Convey("When parsing an IPv4 with explicit port", t, func() { |
||||
ip, port, err := SplitIPPort("1.2.3.4:56", "78") |
||||
|
||||
So(err, ShouldEqual, nil) |
||||
So(ip, ShouldEqual, "1.2.3.4") |
||||
So(port, ShouldEqual, "56") |
||||
}) |
||||
|
||||
Convey("When parsing an IPv6 with explicit port", t, func() { |
||||
ip, port, err := SplitIPPort("[::1]:56", "78") |
||||
|
||||
So(err, ShouldEqual, nil) |
||||
So(ip, ShouldEqual, "::1") |
||||
So(port, ShouldEqual, "56") |
||||
}) |
||||
|
||||
} |
@ -0,0 +1,83 @@ |
||||
import { |
||||
actionCreatorFactory, |
||||
resetAllActionCreatorTypes, |
||||
noPayloadActionCreatorFactory, |
||||
} from './actionCreatorFactory'; |
||||
|
||||
interface Dummy { |
||||
n: number; |
||||
s: string; |
||||
o: { |
||||
n: number; |
||||
s: string; |
||||
b: boolean; |
||||
}; |
||||
b: boolean; |
||||
} |
||||
|
||||
const setup = (payload?: Dummy) => { |
||||
resetAllActionCreatorTypes(); |
||||
const actionCreator = actionCreatorFactory<Dummy>('dummy').create(); |
||||
const noPayloadactionCreator = noPayloadActionCreatorFactory('NoPayload').create(); |
||||
const result = actionCreator(payload); |
||||
const noPayloadResult = noPayloadactionCreator(); |
||||
|
||||
return { actionCreator, noPayloadactionCreator, result, noPayloadResult }; |
||||
}; |
||||
|
||||
describe('actionCreatorFactory', () => { |
||||
describe('when calling create', () => { |
||||
it('then it should create correct type string', () => { |
||||
const payload = { n: 1, b: true, s: 'dummy', o: { n: 1, b: true, s: 'dummy' } }; |
||||
const { actionCreator, result } = setup(payload); |
||||
|
||||
expect(actionCreator.type).toEqual('dummy'); |
||||
expect(result.type).toEqual('dummy'); |
||||
}); |
||||
|
||||
it('then it should create correct payload', () => { |
||||
const payload = { n: 1, b: true, s: 'dummy', o: { n: 1, b: true, s: 'dummy' } }; |
||||
const { result } = setup(payload); |
||||
|
||||
expect(result.payload).toEqual(payload); |
||||
}); |
||||
}); |
||||
|
||||
describe('when calling create with existing type', () => { |
||||
it('then it should throw error', () => { |
||||
const payload = { n: 1, b: true, s: 'dummy', o: { n: 1, b: true, s: 'dummy' } }; |
||||
setup(payload); |
||||
|
||||
expect(() => { |
||||
noPayloadActionCreatorFactory('DuMmY').create(); |
||||
}).toThrow(); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('noPayloadActionCreatorFactory', () => { |
||||
describe('when calling create', () => { |
||||
it('then it should create correct type string', () => { |
||||
const { noPayloadResult, noPayloadactionCreator } = setup(); |
||||
|
||||
expect(noPayloadactionCreator.type).toEqual('NoPayload'); |
||||
expect(noPayloadResult.type).toEqual('NoPayload'); |
||||
}); |
||||
|
||||
it('then it should create correct payload', () => { |
||||
const { noPayloadResult } = setup(); |
||||
|
||||
expect(noPayloadResult.payload).toBeUndefined(); |
||||
}); |
||||
}); |
||||
|
||||
describe('when calling create with existing type', () => { |
||||
it('then it should throw error', () => { |
||||
setup(); |
||||
|
||||
expect(() => { |
||||
actionCreatorFactory<Dummy>('nOpAyLoAd').create(); |
||||
}).toThrow(); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,57 @@ |
||||
import { Action } from 'redux'; |
||||
|
||||
const allActionCreators: string[] = []; |
||||
|
||||
export interface ActionOf<Payload> extends Action { |
||||
readonly type: string; |
||||
readonly payload: Payload; |
||||
} |
||||
|
||||
export interface ActionCreator<Payload> { |
||||
readonly type: string; |
||||
(payload: Payload): ActionOf<Payload>; |
||||
} |
||||
|
||||
export interface NoPayloadActionCreator { |
||||
readonly type: string; |
||||
(): ActionOf<undefined>; |
||||
} |
||||
|
||||
export interface ActionCreatorFactory<Payload> { |
||||
create: () => ActionCreator<Payload>; |
||||
} |
||||
|
||||
export interface NoPayloadActionCreatorFactory { |
||||
create: () => NoPayloadActionCreator; |
||||
} |
||||
|
||||
export const actionCreatorFactory = <Payload>(type: string): ActionCreatorFactory<Payload> => { |
||||
const create = (): ActionCreator<Payload> => { |
||||
return Object.assign((payload: Payload): ActionOf<Payload> => ({ type, payload }), { type }); |
||||
}; |
||||
|
||||
if (allActionCreators.some(t => (t && type ? t.toLocaleUpperCase() === type.toLocaleUpperCase() : false))) { |
||||
throw new Error(`There is already an actionCreator defined with the type ${type}`); |
||||
} |
||||
|
||||
allActionCreators.push(type); |
||||
|
||||
return { create }; |
||||
}; |
||||
|
||||
export const noPayloadActionCreatorFactory = (type: string): NoPayloadActionCreatorFactory => { |
||||
const create = (): NoPayloadActionCreator => { |
||||
return Object.assign((): ActionOf<undefined> => ({ type, payload: undefined }), { type }); |
||||
}; |
||||
|
||||
if (allActionCreators.some(t => (t && type ? t.toLocaleUpperCase() === type.toLocaleUpperCase() : false))) { |
||||
throw new Error(`There is already an actionCreator defined with the type ${type}`); |
||||
} |
||||
|
||||
allActionCreators.push(type); |
||||
|
||||
return { create }; |
||||
}; |
||||
|
||||
// Should only be used by tests
|
||||
export const resetAllActionCreatorTypes = () => (allActionCreators.length = 0); |
@ -0,0 +1,4 @@ |
||||
import { actionCreatorFactory } from './actionCreatorFactory'; |
||||
import { reducerFactory } from './reducerFactory'; |
||||
|
||||
export { actionCreatorFactory, reducerFactory }; |
@ -0,0 +1,97 @@ |
||||
import { reducerFactory } from './reducerFactory'; |
||||
import { actionCreatorFactory, ActionOf } from './actionCreatorFactory'; |
||||
|
||||
interface DummyReducerState { |
||||
n: number; |
||||
s: string; |
||||
b: boolean; |
||||
o: { |
||||
n: number; |
||||
s: string; |
||||
b: boolean; |
||||
}; |
||||
} |
||||
|
||||
const dummyReducerIntialState: DummyReducerState = { |
||||
n: 1, |
||||
s: 'One', |
||||
b: true, |
||||
o: { |
||||
n: 2, |
||||
s: 'two', |
||||
b: false, |
||||
}, |
||||
}; |
||||
|
||||
const dummyActionCreator = actionCreatorFactory<DummyReducerState>('dummy').create(); |
||||
|
||||
const dummyReducer = reducerFactory(dummyReducerIntialState) |
||||
.addMapper({ |
||||
filter: dummyActionCreator, |
||||
mapper: (state, action) => ({ ...state, ...action.payload }), |
||||
}) |
||||
.create(); |
||||
|
||||
describe('reducerFactory', () => { |
||||
describe('given it is created with a defined handler', () => { |
||||
describe('when reducer is called with no state', () => { |
||||
describe('and with an action that the handler can not handle', () => { |
||||
it('then the resulting state should be intial state', () => { |
||||
const result = dummyReducer(undefined as DummyReducerState, {} as ActionOf<any>); |
||||
|
||||
expect(result).toEqual(dummyReducerIntialState); |
||||
}); |
||||
}); |
||||
|
||||
describe('and with an action that the handler can handle', () => { |
||||
it('then the resulting state should correct', () => { |
||||
const payload = { n: 10, s: 'ten', b: false, o: { n: 20, s: 'twenty', b: true } }; |
||||
const result = dummyReducer(undefined as DummyReducerState, dummyActionCreator(payload)); |
||||
|
||||
expect(result).toEqual(payload); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when reducer is called with a state', () => { |
||||
describe('and with an action that the handler can not handle', () => { |
||||
it('then the resulting state should be intial state', () => { |
||||
const result = dummyReducer(dummyReducerIntialState, {} as ActionOf<any>); |
||||
|
||||
expect(result).toEqual(dummyReducerIntialState); |
||||
}); |
||||
}); |
||||
|
||||
describe('and with an action that the handler can handle', () => { |
||||
it('then the resulting state should correct', () => { |
||||
const payload = { n: 10, s: 'ten', b: false, o: { n: 20, s: 'twenty', b: true } }; |
||||
const result = dummyReducer(dummyReducerIntialState, dummyActionCreator(payload)); |
||||
|
||||
expect(result).toEqual(payload); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('given a handler is added', () => { |
||||
describe('when a handler with the same creator is added', () => { |
||||
it('then is should throw', () => { |
||||
const faultyReducer = reducerFactory(dummyReducerIntialState).addMapper({ |
||||
filter: dummyActionCreator, |
||||
mapper: (state, action) => { |
||||
return { ...state, ...action.payload }; |
||||
}, |
||||
}); |
||||
|
||||
expect(() => { |
||||
faultyReducer.addMapper({ |
||||
filter: dummyActionCreator, |
||||
mapper: state => { |
||||
return state; |
||||
}, |
||||
}); |
||||
}).toThrow(); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,45 @@ |
||||
import { ActionOf, ActionCreator } from './actionCreatorFactory'; |
||||
import { Reducer } from 'redux'; |
||||
|
||||
export type Mapper<State, Payload> = (state: State, action: ActionOf<Payload>) => State; |
||||
|
||||
export interface MapperConfig<State, Payload> { |
||||
filter: ActionCreator<Payload>; |
||||
mapper: Mapper<State, Payload>; |
||||
} |
||||
|
||||
export interface AddMapper<State> { |
||||
addMapper: <Payload>(config: MapperConfig<State, Payload>) => CreateReducer<State>; |
||||
} |
||||
|
||||
export interface CreateReducer<State> extends AddMapper<State> { |
||||
create: () => Reducer<State, ActionOf<any>>; |
||||
} |
||||
|
||||
export const reducerFactory = <State>(initialState: State): AddMapper<State> => { |
||||
const allMappers: { [key: string]: Mapper<State, any> } = {}; |
||||
|
||||
const addMapper = <Payload>(config: MapperConfig<State, Payload>): CreateReducer<State> => { |
||||
if (allMappers[config.filter.type]) { |
||||
throw new Error(`There is already a mapper defined with the type ${config.filter.type}`); |
||||
} |
||||
|
||||
allMappers[config.filter.type] = config.mapper; |
||||
|
||||
return instance; |
||||
}; |
||||
|
||||
const create = (): Reducer<State, ActionOf<any>> => (state: State = initialState, action: ActionOf<any>): State => { |
||||
const mapper = allMappers[action.type]; |
||||
|
||||
if (mapper) { |
||||
return mapper(state, action); |
||||
} |
||||
|
||||
return state; |
||||
}; |
||||
|
||||
const instance: CreateReducer<State> = { addMapper, create }; |
||||
|
||||
return instance; |
||||
}; |
@ -1,2 +1,3 @@ |
||||
export { SaveDashboardAsModalCtrl } from './SaveDashboardAsModalCtrl'; |
||||
export { SaveDashboardModalCtrl } from './SaveDashboardModalCtrl'; |
||||
export { SaveProvisionedDashboardModalCtrl } from './SaveProvisionedDashboardModalCtrl'; |
||||
|
@ -0,0 +1,123 @@ |
||||
// Libraries
|
||||
import React, { Component } from 'react'; |
||||
import { hot } from 'react-hot-loader'; |
||||
import { connect } from 'react-redux'; |
||||
|
||||
// Utils & Services
|
||||
import appEvents from 'app/core/app_events'; |
||||
import locationUtil from 'app/core/utils/location_util'; |
||||
import { getBackendSrv } from 'app/core/services/backend_srv'; |
||||
|
||||
// Components
|
||||
import { DashboardPanel } from '../dashgrid/DashboardPanel'; |
||||
|
||||
// Redux
|
||||
import { updateLocation } from 'app/core/actions'; |
||||
|
||||
// Types
|
||||
import { StoreState } from 'app/types'; |
||||
import { PanelModel, DashboardModel } from 'app/features/dashboard/state'; |
||||
|
||||
interface Props { |
||||
panelId: string; |
||||
urlUid?: string; |
||||
urlSlug?: string; |
||||
urlType?: string; |
||||
$scope: any; |
||||
$injector: any; |
||||
updateLocation: typeof updateLocation; |
||||
} |
||||
|
||||
interface State { |
||||
panel: PanelModel | null; |
||||
dashboard: DashboardModel | null; |
||||
notFound: boolean; |
||||
} |
||||
|
||||
export class SoloPanelPage extends Component<Props, State> { |
||||
|
||||
state: State = { |
||||
panel: null, |
||||
dashboard: null, |
||||
notFound: false, |
||||
}; |
||||
|
||||
componentDidMount() { |
||||
const { $injector, $scope, urlUid, urlType, urlSlug } = this.props; |
||||
|
||||
// handle old urls with no uid
|
||||
if (!urlUid && !(urlType === 'script' || urlType === 'snapshot')) { |
||||
this.redirectToNewUrl(); |
||||
return; |
||||
} |
||||
|
||||
const dashboardLoaderSrv = $injector.get('dashboardLoaderSrv'); |
||||
|
||||
// subscribe to event to know when dashboard controller is done with inititalization
|
||||
appEvents.on('dashboard-initialized', this.onDashoardInitialized); |
||||
|
||||
dashboardLoaderSrv.loadDashboard(urlType, urlSlug, urlUid).then(result => { |
||||
result.meta.soloMode = true; |
||||
$scope.initDashboard(result, $scope); |
||||
}); |
||||
} |
||||
|
||||
redirectToNewUrl() { |
||||
getBackendSrv().getDashboardBySlug(this.props.urlSlug).then(res => { |
||||
if (res) { |
||||
const url = locationUtil.stripBaseFromUrl(res.meta.url.replace('/d/', '/d-solo/')); |
||||
this.props.updateLocation(url); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
onDashoardInitialized = () => { |
||||
const { $scope, panelId } = this.props; |
||||
|
||||
const dashboard: DashboardModel = $scope.dashboard; |
||||
const panel = dashboard.getPanelById(parseInt(panelId, 10)); |
||||
|
||||
if (!panel) { |
||||
this.setState({ notFound: true }); |
||||
return; |
||||
} |
||||
|
||||
this.setState({ dashboard, panel }); |
||||
}; |
||||
|
||||
render() { |
||||
const { panelId } = this.props; |
||||
const { notFound, panel, dashboard } = this.state; |
||||
|
||||
if (notFound) { |
||||
return ( |
||||
<div className="alert alert-error"> |
||||
Panel with id { panelId } not found |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
if (!panel) { |
||||
return <div>Loading & initializing dashboard</div>; |
||||
} |
||||
|
||||
return ( |
||||
<div className="panel-solo"> |
||||
<DashboardPanel dashboard={dashboard} panel={panel} isEditing={false} isFullscreen={false} /> |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
const mapStateToProps = (state: StoreState) => ({ |
||||
urlUid: state.location.routeParams.uid, |
||||
urlSlug: state.location.routeParams.slug, |
||||
urlType: state.location.routeParams.type, |
||||
panelId: state.location.query.panelId |
||||
}); |
||||
|
||||
const mapDispatchToProps = { |
||||
updateLocation |
||||
}; |
||||
|
||||
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(SoloPanelPage)); |
@ -0,0 +1,137 @@ |
||||
import { reducerTester } from 'test/core/redux/reducerTester'; |
||||
import { dataSourcesReducer, initialState } from './reducers'; |
||||
import { |
||||
dataSourcesLoaded, |
||||
dataSourceLoaded, |
||||
setDataSourcesSearchQuery, |
||||
setDataSourcesLayoutMode, |
||||
dataSourceTypesLoad, |
||||
dataSourceTypesLoaded, |
||||
setDataSourceTypeSearchQuery, |
||||
dataSourceMetaLoaded, |
||||
setDataSourceName, |
||||
setIsDefault, |
||||
} from './actions'; |
||||
import { getMockDataSources, getMockDataSource } from '../__mocks__/dataSourcesMocks'; |
||||
import { LayoutModes } from 'app/core/components/LayoutSelector/LayoutSelector'; |
||||
import { DataSourcesState } from 'app/types'; |
||||
import { PluginMetaInfo } from '@grafana/ui'; |
||||
|
||||
const mockPlugin = () => ({ |
||||
defaultNavUrl: 'defaultNavUrl', |
||||
enabled: true, |
||||
hasUpdate: true, |
||||
id: 'id', |
||||
info: {} as PluginMetaInfo, |
||||
latestVersion: 'latestVersion', |
||||
name: 'name', |
||||
pinned: true, |
||||
state: 'state', |
||||
type: 'type', |
||||
module: {}, |
||||
}); |
||||
|
||||
describe('dataSourcesReducer', () => { |
||||
describe('when dataSourcesLoaded is dispatched', () => { |
||||
it('then state should be correct', () => { |
||||
const dataSources = getMockDataSources(0); |
||||
|
||||
reducerTester() |
||||
.givenReducer(dataSourcesReducer, initialState) |
||||
.whenActionIsDispatched(dataSourcesLoaded(dataSources)) |
||||
.thenStateShouldEqual({ ...initialState, hasFetched: true, dataSources, dataSourcesCount: 1 }); |
||||
}); |
||||
}); |
||||
|
||||
describe('when dataSourceLoaded is dispatched', () => { |
||||
it('then state should be correct', () => { |
||||
const dataSource = getMockDataSource(); |
||||
|
||||
reducerTester() |
||||
.givenReducer(dataSourcesReducer, initialState) |
||||
.whenActionIsDispatched(dataSourceLoaded(dataSource)) |
||||
.thenStateShouldEqual({ ...initialState, dataSource }); |
||||
}); |
||||
}); |
||||
|
||||
describe('when setDataSourcesSearchQuery is dispatched', () => { |
||||
it('then state should be correct', () => { |
||||
reducerTester() |
||||
.givenReducer(dataSourcesReducer, initialState) |
||||
.whenActionIsDispatched(setDataSourcesSearchQuery('some query')) |
||||
.thenStateShouldEqual({ ...initialState, searchQuery: 'some query' }); |
||||
}); |
||||
}); |
||||
|
||||
describe('when setDataSourcesLayoutMode is dispatched', () => { |
||||
it('then state should be correct', () => { |
||||
const layoutMode: LayoutModes = LayoutModes.Grid; |
||||
|
||||
reducerTester() |
||||
.givenReducer(dataSourcesReducer, initialState) |
||||
.whenActionIsDispatched(setDataSourcesLayoutMode(layoutMode)) |
||||
.thenStateShouldEqual({ ...initialState, layoutMode: LayoutModes.Grid }); |
||||
}); |
||||
}); |
||||
|
||||
describe('when dataSourceTypesLoad is dispatched', () => { |
||||
it('then state should be correct', () => { |
||||
const state: DataSourcesState = { ...initialState, dataSourceTypes: [mockPlugin()] }; |
||||
|
||||
reducerTester() |
||||
.givenReducer(dataSourcesReducer, state) |
||||
.whenActionIsDispatched(dataSourceTypesLoad()) |
||||
.thenStateShouldEqual({ ...initialState, dataSourceTypes: [], isLoadingDataSources: true }); |
||||
}); |
||||
}); |
||||
|
||||
describe('when dataSourceTypesLoaded is dispatched', () => { |
||||
it('then state should be correct', () => { |
||||
const dataSourceTypes = [mockPlugin()]; |
||||
const state: DataSourcesState = { ...initialState, isLoadingDataSources: true }; |
||||
|
||||
reducerTester() |
||||
.givenReducer(dataSourcesReducer, state) |
||||
.whenActionIsDispatched(dataSourceTypesLoaded(dataSourceTypes)) |
||||
.thenStateShouldEqual({ ...initialState, dataSourceTypes, isLoadingDataSources: false }); |
||||
}); |
||||
}); |
||||
|
||||
describe('when setDataSourceTypeSearchQuery is dispatched', () => { |
||||
it('then state should be correct', () => { |
||||
reducerTester() |
||||
.givenReducer(dataSourcesReducer, initialState) |
||||
.whenActionIsDispatched(setDataSourceTypeSearchQuery('type search query')) |
||||
.thenStateShouldEqual({ ...initialState, dataSourceTypeSearchQuery: 'type search query' }); |
||||
}); |
||||
}); |
||||
|
||||
describe('when dataSourceMetaLoaded is dispatched', () => { |
||||
it('then state should be correct', () => { |
||||
const dataSourceMeta = mockPlugin(); |
||||
|
||||
reducerTester() |
||||
.givenReducer(dataSourcesReducer, initialState) |
||||
.whenActionIsDispatched(dataSourceMetaLoaded(dataSourceMeta)) |
||||
.thenStateShouldEqual({ ...initialState, dataSourceMeta }); |
||||
}); |
||||
}); |
||||
|
||||
describe('when setDataSourceName is dispatched', () => { |
||||
it('then state should be correct', () => { |
||||
reducerTester() |
||||
.givenReducer(dataSourcesReducer, initialState) |
||||
.whenActionIsDispatched(setDataSourceName('some name')) |
||||
.thenStateShouldEqual({ ...initialState, dataSource: { name: 'some name' } }); |
||||
}); |
||||
}); |
||||
|
||||
describe('when setIsDefault is dispatched', () => { |
||||
it('then state should be correct', () => { |
||||
reducerTester() |
||||
.givenReducer(dataSourcesReducer, initialState) |
||||
.whenActionIsDispatched(setIsDefault(true)) |
||||
.thenStateShouldEqual({ ...initialState, dataSource: { isDefault: true } }); |
||||
}); |
||||
}); |
||||
}); |
@ -1,4 +0,0 @@ |
||||
<div class="panel-solo" ng-if="panel"> |
||||
<plugin-component type="panel"> |
||||
</plugin-component> |
||||
</div> |
@ -1,58 +0,0 @@ |
||||
import angular from 'angular'; |
||||
import locationUtil from 'app/core/utils/location_util'; |
||||
import appEvents from 'app/core/app_events'; |
||||
|
||||
export class SoloPanelCtrl { |
||||
/** @ngInject */ |
||||
constructor($scope, $routeParams, $location, dashboardLoaderSrv, contextSrv, backendSrv) { |
||||
let panelId; |
||||
|
||||
$scope.init = () => { |
||||
contextSrv.sidemenu = false; |
||||
appEvents.emit('toggle-sidemenu-hidden'); |
||||
|
||||
const params = $location.search(); |
||||
panelId = parseInt(params.panelId, 10); |
||||
|
||||
appEvents.on('dashboard-initialized', $scope.initPanelScope); |
||||
|
||||
// if no uid, redirect to new route based on slug
|
||||
if (!($routeParams.type === 'script' || $routeParams.type === 'snapshot') && !$routeParams.uid) { |
||||
backendSrv.getDashboardBySlug($routeParams.slug).then(res => { |
||||
if (res) { |
||||
const url = locationUtil.stripBaseFromUrl(res.meta.url.replace('/d/', '/d-solo/')); |
||||
$location.path(url).replace(); |
||||
} |
||||
}); |
||||
return; |
||||
} |
||||
|
||||
dashboardLoaderSrv.loadDashboard($routeParams.type, $routeParams.slug, $routeParams.uid).then(result => { |
||||
result.meta.soloMode = true; |
||||
$scope.initDashboard(result, $scope); |
||||
}); |
||||
}; |
||||
|
||||
$scope.initPanelScope = () => { |
||||
const panelInfo = $scope.dashboard.getPanelInfoById(panelId); |
||||
|
||||
// fake row ctrl scope
|
||||
$scope.ctrl = { |
||||
dashboard: $scope.dashboard, |
||||
}; |
||||
|
||||
$scope.panel = panelInfo.panel; |
||||
$scope.panel.soloMode = true; |
||||
$scope.$index = 0; |
||||
|
||||
if (!$scope.panel) { |
||||
$scope.appEvent('alert-error', ['Panel not found', '']); |
||||
return; |
||||
} |
||||
}; |
||||
|
||||
$scope.init(); |
||||
} |
||||
} |
||||
|
||||
angular.module('grafana.routes').controller('SoloPanelCtrl', SoloPanelCtrl); |
@ -0,0 +1,80 @@ |
||||
// Libraries
|
||||
import React, { PureComponent } from 'react'; |
||||
|
||||
// Components
|
||||
import { Select, SelectOptionItem } from '@grafana/ui'; |
||||
|
||||
// Types
|
||||
import { QueryEditorProps } from '@grafana/ui/src/types'; |
||||
import { LokiDatasource } from '../datasource'; |
||||
import { LokiQuery } from '../types'; |
||||
import { LokiQueryField } from './LokiQueryField'; |
||||
|
||||
type Props = QueryEditorProps<LokiDatasource, LokiQuery>; |
||||
|
||||
interface State { |
||||
query: LokiQuery; |
||||
} |
||||
|
||||
export class LokiQueryEditor extends PureComponent<Props> { |
||||
state: State = { |
||||
query: this.props.query, |
||||
}; |
||||
|
||||
onRunQuery = () => { |
||||
const { query } = this.state; |
||||
|
||||
this.props.onChange(query); |
||||
this.props.onRunQuery(); |
||||
}; |
||||
|
||||
onFieldChange = (query: LokiQuery, override?) => { |
||||
this.setState({ |
||||
query: { |
||||
...this.state.query, |
||||
expr: query.expr, |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
onFormatChanged = (option: SelectOptionItem) => { |
||||
this.props.onChange({ |
||||
...this.state.query, |
||||
resultFormat: option.value, |
||||
}); |
||||
}; |
||||
|
||||
render() { |
||||
const { query } = this.state; |
||||
const { datasource } = this.props; |
||||
const formatOptions: SelectOptionItem[] = [ |
||||
{ label: 'Time Series', value: 'time_series' }, |
||||
{ label: 'Table', value: 'table' }, |
||||
]; |
||||
|
||||
query.resultFormat = query.resultFormat || 'time_series'; |
||||
const currentFormat = formatOptions.find(item => item.value === query.resultFormat); |
||||
|
||||
return ( |
||||
<div> |
||||
<LokiQueryField |
||||
datasource={datasource} |
||||
initialQuery={query} |
||||
onQueryChange={this.onFieldChange} |
||||
onPressEnter={this.onRunQuery} |
||||
/> |
||||
<div className="gf-form-inline"> |
||||
<div className="gf-form"> |
||||
<div className="gf-form-label">Format as</div> |
||||
<Select isSearchable={false} options={formatOptions} onChange={this.onFormatChanged} value={currentFormat} /> |
||||
</div> |
||||
<div className="gf-form gf-form--grow"> |
||||
<div className="gf-form-label gf-form-label--grow" /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default LokiQueryEditor; |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue