diff --git a/packages/grafana-data/src/types/templateVars.ts b/packages/grafana-data/src/types/templateVars.ts index 1f9e89b5d44..9a116439387 100644 --- a/packages/grafana-data/src/types/templateVars.ts +++ b/packages/grafana-data/src/types/templateVars.ts @@ -1,4 +1,4 @@ -export type VariableType = 'query' | 'adhoc' | 'constant' | 'datasource' | 'interval' | 'textbox' | 'custom'; +export type VariableType = 'query' | 'adhoc' | 'constant' | 'datasource' | 'interval' | 'textbox' | 'custom' | 'system'; export interface VariableModel { type: VariableType; diff --git a/public/app/features/dashboard/components/SubMenu/SubMenu.tsx b/public/app/features/dashboard/components/SubMenu/SubMenu.tsx index f6ead203cd6..b27124f34cc 100644 --- a/public/app/features/dashboard/components/SubMenu/SubMenu.tsx +++ b/public/app/features/dashboard/components/SubMenu/SubMenu.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; import { connect, MapStateToProps } from 'react-redux'; import { StoreState } from '../../../../types'; -import { getVariables } from '../../../variables/state/selectors'; +import { getSubMenuVariables } from '../../../variables/state/selectors'; import { VariableHide, VariableModel } from '../../../variables/types'; import { DashboardModel } from '../../state'; import { DashboardLinks } from './DashboardLinks'; @@ -67,7 +67,7 @@ class SubMenuUnConnected extends PureComponent { } const mapStateToProps: MapStateToProps = state => ({ - variables: getVariables(state, false), + variables: getSubMenuVariables(state), }); export const SubMenu = connect(mapStateToProps)(SubMenuUnConnected); diff --git a/public/app/features/dashboard/state/initDashboard.test.ts b/public/app/features/dashboard/state/initDashboard.test.ts index 8681652d150..9f1fd1d109f 100644 --- a/public/app/features/dashboard/state/initDashboard.test.ts +++ b/public/app/features/dashboard/state/initDashboard.test.ts @@ -1,7 +1,7 @@ import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { initDashboard, InitDashboardArgs } from './initDashboard'; -import { DashboardRouteInfo, DashboardInitPhase } from 'app/types'; +import { DashboardInitPhase, DashboardRouteInfo } from 'app/types'; import { getBackendSrv } from 'app/core/services/backend_srv'; import { dashboardInitCompleted, dashboardInitFetching, dashboardInitServices } from './reducers'; import { updateLocation } from '../../../core/actions'; @@ -184,8 +184,8 @@ describeInitScenario('Initializing new dashboard', ctx => { }); it('Should send action dashboardInitCompleted', () => { - expect(ctx.actions[5].type).toBe(dashboardInitCompleted.type); - expect(ctx.actions[5].payload.title).toBe('New dashboard'); + expect(ctx.actions[8].type).toBe(dashboardInitCompleted.type); + expect(ctx.actions[8].payload.title).toBe('New dashboard'); }); it('Should initialize services', () => { @@ -257,8 +257,8 @@ describeInitScenario('Initializing existing dashboard', ctx => { }); it('Should send action dashboardInitCompleted', () => { - expect(ctx.actions[6].type).toBe(dashboardInitCompleted.type); - expect(ctx.actions[6].payload.title).toBe('My cool dashboard'); + expect(ctx.actions[9].type).toBe(dashboardInitCompleted.type); + expect(ctx.actions[9].payload.title).toBe('My cool dashboard'); }); it('Should initialize services', () => { diff --git a/public/app/features/templating/template_srv.ts b/public/app/features/templating/template_srv.ts index 2852dac0dc7..843c86d198d 100644 --- a/public/app/features/templating/template_srv.ts +++ b/public/app/features/templating/template_srv.ts @@ -240,7 +240,13 @@ export class TemplateSrv implements BaseTemplateSrv { this.grafanaVariables[name] = value; } + /** + * @deprecated: setGlobalVariable function should not be used and will be removed in future releases + * + * Use addVariable action to add variables to Redux instead + */ setGlobalVariable(name: string, variable: any) { + deprecationWarning('template_srv.ts', 'setGlobalVariable', ''); this.index = { ...this.index, [name]: { diff --git a/public/app/features/variables/adapters.ts b/public/app/features/variables/adapters.ts index 9a423eb39a0..8bc05c9af01 100644 --- a/public/app/features/variables/adapters.ts +++ b/public/app/features/variables/adapters.ts @@ -23,6 +23,7 @@ import { createConstantVariableAdapter } from './constant/adapter'; import { createDataSourceVariableAdapter } from './datasource/adapter'; import { createIntervalVariableAdapter } from './interval/adapter'; import { createAdHocVariableAdapter } from './adhoc/adapter'; +import { createSystemVariableAdapter } from './system/adapter'; export interface VariableAdapter { id: VariableType; @@ -58,6 +59,7 @@ export const getDefaultVariableAdapters = () => [ createDataSourceVariableAdapter(), createIntervalVariableAdapter(), createAdHocVariableAdapter(), + createSystemVariableAdapter(), ]; export const variableAdapters: VariableTypeRegistry = new Registry>(); diff --git a/public/app/features/variables/editor/VariableEditorContainer.tsx b/public/app/features/variables/editor/VariableEditorContainer.tsx index 0fff1dc7e2f..65910544868 100644 --- a/public/app/features/variables/editor/VariableEditorContainer.tsx +++ b/public/app/features/variables/editor/VariableEditorContainer.tsx @@ -8,7 +8,7 @@ import { VariableEditorList } from './VariableEditorList'; import { VariableEditorEditor } from './VariableEditorEditor'; import { MapDispatchToProps, MapStateToProps } from 'react-redux'; import { connectWithStore } from '../../../core/utils/connectWithReduxStore'; -import { getVariables } from '../state/selectors'; +import { getEditorVariables } from '../state/selectors'; import { VariableModel } from '../types'; import { switchToEditMode, switchToListMode, switchToNewMode } from './actions'; import { changeVariableOrder, duplicateVariable, removeVariable } from '../state/sharedReducer'; @@ -124,7 +124,7 @@ class VariableEditorContainerUnconnected extends PureComponent { } const mapStateToProps: MapStateToProps = state => ({ - variables: getVariables(state, true), + variables: getEditorVariables(state), idInEditor: state.templating.editor.id, }); diff --git a/public/app/features/variables/state/actions.test.ts b/public/app/features/variables/state/actions.test.ts index 9eeff9226f3..dadfb0b9fc9 100644 --- a/public/app/features/variables/state/actions.test.ts +++ b/public/app/features/variables/state/actions.test.ts @@ -566,14 +566,23 @@ describe('shared actions', () => { .givenRootReducer(getRootReducer()) .whenAsyncActionIsDispatched(initVariablesTransaction(uid, dashboard)); - tester.thenDispatchedActionsShouldEqual( - variablesInitTransaction({ uid }), - addVariable(toVariablePayload(constant, { global: false, index: 0, model: constant })), - addInitLock(toVariablePayload(constant)), - resolveInitLock(toVariablePayload(constant)), - removeInitLock(toVariablePayload(constant)), - variablesCompleteTransaction({ uid }) - ); + tester.thenDispatchedActionsPredicateShouldEqual(dispatchedActions => { + expect(dispatchedActions[0]).toEqual(variablesInitTransaction({ uid })); + expect(dispatchedActions[1]).toEqual( + addVariable(toVariablePayload(constant, { global: false, index: 0, model: constant })) + ); + expect(dispatchedActions[2]).toEqual(addInitLock(toVariablePayload(constant))); + expect(dispatchedActions[3]).toEqual(resolveInitLock(toVariablePayload(constant))); + expect(dispatchedActions[4]).toEqual(removeInitLock(toVariablePayload(constant))); + expect(dispatchedActions[5].type).toEqual(addVariable.type); + expect(dispatchedActions[5].payload.id).toEqual('__dashboard'); + expect(dispatchedActions[6].type).toEqual(addVariable.type); + expect(dispatchedActions[6].payload.id).toEqual('__org'); + expect(dispatchedActions[7].type).toEqual(addVariable.type); + expect(dispatchedActions[7].payload.id).toEqual('__user'); + expect(dispatchedActions[8]).toEqual(variablesCompleteTransaction({ uid })); + return dispatchedActions.length === 9; + }); }); }); @@ -594,16 +603,25 @@ describe('shared actions', () => { .givenRootReducer(getRootReducer()) .whenAsyncActionIsDispatched(initVariablesTransaction(uid, dashboard)); - tester.thenDispatchedActionsShouldEqual( - cleanVariables(), - variablesClearTransaction(), - variablesInitTransaction({ uid }), - addVariable(toVariablePayload(constant, { global: false, index: 0, model: constant })), - addInitLock(toVariablePayload(constant)), - resolveInitLock(toVariablePayload(constant)), - removeInitLock(toVariablePayload(constant)), - variablesCompleteTransaction({ uid }) - ); + tester.thenDispatchedActionsPredicateShouldEqual(dispatchedActions => { + expect(dispatchedActions[0]).toEqual(cleanVariables()); + expect(dispatchedActions[1]).toEqual(variablesClearTransaction()); + expect(dispatchedActions[2]).toEqual(variablesInitTransaction({ uid })); + expect(dispatchedActions[3]).toEqual( + addVariable(toVariablePayload(constant, { global: false, index: 0, model: constant })) + ); + expect(dispatchedActions[4]).toEqual(addInitLock(toVariablePayload(constant))); + expect(dispatchedActions[5]).toEqual(resolveInitLock(toVariablePayload(constant))); + expect(dispatchedActions[6]).toEqual(removeInitLock(toVariablePayload(constant))); + expect(dispatchedActions[7].type).toEqual(addVariable.type); + expect(dispatchedActions[7].payload.id).toEqual('__dashboard'); + expect(dispatchedActions[8].type).toEqual(addVariable.type); + expect(dispatchedActions[8].payload.id).toEqual('__org'); + expect(dispatchedActions[9].type).toEqual(addVariable.type); + expect(dispatchedActions[9].payload.id).toEqual('__user'); + expect(dispatchedActions[10]).toEqual(variablesCompleteTransaction({ uid })); + return dispatchedActions.length === 11; + }); }); }); }); diff --git a/public/app/features/variables/state/actions.ts b/public/app/features/variables/state/actions.ts index 2ac74fb748e..6e49bfe7c97 100644 --- a/public/app/features/variables/state/actions.ts +++ b/public/app/features/variables/state/actions.ts @@ -3,7 +3,11 @@ import { AppEvents, TimeRange, UrlQueryMap, UrlQueryValue } from '@grafana/data' import angular from 'angular'; import { + DashboardVariableModel, + OrgVariableModel, QueryVariableModel, + UserVariableModel, + VariableHide, VariableModel, VariableOption, VariableRefresh, @@ -94,33 +98,77 @@ export const initDashboardTemplating = (list: VariableModel[]): ThunkResult => { return (dispatch, getState) => { - templateSrv.setGlobalVariable('__dashboard', { - value: { - name: dashboard.title, - uid: dashboard.uid, - toString: function() { - return this.uid; + const dashboardModel: DashboardVariableModel = { + id: '__dashboard', + name: '__dashboard', + label: null, + type: 'system', + index: -3, + skipUrlSync: true, + hide: VariableHide.hideVariable, + global: false, + current: { + value: { + name: dashboard.title, + uid: dashboard.uid, + toString: () => dashboard.title, }, }, - }); - templateSrv.setGlobalVariable('__org', { - value: { - name: contextSrv.user.orgName, - id: contextSrv.user.orgId, - toString: function() { - return this.id; + }; + + dispatch( + addVariable( + toVariablePayload(dashboardModel, { + global: dashboardModel.global, + index: dashboardModel.index, + model: dashboardModel, + }) + ) + ); + + const orgModel: OrgVariableModel = { + id: '__org', + name: '__org', + label: null, + type: 'system', + index: -2, + skipUrlSync: true, + hide: VariableHide.hideVariable, + global: false, + current: { + value: { + name: contextSrv.user.orgName, + id: contextSrv.user.orgId, + toString: () => contextSrv.user.orgId.toString(), }, }, - }); - templateSrv.setGlobalVariable('__user', { - value: { - login: contextSrv.user.login, - id: contextSrv.user.id, - toString: function() { - return this.id; + }; + + dispatch( + addVariable(toVariablePayload(orgModel, { global: orgModel.global, index: orgModel.index, model: orgModel })) + ); + + const userModel: UserVariableModel = { + id: '__user', + name: '__user', + label: null, + type: 'system', + index: -1, + skipUrlSync: true, + hide: VariableHide.hideVariable, + global: false, + current: { + value: { + login: contextSrv.user.login, + id: contextSrv.user.id, + toString: () => contextSrv.user.id.toString(), }, }, - }); + }; + + dispatch( + addVariable(toVariablePayload(userModel, { global: userModel.global, index: userModel.index, model: userModel })) + ); }; }; diff --git a/public/app/features/variables/state/selectors.ts b/public/app/features/variables/state/selectors.ts index 4df3a53ccbe..613884560a7 100644 --- a/public/app/features/variables/state/selectors.ts +++ b/public/app/features/variables/state/selectors.ts @@ -29,7 +29,25 @@ export const getVariableWithName = (name: string, state: StoreState = getState() }; export const getVariables = (state: StoreState = getState(), includeNewVariable = false): VariableModel[] => { - return getFilteredVariables(variable => (includeNewVariable ? true : variable.id !== NEW_VARIABLE_ID), state); + const filter = (variable: VariableModel) => { + if (variable.type === 'system') { + return false; + } + if (includeNewVariable) { + return true; + } + return variable.id !== NEW_VARIABLE_ID; + }; + + return getFilteredVariables(filter, state); +}; + +export const getSubMenuVariables = (state: StoreState): VariableModel[] => { + return getVariables(state); +}; + +export const getEditorVariables = (state: StoreState): VariableModel[] => { + return getVariables(state, true); }; export type GetVariables = typeof getVariables; diff --git a/public/app/features/variables/system/adapter.ts b/public/app/features/variables/system/adapter.ts new file mode 100644 index 00000000000..bae30da4680 --- /dev/null +++ b/public/app/features/variables/system/adapter.ts @@ -0,0 +1,48 @@ +import { ComponentType } from 'react'; +import { SystemVariable, VariableHide } from '../types'; +import { VariableAdapter } from '../adapters'; +import { NEW_VARIABLE_ID } from '../state/types'; +import { Deferred } from '../../../core/utils/deferred'; +import { VariablePickerProps } from '../pickers/types'; +import { VariableEditorProps } from '../editor/types'; + +export const createSystemVariableAdapter = (): VariableAdapter> => { + return { + id: 'system', + description: '', + name: 'system', + initialState: { + id: NEW_VARIABLE_ID, + global: false, + type: 'system', + name: '', + label: (null as unknown) as string, + hide: VariableHide.hideVariable, + skipUrlSync: true, + current: { value: { toString: () => '' } }, + index: -1, + initLock: (null as unknown) as Deferred, + }, + reducer: (state: any, action: any) => state, + picker: (null as unknown) as ComponentType, + editor: (null as unknown) as ComponentType, + dependsOn: () => { + return false; + }, + setValue: async (variable, option, emitChanges = false) => { + return; + }, + setValueFromUrl: async (variable, urlValue) => { + return; + }, + updateOptions: async variable => { + return; + }, + getSaveModel: variable => { + return {}; + }, + getValueForUrl: variable => { + return ''; + }, + }; +}; diff --git a/public/app/features/variables/types.ts b/public/app/features/variables/types.ts index daf580dc66b..6d1fc1b1cb5 100644 --- a/public/app/features/variables/types.ts +++ b/public/app/features/variables/types.ts @@ -91,6 +91,34 @@ export interface VariableWithOptions extends VariableModel { query: string; } +export interface DashboardProps { + name: string; + uid: string; + toString: () => string; +} + +export interface DashboardVariableModel extends SystemVariable {} + +export interface OrgProps { + name: string; + id: number; + toString: () => string; +} + +export interface OrgVariableModel extends SystemVariable {} + +export interface UserProps { + login: string; + id: number; + toString: () => string; +} + +export interface UserVariableModel extends SystemVariable {} + +export interface SystemVariable string }> extends VariableModel { + current: { value: TProps }; +} + export interface VariableModel extends BaseVariableModel { id: string; global: boolean;