Types: Adds type safety to appEvents (#19418)

* Types: Add type safety to appEvents
This commit is contained in:
kay delaney 2019-10-14 09:27:47 +01:00 committed by GitHub
parent e7c37cc316
commit 99411bf37a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
138 changed files with 991 additions and 508 deletions

View File

@ -0,0 +1,4 @@
export interface AppEvent<T> {
readonly name: string;
payload?: T;
}

View File

@ -0,0 +1,7 @@
import { eventFactory } from './utils';
export type AlertPayload = [string, string?];
export const alertSuccess = eventFactory<AlertPayload>('alert-success');
export const alertWarning = eventFactory<AlertPayload>('alert-warning');
export const alertError = eventFactory<AlertPayload>('alert-error');

View File

@ -13,3 +13,7 @@ export * from './graph';
export * from './ScopedVars'; export * from './ScopedVars';
export * from './transformations'; export * from './transformations';
export * from './vector'; export * from './vector';
export * from './appEvent';
import * as AppEvents from './events';
export { AppEvents };

View File

@ -1,2 +1,14 @@
import { AppEvent } from './appEvent';
export type Omit<T, K> = Pick<T, Exclude<keyof T, K>>; export type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
export type Subtract<T, K> = Omit<T, keyof K>; export type Subtract<T, K> = Omit<T, keyof K>;
const typeList: Set<string> = new Set();
export function eventFactory<T = undefined>(name: string): AppEvent<T> {
if (typeList.has(name)) {
throw new Error(`There is already an event defined with type '${name}'`);
}
typeList.add(name);
return { name };
}

View File

@ -0,0 +1,34 @@
import { eventFactory } from '@grafana/data';
import { DataQueryResponseData, DataQueryError } from '.';
/** Payloads */
export interface PanelChangeViewPayload {
fullscreen?: boolean;
edit?: boolean;
panelId?: number;
toggle?: boolean;
}
export interface MenuElement {
text: string;
click: string;
role?: string;
shortcut?: string;
}
/** Events */
export const refresh = eventFactory('refresh');
export const componentDidMount = eventFactory('component-did-mount');
export const dataError = eventFactory<DataQueryError>('data-error');
export const dataReceived = eventFactory<DataQueryResponseData[]>('data-received');
export const dataSnapshotLoad = eventFactory<DataQueryResponseData[]>('data-snapshot-load');
export const editModeInitialized = eventFactory('init-edit-mode');
export const initPanelActions = eventFactory<MenuElement[]>('init-panel-actions');
export const panelChangeView = eventFactory<PanelChangeViewPayload>('panel-change-view');
export const panelInitialized = eventFactory('panel-initialized');
export const panelSizeChanged = eventFactory('panel-size-changed');
export const panelTeardown = eventFactory('panel-teardown');
export const render = eventFactory<any>('render');
export const viewModeChanged = eventFactory('view-mode-changed');

View File

@ -4,3 +4,6 @@ export * from './app';
export * from './datasource'; export * from './datasource';
export * from './theme'; export * from './theme';
export * from './input'; export * from './input';
import * as PanelEvents from './events';
export { PanelEvents };

View File

@ -4,11 +4,13 @@ import AppNotificationItem from './AppNotificationItem';
import { notifyApp, clearAppNotification } from 'app/core/actions'; import { notifyApp, clearAppNotification } from 'app/core/actions';
import { connectWithStore } from 'app/core/utils/connectWithReduxStore'; import { connectWithStore } from 'app/core/utils/connectWithReduxStore';
import { AppNotification, StoreState } from 'app/types'; import { AppNotification, StoreState } from 'app/types';
import { import {
createErrorNotification, createErrorNotification,
createSuccessNotification, createSuccessNotification,
createWarningNotification, createWarningNotification,
} from '../../copy/appNotification'; } from '../../copy/appNotification';
import { AppEvents } from '@grafana/data';
export interface Props { export interface Props {
appNotifications: AppNotification[]; appNotifications: AppNotification[];
@ -20,9 +22,9 @@ export class AppNotificationList extends PureComponent<Props> {
componentDidMount() { componentDidMount() {
const { notifyApp } = this.props; const { notifyApp } = this.props;
appEvents.on('alert-warning', (options: string[]) => notifyApp(createWarningNotification(options[0], options[1]))); appEvents.on(AppEvents.alertWarning, payload => notifyApp(createWarningNotification(...payload)));
appEvents.on('alert-success', (options: string[]) => notifyApp(createSuccessNotification(options[0], options[1]))); appEvents.on(AppEvents.alertSuccess, payload => notifyApp(createSuccessNotification(...payload)));
appEvents.on('alert-error', (options: string[]) => notifyApp(createErrorNotification(options[0], options[1]))); appEvents.on(AppEvents.alertError, payload => notifyApp(createErrorNotification(...payload)));
} }
onClearAppNotification = (id: number) => { onClearAppNotification = (id: number) => {

View File

@ -1,6 +1,7 @@
import React, { PureComponent, SyntheticEvent, ChangeEvent } from 'react'; import React, { PureComponent, SyntheticEvent, ChangeEvent } from 'react';
import { Tooltip } from '@grafana/ui'; import { Tooltip } from '@grafana/ui';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { AppEvents } from '@grafana/data';
interface Props { interface Props {
onSubmit: (pw: string) => void; onSubmit: (pw: string) => void;
@ -42,7 +43,7 @@ export class ChangePassword extends PureComponent<Props, State> {
if (valid) { if (valid) {
this.props.onSubmit(newPassword); this.props.onSubmit(newPassword);
} else { } else {
appEvents.emit('alert-warning', ['New passwords do not match', '']); appEvents.emit(AppEvents.alertWarning, ['New passwords do not match']);
} }
}; };

View File

@ -8,6 +8,7 @@ import { PureComponent } from 'react';
import { getBackendSrv } from '@grafana/runtime'; import { getBackendSrv } from '@grafana/runtime';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { AppEvents } from '@grafana/data';
const isOauthEnabled = () => { const isOauthEnabled = () => {
return !!config.oauth && Object.keys(config.oauth).length > 0; return !!config.oauth && Object.keys(config.oauth).length > 0;
@ -52,7 +53,7 @@ export class LoginCtrl extends PureComponent<Props, State> {
}; };
if (config.loginError) { if (config.loginError) {
appEvents.emit('alert-warning', ['Login Failed', config.loginError]); appEvents.emit(AppEvents.alertWarning, ['Login Failed', config.loginError]);
} }
} }

View File

@ -2,6 +2,7 @@ import React, { FormEvent } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { NavModel, NavModelItem, NavModelBreadcrumb } from '@grafana/data'; import { NavModel, NavModelItem, NavModelBreadcrumb } from '@grafana/data';
import { CoreEvents } from 'app/types';
export interface Props { export interface Props {
model: NavModel; model: NavModel;
@ -15,7 +16,7 @@ const SelectNav = ({ main, customCss }: { main: NavModelItem; customCss: string
const gotoUrl = (evt: FormEvent) => { const gotoUrl = (evt: FormEvent) => {
const element = evt.target as HTMLSelectElement; const element = evt.target as HTMLSelectElement;
const url = element.options[element.selectedIndex].value; const url = element.options[element.selectedIndex].value;
appEvents.emit('location-change', { href: url }); appEvents.emit(CoreEvents.locationChange, { href: url });
}; };
return ( return (

View File

@ -1,5 +1,7 @@
import store from 'app/core/store'; import store from 'app/core/store';
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { CoreEvents } from 'app/types';
const template = ` const template = `
<div class="layout-selector"> <div class="layout-selector">
@ -16,20 +18,20 @@ export class LayoutSelectorCtrl {
mode: string; mode: string;
/** @ngInject */ /** @ngInject */
constructor(private $rootScope: any) { constructor(private $rootScope: GrafanaRootScope) {
this.mode = store.get('grafana.list.layout.mode') || 'grid'; this.mode = store.get('grafana.list.layout.mode') || 'grid';
} }
listView() { listView() {
this.mode = 'list'; this.mode = 'list';
store.set('grafana.list.layout.mode', 'list'); store.set('grafana.list.layout.mode', 'list');
this.$rootScope.appEvent('layout-mode-changed', 'list'); this.$rootScope.appEvent(CoreEvents.layoutModeChanged, 'list');
} }
gridView() { gridView() {
this.mode = 'grid'; this.mode = 'grid';
store.set('grafana.list.layout.mode', 'grid'); store.set('grafana.list.layout.mode', 'grid');
this.$rootScope.appEvent('layout-mode-changed', 'grid'); this.$rootScope.appEvent(CoreEvents.layoutModeChanged, 'grid');
} }
} }
@ -46,7 +48,7 @@ export function layoutSelector() {
} }
/** @ngInject */ /** @ngInject */
export function layoutMode($rootScope: any) { export function layoutMode($rootScope: GrafanaRootScope) {
return { return {
restrict: 'A', restrict: 'A',
scope: {}, scope: {},
@ -56,7 +58,7 @@ export function layoutMode($rootScope: any) {
elem.addClass(className); elem.addClass(className);
$rootScope.onAppEvent( $rootScope.onAppEvent(
'layout-mode-changed', CoreEvents.layoutModeChanged,
(evt: any, newLayout: any) => { (evt: any, newLayout: any) => {
elem.removeClass(className); elem.removeClass(className);
className = 'card-list-layout-' + newLayout; className = 'card-list-layout-' + newLayout;

View File

@ -5,6 +5,7 @@ import { SearchSrv } from 'app/core/services/search_srv';
import { BackendSrv } from 'app/core/services/backend_srv'; import { BackendSrv } from 'app/core/services/backend_srv';
import { NavModelSrv } from 'app/core/nav_model_srv'; import { NavModelSrv } from 'app/core/nav_model_srv';
import { ContextSrv } from 'app/core/services/context_srv'; import { ContextSrv } from 'app/core/services/context_srv';
import { CoreEvents } from 'app/types';
export interface Section { export interface Section {
id: number; id: number;
@ -200,7 +201,7 @@ export class ManageDashboardsCtrl {
text += `selected dashboard${dashCount === 1 ? '' : 's'}?`; text += `selected dashboard${dashCount === 1 ? '' : 's'}?`;
} }
appEvents.emit('confirm-modal', { appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Delete', title: 'Delete',
text: text, text: text,
text2: text2, text2: text2,
@ -236,7 +237,7 @@ export class ManageDashboardsCtrl {
'<move-to-folder-modal dismiss="dismiss()" ' + '<move-to-folder-modal dismiss="dismiss()" ' +
'dashboards="model.dashboards" after-save="model.afterSave()">' + 'dashboards="model.dashboards" after-save="model.afterSave()">' +
'</move-to-folder-modal>'; '</move-to-folder-modal>';
appEvents.emit('show-modal', { appEvents.emit(CoreEvents.showModal, {
templateHtml: template, templateHtml: template,
modalClass: 'modal--narrow', modalClass: 'modal--narrow',
model: { model: {

View File

@ -6,6 +6,7 @@ import { contextSrv } from 'app/core/services/context_srv';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { parse, SearchParserOptions, SearchParserResult } from 'search-query-parser'; import { parse, SearchParserOptions, SearchParserResult } from 'search-query-parser';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { CoreEvents } from 'app/types';
export interface SearchQuery { export interface SearchQuery {
query: string; query: string;
@ -60,9 +61,9 @@ export class SearchCtrl {
/** @ngInject */ /** @ngInject */
constructor($scope: any, private $location: any, private $timeout: any, private searchSrv: SearchSrv) { constructor($scope: any, private $location: any, private $timeout: any, private searchSrv: SearchSrv) {
appEvents.on('show-dash-search', this.openSearch.bind(this), $scope); appEvents.on(CoreEvents.showDashSearch, this.openSearch.bind(this), $scope);
appEvents.on('hide-dash-search', this.closeSearch.bind(this), $scope); appEvents.on(CoreEvents.hideDashSearch, this.closeSearch.bind(this), $scope);
appEvents.on('search-query', debounce(this.search.bind(this), 500), $scope); appEvents.on(CoreEvents.searchQuery, debounce(this.search.bind(this), 500), $scope);
this.initialFolderFilterTitle = 'All'; this.initialFolderFilterTitle = 'All';
this.isEditor = contextSrv.isEditor; this.isEditor = contextSrv.isEditor;
@ -95,7 +96,7 @@ export class SearchCtrl {
} else { } else {
this.query = query; this.query = query;
} }
appEvents.emit('search-query'); appEvents.emit(CoreEvents.searchQuery);
} }
openSearch(payload: OpenSearchParams = {}) { openSearch(payload: OpenSearchParams = {}) {

View File

@ -1,6 +1,7 @@
import _ from 'lodash'; import _ from 'lodash';
import coreModule from '../../core_module'; import coreModule from '../../core_module';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { CoreEvents } from 'app/types';
export class SearchResultsCtrl { export class SearchResultsCtrl {
results: any; results: any;
@ -65,7 +66,7 @@ export class SearchResultsCtrl {
onItemClick(item: any) { onItemClick(item: any) {
//Check if one string can be found in the other //Check if one string can be found in the other
if (this.$location.path().indexOf(item.url) > -1 || item.url.indexOf(this.$location.path()) > -1) { if (this.$location.path().indexOf(item.url) > -1 || item.url.indexOf(this.$location.path()) > -1) {
appEvents.emit('hide-dash-search'); appEvents.emit(CoreEvents.hideDashSearch);
} }
} }

View File

@ -2,6 +2,7 @@ import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import BottomNavLinks from './BottomNavLinks'; import BottomNavLinks from './BottomNavLinks';
import appEvents from '../../app_events'; import appEvents from '../../app_events';
import { CoreEvents } from 'app/types';
jest.mock('../../app_events', () => ({ jest.mock('../../app_events', () => ({
emit: jest.fn(), emit: jest.fn(),
@ -93,7 +94,7 @@ describe('Functions', () => {
const instance = wrapper.instance() as BottomNavLinks; const instance = wrapper.instance() as BottomNavLinks;
instance.itemClicked(mockEvent as any, child); instance.itemClicked(mockEvent as any, child);
expect(appEvents.emit).toHaveBeenCalledWith('show-modal', { templateHtml: '<help-modal></help-modal>' }); expect(appEvents.emit).toHaveBeenCalledWith(CoreEvents.showModal, { templateHtml: '<help-modal></help-modal>' });
}); });
}); });
}); });

View File

@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
import appEvents from '../../app_events'; import appEvents from '../../app_events';
import { User } from '../../services/context_srv'; import { User } from '../../services/context_srv';
import { NavModelItem } from '@grafana/data'; import { NavModelItem } from '@grafana/data';
import { CoreEvents } from 'app/types';
export interface Props { export interface Props {
link: NavModelItem; link: NavModelItem;
@ -12,14 +13,14 @@ class BottomNavLinks extends PureComponent<Props> {
itemClicked = (event: React.SyntheticEvent, child: NavModelItem) => { itemClicked = (event: React.SyntheticEvent, child: NavModelItem) => {
if (child.url === '/shortcuts') { if (child.url === '/shortcuts') {
event.preventDefault(); event.preventDefault();
appEvents.emit('show-modal', { appEvents.emit(CoreEvents.showModal, {
templateHtml: '<help-modal></help-modal>', templateHtml: '<help-modal></help-modal>',
}); });
} }
}; };
switchOrg = () => { switchOrg = () => {
appEvents.emit('show-modal', { appEvents.emit(CoreEvents.showModal, {
templateHtml: '<org-switcher dismiss="dismiss()"></org-switcher>', templateHtml: '<org-switcher dismiss="dismiss()"></org-switcher>',
}); });
}; };

View File

@ -2,6 +2,7 @@ import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { SideMenu } from './SideMenu'; import { SideMenu } from './SideMenu';
import appEvents from '../../app_events'; import appEvents from '../../app_events';
import { CoreEvents } from 'app/types';
jest.mock('../../app_events', () => ({ jest.mock('../../app_events', () => ({
emit: jest.fn(), emit: jest.fn(),
@ -58,7 +59,7 @@ describe('Functions', () => {
instance.toggleSideMenuSmallBreakpoint(); instance.toggleSideMenuSmallBreakpoint();
it('should emit toggle sidemenu event', () => { it('should emit toggle sidemenu event', () => {
expect(appEvents.emit).toHaveBeenCalledWith('toggle-sidemenu-mobile'); expect(appEvents.emit).toHaveBeenCalledWith(CoreEvents.toggleSidemenuMobile);
}); });
}); });
}); });

View File

@ -3,12 +3,13 @@ import appEvents from '../../app_events';
import TopSection from './TopSection'; import TopSection from './TopSection';
import BottomSection from './BottomSection'; import BottomSection from './BottomSection';
import config from 'app/core/config'; import config from 'app/core/config';
import { CoreEvents } from 'app/types';
const homeUrl = config.appSubUrl || '/'; const homeUrl = config.appSubUrl || '/';
export class SideMenu extends PureComponent { export class SideMenu extends PureComponent {
toggleSideMenuSmallBreakpoint = () => { toggleSideMenuSmallBreakpoint = () => {
appEvents.emit('toggle-sidemenu-mobile'); appEvents.emit(CoreEvents.toggleSidemenuMobile);
}; };
render() { render() {

View File

@ -1,6 +1,7 @@
import config from 'app/core/config'; import config from 'app/core/config';
import coreModule from '../core_module'; import coreModule from '../core_module';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { CoreEvents } from 'app/types';
export class ErrorCtrl { export class ErrorCtrl {
/** @ngInject */ /** @ngInject */
@ -9,12 +10,12 @@ export class ErrorCtrl {
$scope.appSubUrl = config.appSubUrl; $scope.appSubUrl = config.appSubUrl;
if (!contextSrv.isSignedIn) { if (!contextSrv.isSignedIn) {
appEvents.emit('toggle-sidemenu-hidden'); appEvents.emit(CoreEvents.toggleSidemenuHidden);
} }
$scope.$on('destroy', () => { $scope.$on('destroy', () => {
if (!contextSrv.isSignedIn) { if (!contextSrv.isSignedIn) {
appEvents.emit('toggle-sidemenu-hidden'); appEvents.emit(CoreEvents.toggleSidemenuHidden);
} }
}); });
} }

View File

@ -1,6 +1,7 @@
import coreModule from '../core_module'; import coreModule from '../core_module';
import config from 'app/core/config'; import config from 'app/core/config';
import { BackendSrv } from '../services/backend_srv'; import { BackendSrv } from '../services/backend_srv';
import { AppEvents } from '@grafana/data';
export class ResetPasswordCtrl { export class ResetPasswordCtrl {
/** @ngInject */ /** @ngInject */
@ -41,7 +42,7 @@ export class ResetPasswordCtrl {
} }
if ($scope.formModel.newPassword !== $scope.formModel.confirmPassword) { if ($scope.formModel.newPassword !== $scope.formModel.confirmPassword) {
$scope.appEvent('alert-warning', ['New passwords do not match', '']); $scope.appEvent(AppEvents.alertWarning, ['New passwords do not match']);
return; return;
} }

View File

@ -25,14 +25,14 @@ const defaultErrorNotification = {
timeout: AppNotificationTimeout.Error, timeout: AppNotificationTimeout.Error,
}; };
export const createSuccessNotification = (title: string, text?: string): AppNotification => ({ export const createSuccessNotification = (title: string, text = ''): AppNotification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
title: title, title: title,
text: text, text: text,
id: Date.now(), id: Date.now(),
}); });
export const createErrorNotification = (title: string, text?: any): AppNotification => { export const createErrorNotification = (title: string, text = ''): AppNotification => {
return { return {
...defaultErrorNotification, ...defaultErrorNotification,
title: title, title: title,
@ -41,7 +41,7 @@ export const createErrorNotification = (title: string, text?: any): AppNotificat
}; };
}; };
export const createWarningNotification = (title: string, text?: string): AppNotification => ({ export const createWarningNotification = (title: string, text = ''): AppNotification => ({
...defaultWarningNotification, ...defaultWarningNotification,
title: title, title: title,
text: text, text: text,

View File

@ -1,14 +1,16 @@
import angular from 'angular'; import angular from 'angular';
import coreModule from '../core_module'; import coreModule from '../core_module';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { CoreEvents } from 'app/types';
export class DeltaCtrl { export class DeltaCtrl {
observer: any; observer: any;
/** @ngInject */ /** @ngInject */
constructor(private $rootScope: any) { constructor(private $rootScope: GrafanaRootScope) {
const waitForCompile = (mutations: any) => { const waitForCompile = (mutations: any) => {
if (mutations.length === 1) { if (mutations.length === 1) {
this.$rootScope.appEvent('json-diff-ready'); this.$rootScope.appEvent(CoreEvents.jsonDiffReady);
} }
}; };
@ -42,7 +44,7 @@ coreModule.directive('diffDelta', delta);
// Link to JSON line number // Link to JSON line number
export class LinkJSONCtrl { export class LinkJSONCtrl {
/** @ngInject */ /** @ngInject */
constructor(private $scope: any, private $rootScope: any, private $anchorScroll: any) {} constructor(private $scope: any, private $rootScope: GrafanaRootScope, private $anchorScroll: any) {}
goToLine(line: number) { goToLine(line: number) {
let unbind: () => void; let unbind: () => void;

View File

@ -3,6 +3,7 @@ import Clipboard from 'clipboard';
import coreModule from '../core_module'; import coreModule from '../core_module';
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';
import { appEvents } from 'app/core/core'; import { appEvents } from 'app/core/core';
import { AppEvents } from '@grafana/data';
/** @ngInject */ /** @ngInject */
function tip($compile: any) { function tip($compile: any) {
@ -34,7 +35,7 @@ function clipboardButton() {
}); });
scope.clipboard.on('success', () => { scope.clipboard.on('success', () => {
appEvents.emit('alert-success', ['Content copied to clipboard']); appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']);
}); });
scope.$on('$destroy', () => { scope.$on('$destroy', () => {

View File

@ -1,6 +1,7 @@
import angular from 'angular'; import angular from 'angular';
import _ from 'lodash'; import _ from 'lodash';
import coreModule from '../core_module'; import coreModule from '../core_module';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
export class ValueSelectDropdownCtrl { export class ValueSelectDropdownCtrl {
dropdownVisible: any; dropdownVisible: any;
@ -245,7 +246,7 @@ export class ValueSelectDropdownCtrl {
} }
/** @ngInject */ /** @ngInject */
export function valueSelectDropdown($compile: any, $window: any, $timeout: any, $rootScope: any) { export function valueSelectDropdown($compile: any, $window: any, $timeout: any, $rootScope: GrafanaRootScope) {
return { return {
scope: { dashboard: '=', variable: '=', onUpdated: '&' }, scope: { dashboard: '=', variable: '=', onUpdated: '&' },
templateUrl: 'public/app/partials/valueSelectDropdown.html', templateUrl: 'public/app/partials/valueSelectDropdown.html',

View File

@ -1,10 +1,12 @@
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
export class Profiler { export class Profiler {
panelsRendered: number; panelsRendered: number;
enabled: boolean; enabled: boolean;
$rootScope: any; $rootScope: GrafanaRootScope;
window: any; window: any;
init(config: any, $rootScope: any) { init(config: any, $rootScope: GrafanaRootScope) {
this.$rootScope = $rootScope; this.$rootScope = $rootScope;
this.window = window; this.window = window;

View File

@ -7,10 +7,11 @@ import {
AngularLoader as AngularLoaderInterface, AngularLoader as AngularLoaderInterface,
setAngularLoader as setAngularLoaderInterface, setAngularLoader as setAngularLoaderInterface,
} from '@grafana/runtime'; } from '@grafana/runtime';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
export class AngularLoader implements AngularLoaderInterface { export class AngularLoader implements AngularLoaderInterface {
/** @ngInject */ /** @ngInject */
constructor(private $compile: any, private $rootScope: any) {} constructor(private $compile: any, private $rootScope: GrafanaRootScope) {}
load(elem: any, scopeProps: any, template: string): AngularComponent { load(elem: any, scopeProps: any, template: string): AngularComponent {
const scope = this.$rootScope.$new(); const scope = this.$rootScope.$new();

View File

@ -1,10 +1,11 @@
import $ from 'jquery'; import $ from 'jquery';
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import config from 'app/core/config'; import config from 'app/core/config';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
export class Analytics { export class Analytics {
/** @ngInject */ /** @ngInject */
constructor(private $rootScope: any, private $location: any) {} constructor(private $rootScope: GrafanaRootScope, private $location: any) {}
gaInit() { gaInit() {
$.ajax({ $.ajax({

View File

@ -6,8 +6,9 @@ import config from 'app/core/config';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
import { DashboardSearchHit } from 'app/types/search'; import { DashboardSearchHit } from 'app/types/search';
import { ContextSrv } from './context_srv'; import { ContextSrv } from './context_srv';
import { FolderInfo, DashboardDTO } from 'app/types'; import { FolderInfo, DashboardDTO, CoreEvents } from 'app/types';
import { BackendSrv as BackendService, getBackendSrv as getBackendService, BackendSrvRequest } from '@grafana/runtime'; import { BackendSrv as BackendService, getBackendSrv as getBackendService, BackendSrvRequest } from '@grafana/runtime';
import { AppEvents } from '@grafana/data';
export class BackendSrv implements BackendService { export class BackendSrv implements BackendService {
private inFlightRequests: { [key: string]: Array<angular.IDeferred<any>> } = {}; private inFlightRequests: { [key: string]: Array<angular.IDeferred<any>> } = {};
@ -60,16 +61,10 @@ export class BackendSrv implements BackendService {
} }
if (err.status === 422) { if (err.status === 422) {
appEvents.emit('alert-warning', ['Validation failed', data.message]); appEvents.emit(AppEvents.alertWarning, ['Validation failed', data.message]);
throw data; throw data;
} }
let severity = 'error';
if (err.status < 500) {
severity = 'warning';
}
if (data.message) { if (data.message) {
let description = ''; let description = '';
let message = data.message; let message = data.message;
@ -78,7 +73,7 @@ export class BackendSrv implements BackendService {
message = 'Error'; message = 'Error';
} }
appEvents.emit('alert-' + severity, [message, description]); appEvents.emit(err.status < 500 ? AppEvents.alertWarning : AppEvents.alertError, [message, description]);
} }
throw data; throw data;
@ -105,7 +100,7 @@ export class BackendSrv implements BackendService {
if (options.method !== 'GET') { if (options.method !== 'GET') {
if (results && results.data.message) { if (results && results.data.message) {
if (options.showSuccessAlert !== false) { if (options.showSuccessAlert !== false) {
appEvents.emit('alert-success', [results.data.message]); appEvents.emit(AppEvents.alertSuccess, [results.data.message]);
} }
} }
} }
@ -192,7 +187,7 @@ export class BackendSrv implements BackendService {
return this.$http(options) return this.$http(options)
.then((response: any) => { .then((response: any) => {
if (!options.silent) { if (!options.silent) {
appEvents.emit('ds-request-response', response); appEvents.emit(CoreEvents.dsRequestResponse, response);
} }
return response; return response;
}) })
@ -232,7 +227,7 @@ export class BackendSrv implements BackendService {
err.data.message = err.data.error; err.data.message = err.data.error;
} }
if (!options.silent) { if (!options.silent) {
appEvents.emit('ds-request-error', err); appEvents.emit(CoreEvents.dsRequestError, err);
} }
throw err; throw err;
}) })

View File

@ -3,7 +3,9 @@ import appEvents from 'app/core/app_events';
import { store } from 'app/store/store'; import { store } from 'app/store/store';
import locationUtil from 'app/core/utils/location_util'; import locationUtil from 'app/core/utils/location_util';
import { updateLocation } from 'app/core/actions'; import { updateLocation } from 'app/core/actions';
import { ITimeoutService, ILocationService, IWindowService, IRootScopeService } from 'angular'; import { ITimeoutService, ILocationService, IWindowService } from 'angular';
import { CoreEvents } from 'app/types';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
// Services that handles angular -> redux store sync & other react <-> angular sync // Services that handles angular -> redux store sync & other react <-> angular sync
export class BridgeSrv { export class BridgeSrv {
@ -14,7 +16,7 @@ export class BridgeSrv {
private $location: ILocationService, private $location: ILocationService,
private $timeout: ITimeoutService, private $timeout: ITimeoutService,
private $window: IWindowService, private $window: IWindowService,
private $rootScope: IRootScopeService, private $rootScope: GrafanaRootScope,
private $route: any private $route: any
) { ) {
this.fullPageReloadRoutes = ['/logout']; this.fullPageReloadRoutes = ['/logout'];
@ -62,7 +64,7 @@ export class BridgeSrv {
} }
}); });
appEvents.on('location-change', (payload: any) => { appEvents.on(CoreEvents.locationChange, payload => {
const urlWithoutBase = locationUtil.stripBaseFromUrl(payload.href); const urlWithoutBase = locationUtil.stripBaseFromUrl(payload.href);
if (this.fullPageReloadRoutes.indexOf(urlWithoutBase) > -1) { if (this.fullPageReloadRoutes.indexOf(urlWithoutBase) > -1) {
this.$window.location.href = payload.href; this.$window.location.href = payload.href;

View File

@ -5,11 +5,14 @@ import appEvents from 'app/core/app_events';
import { getExploreUrl } from 'app/core/utils/explore'; import { getExploreUrl } from 'app/core/utils/explore';
import locationUtil from 'app/core/utils/location_util'; import locationUtil from 'app/core/utils/location_util';
import { store } from 'app/store/store'; import { store } from 'app/store/store';
import { CoreEvents, AppEventEmitter } from 'app/types';
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import { PanelEvents } from '@grafana/ui';
import 'mousetrap-global-bind'; import 'mousetrap-global-bind';
import { ContextSrv } from './context_srv'; import { ContextSrv } from './context_srv';
import { ILocationService, ITimeoutService } from 'angular'; import { ILocationService, ITimeoutService, IRootScopeService } from 'angular';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
export class KeybindingSrv { export class KeybindingSrv {
helpModal: boolean; helpModal: boolean;
@ -18,7 +21,7 @@ export class KeybindingSrv {
/** @ngInject */ /** @ngInject */
constructor( constructor(
private $rootScope: any, private $rootScope: GrafanaRootScope,
private $location: ILocationService, private $location: ILocationService,
private $timeout: ITimeoutService, private $timeout: ITimeoutService,
private datasourceSrv: any, private datasourceSrv: any,
@ -33,9 +36,9 @@ export class KeybindingSrv {
}); });
this.setupGlobal(); this.setupGlobal();
appEvents.on('show-modal', () => (this.modalOpen = true)); appEvents.on(CoreEvents.showModal, () => (this.modalOpen = true));
appEvents.on('timepickerOpen', () => (this.timepickerOpen = true)); appEvents.on(CoreEvents.timepickerOpen, () => (this.timepickerOpen = true));
appEvents.on('timepickerClosed', () => (this.timepickerOpen = false)); appEvents.on(CoreEvents.timepickerClosed, () => (this.timepickerOpen = false));
} }
setupGlobal() { setupGlobal() {
@ -78,7 +81,7 @@ export class KeybindingSrv {
} }
openSearch() { openSearch() {
appEvents.emit('show-dash-search'); appEvents.emit(CoreEvents.showDashSearch);
} }
openAlerting() { openAlerting() {
@ -94,11 +97,11 @@ export class KeybindingSrv {
} }
showHelpModal() { showHelpModal() {
appEvents.emit('show-modal', { templateHtml: '<help-modal></help-modal>' }); appEvents.emit(CoreEvents.showModal, { templateHtml: '<help-modal></help-modal>' });
} }
exit() { exit() {
appEvents.emit('hide-modal'); appEvents.emit(CoreEvents.hideModal);
if (this.modalOpen) { if (this.modalOpen) {
this.modalOpen = false; this.modalOpen = false;
@ -106,7 +109,7 @@ export class KeybindingSrv {
} }
if (this.timepickerOpen) { if (this.timepickerOpen) {
this.$rootScope.appEvent('closeTimepicker'); this.$rootScope.appEvent(CoreEvents.closeTimepicker);
this.timepickerOpen = false; this.timepickerOpen = false;
return; return;
} }
@ -120,12 +123,12 @@ export class KeybindingSrv {
} }
if (search.fullscreen) { if (search.fullscreen) {
appEvents.emit('panel-change-view', { fullscreen: false, edit: false }); appEvents.emit(PanelEvents.panelChangeView, { fullscreen: false, edit: false });
return; return;
} }
if (search.kiosk) { if (search.kiosk) {
this.$rootScope.appEvent('toggle-kiosk-mode', { exit: true }); this.$rootScope.appEvent(CoreEvents.toggleKioskMode, { exit: true });
} }
} }
@ -164,37 +167,37 @@ export class KeybindingSrv {
this.$location.search(search); this.$location.search(search);
} }
setupDashboardBindings(scope: any, dashboard: any) { setupDashboardBindings(scope: IRootScopeService & AppEventEmitter, dashboard: any) {
this.bind('mod+o', () => { this.bind('mod+o', () => {
dashboard.graphTooltip = (dashboard.graphTooltip + 1) % 3; dashboard.graphTooltip = (dashboard.graphTooltip + 1) % 3;
appEvents.emit('graph-hover-clear'); appEvents.emit(CoreEvents.graphHoverClear);
dashboard.startRefresh(); dashboard.startRefresh();
}); });
this.bind('mod+s', () => { this.bind('mod+s', () => {
scope.appEvent('save-dashboard'); scope.appEvent(CoreEvents.saveDashboard);
}); });
this.bind('t z', () => { this.bind('t z', () => {
scope.appEvent('zoom-out', 2); scope.appEvent(CoreEvents.zoomOut, 2);
}); });
this.bind('ctrl+z', () => { this.bind('ctrl+z', () => {
scope.appEvent('zoom-out', 2); scope.appEvent(CoreEvents.zoomOut, 2);
}); });
this.bind('t left', () => { this.bind('t left', () => {
scope.appEvent('shift-time', -1); scope.appEvent(CoreEvents.shiftTime, -1);
}); });
this.bind('t right', () => { this.bind('t right', () => {
scope.appEvent('shift-time', 1); scope.appEvent(CoreEvents.shiftTime, 1);
}); });
// edit panel // edit panel
this.bind('e', () => { this.bind('e', () => {
if (dashboard.meta.focusPanelId && dashboard.meta.canEdit) { if (dashboard.meta.focusPanelId && dashboard.meta.canEdit) {
appEvents.emit('panel-change-view', { appEvents.emit(PanelEvents.panelChangeView, {
fullscreen: true, fullscreen: true,
edit: true, edit: true,
panelId: dashboard.meta.focusPanelId, panelId: dashboard.meta.focusPanelId,
@ -206,7 +209,7 @@ export class KeybindingSrv {
// view panel // view panel
this.bind('v', () => { this.bind('v', () => {
if (dashboard.meta.focusPanelId) { if (dashboard.meta.focusPanelId) {
appEvents.emit('panel-change-view', { appEvents.emit(PanelEvents.panelChangeView, {
fullscreen: true, fullscreen: true,
panelId: dashboard.meta.focusPanelId, panelId: dashboard.meta.focusPanelId,
toggle: true, toggle: true,
@ -233,7 +236,7 @@ export class KeybindingSrv {
// delete panel // delete panel
this.bind('p r', () => { this.bind('p r', () => {
if (dashboard.meta.focusPanelId && dashboard.meta.canEdit) { if (dashboard.meta.focusPanelId && dashboard.meta.canEdit) {
appEvents.emit('remove-panel', dashboard.meta.focusPanelId); appEvents.emit(CoreEvents.removePanel, dashboard.meta.focusPanelId);
dashboard.meta.focusPanelId = 0; dashboard.meta.focusPanelId = 0;
} }
}); });
@ -249,12 +252,12 @@ export class KeybindingSrv {
// share panel // share panel
this.bind('p s', () => { this.bind('p s', () => {
if (dashboard.meta.focusPanelId) { if (dashboard.meta.focusPanelId) {
const shareScope = scope.$new(); const shareScope: any = scope.$new();
const panelInfo = dashboard.getPanelInfoById(dashboard.meta.focusPanelId); const panelInfo = dashboard.getPanelInfoById(dashboard.meta.focusPanelId);
shareScope.panel = panelInfo.panel; shareScope.panel = panelInfo.panel;
shareScope.dashboard = dashboard; shareScope.dashboard = dashboard;
appEvents.emit('show-modal', { appEvents.emit(CoreEvents.showModal, {
src: 'public/app/features/dashboard/components/ShareModal/template.html', src: 'public/app/features/dashboard/components/ShareModal/template.html',
scope: shareScope, scope: shareScope,
}); });
@ -301,11 +304,11 @@ export class KeybindingSrv {
}); });
this.bind('d k', () => { this.bind('d k', () => {
appEvents.emit('toggle-kiosk-mode'); appEvents.emit(CoreEvents.toggleKioskMode);
}); });
this.bind('d v', () => { this.bind('d v', () => {
appEvents.emit('toggle-view-mode'); appEvents.emit(CoreEvents.toggleViewMode);
}); });
//Autofit panels //Autofit panels

View File

@ -2,9 +2,10 @@ import _ from 'lodash';
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
// @ts-ignore // @ts-ignore
import Drop from 'tether-drop'; import Drop from 'tether-drop';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
/** @ngInject */ /** @ngInject */
function popoverSrv(this: any, $compile: any, $rootScope: any, $timeout: any) { function popoverSrv(this: any, $compile: any, $rootScope: GrafanaRootScope, $timeout: any) {
let openDrop: any = null; let openDrop: any = null;
this.close = () => { this.close = () => {

View File

@ -1,16 +1,18 @@
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { CoreEvents } from 'app/types';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
export class UtilSrv { export class UtilSrv {
modalScope: any; modalScope: any;
/** @ngInject */ /** @ngInject */
constructor(private $rootScope: any, private $modal: any) {} constructor(private $rootScope: GrafanaRootScope, private $modal: any) {}
init() { init() {
appEvents.on('show-modal', this.showModal.bind(this), this.$rootScope); appEvents.on(CoreEvents.showModal, this.showModal.bind(this), this.$rootScope);
appEvents.on('hide-modal', this.hideModal.bind(this), this.$rootScope); appEvents.on(CoreEvents.hideModal, this.hideModal.bind(this), this.$rootScope);
appEvents.on('confirm-modal', this.showConfirmModal.bind(this), this.$rootScope); appEvents.on(CoreEvents.showConfirmModal, this.showConfirmModal.bind(this), this.$rootScope);
} }
hideModal() { hideModal() {
@ -50,7 +52,7 @@ export class UtilSrv {
} }
showConfirmModal(payload: any) { showConfirmModal(payload: any) {
const scope = this.$rootScope.$new(); const scope: any = this.$rootScope.$new();
scope.updateConfirmText = (value: any) => { scope.updateConfirmText = (value: any) => {
scope.confirmTextValid = payload.confirmText.toLowerCase() === value.toLowerCase(); scope.confirmTextValid = payload.confirmText.toLowerCase() === value.toLowerCase();
@ -70,7 +72,7 @@ export class UtilSrv {
scope.noText = payload.noText || 'Cancel'; scope.noText = payload.noText || 'Cancel';
scope.confirmTextValid = scope.confirmText ? false : true; scope.confirmTextValid = scope.confirmText ? false : true;
appEvents.emit('show-modal', { appEvents.emit(CoreEvents.showModal, {
src: 'public/app/partials/confirm_modal.html', src: 'public/app/partials/confirm_modal.html',
scope: scope, scope: scope,
modalClass: 'confirm-modal', modalClass: 'confirm-modal',

View File

@ -1,4 +1,7 @@
import { Emitter } from '../utils/emitter'; import { Emitter } from '../utils/emitter';
import { eventFactory } from '@grafana/data';
const testEvent = eventFactory('test');
describe('Emitter', () => { describe('Emitter', () => {
describe('given 2 subscribers', () => { describe('given 2 subscribers', () => {
@ -7,14 +10,14 @@ describe('Emitter', () => {
let sub1Called = false; let sub1Called = false;
let sub2Called = false; let sub2Called = false;
events.on('test', () => { events.on(testEvent, () => {
sub1Called = true; sub1Called = true;
}); });
events.on('test', () => { events.on(testEvent, () => {
sub2Called = true; sub2Called = true;
}); });
events.emit('test', null); events.emit(testEvent, null);
expect(sub1Called).toBe(true); expect(sub1Called).toBe(true);
expect(sub2Called).toBe(true); expect(sub2Called).toBe(true);
@ -28,10 +31,10 @@ describe('Emitter', () => {
sub1Called += 1; sub1Called += 1;
} }
events.on('test', handler); events.on(testEvent, handler);
events.on('test', handler); events.on(testEvent, handler);
events.emit('test', null); events.emit(testEvent, null);
expect(sub1Called).toBe(2); expect(sub1Called).toBe(2);
}); });
@ -41,20 +44,20 @@ describe('Emitter', () => {
let sub1Called = 0; let sub1Called = 0;
let sub2Called = 0; let sub2Called = 0;
events.on('test', () => { events.on(testEvent, () => {
sub1Called++; sub1Called++;
throw { message: 'hello' }; throw { message: 'hello' };
}); });
events.on('test', () => { events.on(testEvent, () => {
sub2Called++; sub2Called++;
}); });
try { try {
events.emit('test', null); events.emit(testEvent, null);
} catch (_) {} } catch (_) {}
try { try {
events.emit('test', null); events.emit(testEvent, null);
} catch (_) {} } catch (_) {}
expect(sub1Called).toBe(2); expect(sub1Called).toBe(2);

View File

@ -1,6 +1,7 @@
import { SearchResultsCtrl } from '../components/search/search_results'; import { SearchResultsCtrl } from '../components/search/search_results';
import { beforeEach, afterEach } from 'test/lib/common'; import { beforeEach, afterEach } from 'test/lib/common';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { CoreEvents } from 'app/types';
jest.mock('app/core/app_events', () => { jest.mock('app/core/app_events', () => {
return { return {
@ -121,7 +122,7 @@ describe('SearchResultsCtrl', () => {
it('should close the search', () => { it('should close the search', () => {
expect(appEventsMock.emit.mock.calls.length).toBe(1); expect(appEventsMock.emit.mock.calls.length).toBe(1);
expect(appEventsMock.emit.mock.calls[0][0]).toBe('hide-dash-search'); expect(appEventsMock.emit.mock.calls[0][0]).toBe(CoreEvents.hideDashSearch);
}); });
}); });

View File

@ -1,4 +1,5 @@
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import { AppEvent } from '@grafana/data';
export class Emitter { export class Emitter {
private emitter: EventEmitter; private emitter: EventEmitter;
@ -7,29 +8,83 @@ export class Emitter {
this.emitter = new EventEmitter(); this.emitter = new EventEmitter();
} }
emit(name: string, data?: any) { /**
this.emitter.emit(name, data); * DEPRECATED.
*/
emit(name: string, data?: any): void;
/**
* Emits an `event` with `payload`.
*/
emit<T extends undefined>(event: AppEvent<T>): void;
emit<T extends Partial<T> extends T ? Partial<T> : never>(event: AppEvent<T>): void;
emit<T>(event: AppEvent<T>, payload: T): void;
emit<T>(event: AppEvent<T> | string, payload?: T | any): void {
if (typeof event === 'string') {
console.log(`Using strings as events is deprecated and will be removed in a future version. (${event})`);
this.emitter.emit(event, payload);
} else {
this.emitter.emit(event.name, payload);
}
} }
on(name: string, handler: (payload?: any) => void, scope?: any) { /**
this.emitter.on(name, handler); * DEPRECATED.
*/
on(name: string, handler: (payload?: any) => void, scope?: any): void;
/**
* Handles `event` with `handler()` when emitted.
*/
on<T extends undefined>(event: AppEvent<T>, handler: () => void, scope?: any): void;
on<T extends Partial<T> extends T ? Partial<T> : never>(event: AppEvent<T>, handler: () => void, scope?: any): void;
on<T>(event: AppEvent<T>, handler: (payload: T) => void, scope?: any): void;
on<T>(event: AppEvent<T> | string, handler: (payload?: T | any) => void, scope?: any) {
if (typeof event === 'string') {
console.log(`Using strings as events is deprecated and will be removed in a future version. (${event})`);
this.emitter.on(event, handler);
if (scope) {
const unbind = scope.$on('$destroy', () => {
this.emitter.off(event, handler);
unbind();
});
}
return;
}
this.emitter.on(event.name, handler);
if (scope) { if (scope) {
const unbind = scope.$on('$destroy', () => { const unbind = scope.$on('$destroy', () => {
this.emitter.off(name, handler); this.emitter.off(event.name, handler);
unbind(); unbind();
}); });
} }
} }
/**
* DEPRECATED.
*/
off(name: string, handler: (payload?: any) => void): void;
off<T extends undefined>(event: AppEvent<T>, handler: () => void): void;
off<T extends Partial<T> extends T ? Partial<T> : never>(event: AppEvent<T>, handler: () => void, scope?: any): void;
off<T>(event: AppEvent<T>, handler: (payload: T) => void): void;
off<T>(event: AppEvent<T> | string, handler: (payload?: T | any) => void) {
if (typeof event === 'string') {
console.log(`Using strings as events is deprecated and will be removed in a future version. (${event})`);
this.emitter.off(event, handler);
return;
}
this.emitter.off(event.name, handler);
}
removeAllListeners(evt?: string) { removeAllListeners(evt?: string) {
this.emitter.removeAllListeners(evt); this.emitter.removeAllListeners(evt);
} }
off(name: string, handler: (payload?: any) => void) {
this.emitter.off(name, handler);
}
getEventCount(): number { getEventCount(): number {
return (this.emitter as any)._eventsCount; return (this.emitter as any)._eventsCount;
} }

View File

@ -1,13 +1,19 @@
import _ from 'lodash'; import _ from 'lodash';
import { dateTime } from '@grafana/data';
import { BackendSrv } from 'app/core/services/backend_srv'; import { BackendSrv } from 'app/core/services/backend_srv';
import { NavModelSrv } from 'app/core/core'; import { NavModelSrv } from 'app/core/core';
import { User } from 'app/core/services/context_srv'; import { User } from 'app/core/services/context_srv';
import { UserSession } from 'app/types'; import { UserSession, Scope, CoreEvents, AppEventEmitter } from 'app/types';
import { dateTime } from '@grafana/data';
export default class AdminEditUserCtrl { export default class AdminEditUserCtrl {
/** @ngInject */ /** @ngInject */
constructor($scope: any, $routeParams: any, backendSrv: BackendSrv, $location: any, navModelSrv: NavModelSrv) { constructor(
$scope: Scope & AppEventEmitter,
$routeParams: any,
backendSrv: BackendSrv,
$location: any,
navModelSrv: NavModelSrv
) {
$scope.user = {}; $scope.user = {};
$scope.sessions = []; $scope.sessions = [];
$scope.newOrg = { name: '', role: 'Editor' }; $scope.newOrg = { name: '', role: 'Editor' };
@ -162,7 +168,7 @@ export default class AdminEditUserCtrl {
}; };
$scope.deleteUser = (user: any) => { $scope.deleteUser = (user: any) => {
$scope.appEvent('confirm-modal', { $scope.appEvent(CoreEvents.showConfirmModal, {
title: 'Delete', title: 'Delete',
text: 'Do you want to delete ' + user.login + '?', text: 'Do you want to delete ' + user.login + '?',
icon: 'fa-trash', icon: 'fa-trash',

View File

@ -1,9 +1,10 @@
import { BackendSrv } from 'app/core/services/backend_srv'; import { BackendSrv } from 'app/core/services/backend_srv';
import { NavModelSrv } from 'app/core/core'; import { NavModelSrv } from 'app/core/core';
import { Scope, CoreEvents, AppEventEmitter } from 'app/types';
export default class AdminListOrgsCtrl { export default class AdminListOrgsCtrl {
/** @ngInject */ /** @ngInject */
constructor($scope: any, backendSrv: BackendSrv, navModelSrv: NavModelSrv) { constructor($scope: Scope & AppEventEmitter, backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
$scope.init = () => { $scope.init = () => {
$scope.navModel = navModelSrv.getNav('admin', 'global-orgs', 0); $scope.navModel = navModelSrv.getNav('admin', 'global-orgs', 0);
$scope.getOrgs(); $scope.getOrgs();
@ -16,9 +17,9 @@ export default class AdminListOrgsCtrl {
}; };
$scope.deleteOrg = (org: any) => { $scope.deleteOrg = (org: any) => {
$scope.appEvent('confirm-modal', { $scope.appEvent(CoreEvents.showConfirmModal, {
title: 'Delete', title: 'Delete',
text: 'Do you want to delete organization ' + org.name + '?', text: `Do you want to delete organization ${org.name}?`,
text2: 'All dashboards for this organization will be removed!', text2: 'All dashboards for this organization will be removed!',
icon: 'fa-trash', icon: 'fa-trash',
yesText: 'Delete', yesText: 'Delete',

View File

@ -6,6 +6,7 @@ import appEvents from '../../core/app_events';
import { mockActionCreator } from 'app/core/redux'; import { mockActionCreator } from 'app/core/redux';
import { updateLocation } from 'app/core/actions'; import { updateLocation } from 'app/core/actions';
import { NavModel } from '@grafana/data'; import { NavModel } from '@grafana/data';
import { CoreEvents } from 'app/types';
jest.mock('../../core/app_events', () => ({ jest.mock('../../core/app_events', () => ({
emit: jest.fn(), emit: jest.fn(),
@ -139,7 +140,7 @@ describe('Functions', () => {
instance.onOpenHowTo(); instance.onOpenHowTo();
expect(appEvents.emit).toHaveBeenCalledWith('show-modal', { expect(appEvents.emit).toHaveBeenCalledWith(CoreEvents.showModal, {
src: 'public/app/features/alerting/partials/alert_howto.html', src: 'public/app/features/alerting/partials/alert_howto.html',
modalClass: 'confirm-modal', modalClass: 'confirm-modal',
model: {}, model: {},

View File

@ -6,7 +6,7 @@ import AlertRuleItem from './AlertRuleItem';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { updateLocation } from 'app/core/actions'; import { updateLocation } from 'app/core/actions';
import { getNavModel } from 'app/core/selectors/navModel'; import { getNavModel } from 'app/core/selectors/navModel';
import { StoreState, AlertRule } from 'app/types'; import { StoreState, AlertRule, CoreEvents } from 'app/types';
import { getAlertRulesAsync, setSearchQuery, togglePauseAlertRule } from './state/actions'; import { getAlertRulesAsync, setSearchQuery, togglePauseAlertRule } from './state/actions';
import { getAlertRuleItems, getSearchQuery } from './state/selectors'; import { getAlertRuleItems, getSearchQuery } from './state/selectors';
import { FilterInput } from 'app/core/components/FilterInput/FilterInput'; import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
@ -64,7 +64,7 @@ export class AlertRuleList extends PureComponent<Props, any> {
}; };
onOpenHowTo = () => { onOpenHowTo = () => {
appEvents.emit('show-modal', { appEvents.emit(CoreEvents.showModal, {
src: 'public/app/features/alerting/partials/alert_howto.html', src: 'public/app/features/alerting/partials/alert_howto.html',
modalClass: 'confirm-modal', modalClass: 'confirm-modal',
model: {}, model: {},

View File

@ -19,6 +19,7 @@ import { TestRuleResult } from './TestRuleResult';
import { AppNotificationSeverity, StoreState } from 'app/types'; import { AppNotificationSeverity, StoreState } from 'app/types';
import { PanelEditorTabIds, getPanelEditorTab } from '../dashboard/panel_editor/state/reducers'; import { PanelEditorTabIds, getPanelEditorTab } from '../dashboard/panel_editor/state/reducers';
import { changePanelEditorTab } from '../dashboard/panel_editor/state/actions'; import { changePanelEditorTab } from '../dashboard/panel_editor/state/actions';
import { CoreEvents } from 'app/types';
interface Props { interface Props {
angularPanel?: AngularComponent; angularPanel?: AngularComponent;
@ -116,7 +117,7 @@ class UnConnectedAlertTab extends PureComponent<Props, State> {
title: 'Delete', title: 'Delete',
btnType: 'danger', btnType: 'danger',
onClick: () => { onClick: () => {
appEvents.emit('confirm-modal', { appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Delete Alert', title: 'Delete Alert',
text: 'Are you sure you want to delete this alert rule?', text: 'Are you sure you want to delete this alert rule?',
text2: 'You need to save dashboard for the delete to take effect', text2: 'You need to save dashboard for the delete to take effect',

View File

@ -11,6 +11,7 @@ import DatasourceSrv from '../plugins/datasource_srv';
import { DataQuery } from '@grafana/ui/src/types/datasource'; import { DataQuery } from '@grafana/ui/src/types/datasource';
import { PanelModel } from 'app/features/dashboard/state'; import { PanelModel } from 'app/features/dashboard/state';
import { getDefaultCondition } from './getAlertingValidationMessage'; import { getDefaultCondition } from './getAlertingValidationMessage';
import { CoreEvents } from 'app/types';
export class AlertTabCtrl { export class AlertTabCtrl {
panel: PanelModel; panel: PanelModel;
@ -58,11 +59,11 @@ export class AlertTabCtrl {
// subscribe to graph threshold handle changes // subscribe to graph threshold handle changes
const thresholdChangedEventHandler = this.graphThresholdChanged.bind(this); const thresholdChangedEventHandler = this.graphThresholdChanged.bind(this);
this.panelCtrl.events.on('threshold-changed', thresholdChangedEventHandler); this.panelCtrl.events.on(CoreEvents.thresholdChanged, thresholdChangedEventHandler);
// set panel alert edit mode // set panel alert edit mode
this.$scope.$on('$destroy', () => { this.$scope.$on('$destroy', () => {
this.panelCtrl.events.off('threshold-changed', thresholdChangedEventHandler); this.panelCtrl.events.off(CoreEvents.thresholdChanged, thresholdChangedEventHandler);
this.panelCtrl.editingThresholds = false; this.panelCtrl.editingThresholds = false;
this.panelCtrl.render(); this.panelCtrl.render();
}); });
@ -352,7 +353,7 @@ export class AlertTabCtrl {
} }
delete() { delete() {
appEvents.emit('confirm-modal', { appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Delete Alert', title: 'Delete Alert',
text: 'Are you sure you want to delete this alert rule?', text: 'Are you sure you want to delete this alert rule?',
text2: 'You need to save dashboard for the delete to take effect', text2: 'You need to save dashboard for the delete to take effect',
@ -402,7 +403,7 @@ export class AlertTabCtrl {
} }
clearHistory() { clearHistory() {
appEvents.emit('confirm-modal', { appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Delete Alert History', title: 'Delete Alert History',
text: 'Are you sure you want to remove all history & annotations for this alert?', text: 'Are you sure you want to remove all history & annotations for this alert?',
icon: 'fa-trash', icon: 'fa-trash',

View File

@ -1,6 +1,7 @@
import _ from 'lodash'; import _ from 'lodash';
import { appEvents, coreModule, NavModelSrv } from 'app/core/core'; import { appEvents, coreModule, NavModelSrv } from 'app/core/core';
import { BackendSrv } from 'app/core/services/backend_srv'; import { BackendSrv } from 'app/core/services/backend_srv';
import { AppEvents } from '@grafana/data';
export class AlertNotificationEditCtrl { export class AlertNotificationEditCtrl {
theForm: any; theForm: any;
@ -78,23 +79,23 @@ export class AlertNotificationEditCtrl {
.put(`/api/alert-notifications/${this.model.id}`, this.model) .put(`/api/alert-notifications/${this.model.id}`, this.model)
.then((res: any) => { .then((res: any) => {
this.model = res; this.model = res;
appEvents.emit('alert-success', ['Notification updated', '']); appEvents.emit(AppEvents.alertSuccess, ['Notification updated']);
}) })
.catch((err: any) => { .catch((err: any) => {
if (err.data && err.data.error) { if (err.data && err.data.error) {
appEvents.emit('alert-error', [err.data.error]); appEvents.emit(AppEvents.alertError, [err.data.error]);
} }
}); });
} else { } else {
this.backendSrv this.backendSrv
.post(`/api/alert-notifications`, this.model) .post(`/api/alert-notifications`, this.model)
.then((res: any) => { .then((res: any) => {
appEvents.emit('alert-success', ['Notification created', '']); appEvents.emit(AppEvents.alertSuccess, ['Notification created']);
this.$location.path('alerting/notifications'); this.$location.path('alerting/notifications');
}) })
.catch((err: any) => { .catch((err: any) => {
if (err.data && err.data.error) { if (err.data && err.data.error) {
appEvents.emit('alert-error', [err.data.error]); appEvents.emit(AppEvents.alertError, [err.data.error]);
} }
}); });
} }

View File

@ -3,6 +3,7 @@ import alertDef from './state/alertDef';
import { getBackendSrv } from '@grafana/runtime'; import { getBackendSrv } from '@grafana/runtime';
import { DashboardModel } from '../dashboard/state/DashboardModel'; import { DashboardModel } from '../dashboard/state/DashboardModel';
import appEvents from '../../core/app_events'; import appEvents from '../../core/app_events';
import { CoreEvents } from 'app/types';
interface Props { interface Props {
dashboard: DashboardModel; dashboard: DashboardModel;
@ -42,7 +43,7 @@ class StateHistory extends PureComponent<Props, State> {
clearHistory = () => { clearHistory = () => {
const { dashboard, onRefresh, panelId } = this.props; const { dashboard, onRefresh, panelId } = this.props;
appEvents.emit('confirm-modal', { appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Delete Alert History', title: 'Delete Alert History',
text: 'Are you sure you want to remove all history & annotations for this alert?', text: 'Are you sure you want to remove all history & annotations for this alert?',
icon: 'fa-trash', icon: 'fa-trash',

View File

@ -5,6 +5,7 @@ import appEvents from 'app/core/app_events';
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard'; import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
import { DashboardModel } from '../dashboard/state/DashboardModel'; import { DashboardModel } from '../dashboard/state/DashboardModel';
import { getBackendSrv, BackendSrv } from '@grafana/runtime'; import { getBackendSrv, BackendSrv } from '@grafana/runtime';
import { AppEvents } from '@grafana/data';
export interface Props { export interface Props {
panelId: number; panelId: number;
@ -55,7 +56,7 @@ export class TestRuleResult extends PureComponent<Props, State> {
}; };
onClipboardSuccess = () => { onClipboardSuccess = () => {
appEvents.emit('alert-success', ['Content copied to clipboard']); appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']);
}; };
onToggleExpand = () => { onToggleExpand = () => {

View File

@ -11,11 +11,12 @@ import { dedupAnnotations } from './events_processing';
// Types // Types
import { DashboardModel } from '../dashboard/state/DashboardModel'; import { DashboardModel } from '../dashboard/state/DashboardModel';
import { AnnotationEvent } from '@grafana/data';
import DatasourceSrv from '../plugins/datasource_srv'; import DatasourceSrv from '../plugins/datasource_srv';
import { BackendSrv } from 'app/core/services/backend_srv'; import { BackendSrv } from 'app/core/services/backend_srv';
import { TimeSrv } from '../dashboard/services/TimeSrv'; import { TimeSrv } from '../dashboard/services/TimeSrv';
import { DataSourceApi } from '@grafana/ui'; import { DataSourceApi, PanelEvents } from '@grafana/ui';
import { AnnotationEvent, AppEvents } from '@grafana/data';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
export class AnnotationsSrv { export class AnnotationsSrv {
globalAnnotationsPromise: any; globalAnnotationsPromise: any;
@ -24,7 +25,7 @@ export class AnnotationsSrv {
/** @ngInject */ /** @ngInject */
constructor( constructor(
private $rootScope: any, private $rootScope: GrafanaRootScope,
private $q: IQService, private $q: IQService,
private datasourceSrv: DatasourceSrv, private datasourceSrv: DatasourceSrv,
private backendSrv: BackendSrv, private backendSrv: BackendSrv,
@ -35,7 +36,7 @@ export class AnnotationsSrv {
// always clearPromiseCaches when loading new dashboard // always clearPromiseCaches when loading new dashboard
this.clearPromiseCaches(); this.clearPromiseCaches();
// clear promises on refresh events // clear promises on refresh events
dashboard.on('refresh', this.clearPromiseCaches.bind(this)); dashboard.on(PanelEvents.refresh, this.clearPromiseCaches.bind(this));
} }
clearPromiseCaches() { clearPromiseCaches() {
@ -75,7 +76,7 @@ export class AnnotationsSrv {
err.message = err.data.message; err.message = err.data.message;
} }
console.log('AnnotationSrv.query error', err); console.log('AnnotationSrv.query error', err);
this.$rootScope.appEvent('alert-error', ['Annotation Query Failed', err.message || err]); this.$rootScope.appEvent(AppEvents.alertError, ['Annotation Query Failed', err.message || err]);
return []; return [];
}); });
} }

View File

@ -5,6 +5,7 @@ import coreModule from 'app/core/core_module';
import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardModel } from 'app/features/dashboard/state';
import DatasourceSrv from '../plugins/datasource_srv'; import DatasourceSrv from '../plugins/datasource_srv';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { AppEvents } from '@grafana/data';
export class AnnotationsEditorCtrl { export class AnnotationsEditorCtrl {
mode: any; mode: any;
@ -103,7 +104,7 @@ export class AnnotationsEditorCtrl {
add() { add() {
const sameName: any = _.find(this.annotations, { name: this.currentAnnotation.name }); const sameName: any = _.find(this.annotations, { name: this.currentAnnotation.name });
if (sameName) { if (sameName) {
appEvents.emit('alert-warning', ['Validation', 'Annotations with the same name already exists']); appEvents.emit(AppEvents.alertWarning, ['Validation', 'Annotations with the same name already exists']);
return; return;
} }
this.annotations.push(this.currentAnnotation); this.annotations.push(this.currentAnnotation);

View File

@ -1,7 +1,7 @@
import { AnnotationsSrv } from '../annotations_srv'; import { AnnotationsSrv } from '../annotations_srv';
describe('AnnotationsSrv', () => { describe('AnnotationsSrv', () => {
const $rootScope = { const $rootScope: any = {
onAppEvent: jest.fn(), onAppEvent: jest.fn(),
}; };

View File

@ -13,13 +13,13 @@ import config from 'app/core/config';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { DeleteButton, EventsWithValidation, FormLabel, Input, ValidationEvents } from '@grafana/ui'; import { DeleteButton, EventsWithValidation, FormLabel, Input, ValidationEvents } from '@grafana/ui';
import { NavModel } from '@grafana/data'; import { NavModel, dateTime, isDateTime } from '@grafana/data';
import { FilterInput } from 'app/core/components/FilterInput/FilterInput'; import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
import { store } from 'app/store/store'; import { store } from 'app/store/store';
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';
// Utils // Utils
import { dateTime, isDateTime } from '@grafana/data'; import { CoreEvents } from 'app/types';
import { getTimeZone } from 'app/features/profile/state/selectors'; import { getTimeZone } from 'app/features/profile/state/selectors';
const timeRangeValidationEvents: ValidationEvents = { const timeRangeValidationEvents: ValidationEvents = {
@ -106,7 +106,7 @@ export class ApiKeysPage extends PureComponent<Props, any> {
const rootPath = window.location.origin + config.appSubUrl; const rootPath = window.location.origin + config.appSubUrl;
const modalTemplate = ReactDOMServer.renderToString(<ApiKeysAddedModal apiKey={apiKey} rootPath={rootPath} />); const modalTemplate = ReactDOMServer.renderToString(<ApiKeysAddedModal apiKey={apiKey} rootPath={rootPath} />);
appEvents.emit('show-modal', { appEvents.emit(CoreEvents.showModal, {
templateHtml: modalTemplate, templateHtml: modalTemplate,
}); });
}; };

View File

@ -4,6 +4,7 @@ import coreModule from 'app/core/core_module';
import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardModel } from 'app/features/dashboard/state';
import DatasourceSrv from 'app/features/plugins/datasource_srv'; import DatasourceSrv from 'app/features/plugins/datasource_srv';
import { VariableSrv } from 'app/features/templating/all'; import { VariableSrv } from 'app/features/templating/all';
import { CoreEvents } from 'app/types';
export class AdHocFiltersCtrl { export class AdHocFiltersCtrl {
segments: any; segments: any;
@ -24,7 +25,7 @@ export class AdHocFiltersCtrl {
value: '-- remove filter --', value: '-- remove filter --',
}); });
this.buildSegmentModel(); this.buildSegmentModel();
this.dashboard.events.on('template-variable-value-updated', this.buildSegmentModel.bind(this), $scope); this.dashboard.events.on(CoreEvents.templateVariableValueUpdated, this.buildSegmentModel.bind(this), $scope);
} }
buildSegmentModel() { buildSegmentModel() {

View File

@ -5,6 +5,8 @@ import coreModule from 'app/core/core_module';
import { DashboardExporter } from './DashboardExporter'; import { DashboardExporter } from './DashboardExporter';
import { DashboardSrv } from '../../services/DashboardSrv'; import { DashboardSrv } from '../../services/DashboardSrv';
import DatasourceSrv from 'app/features/plugins/datasource_srv'; import DatasourceSrv from 'app/features/plugins/datasource_srv';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { CoreEvents } from 'app/types';
export class DashExportCtrl { export class DashExportCtrl {
dash: any; dash: any;
@ -17,7 +19,7 @@ export class DashExportCtrl {
private dashboardSrv: DashboardSrv, private dashboardSrv: DashboardSrv,
datasourceSrv: DatasourceSrv, datasourceSrv: DatasourceSrv,
private $scope: any, private $scope: any,
private $rootScope: any private $rootScope: GrafanaRootScope
) { ) {
this.exporter = new DashboardExporter(datasourceSrv); this.exporter = new DashboardExporter(datasourceSrv);
@ -61,7 +63,7 @@ export class DashExportCtrl {
enableCopy: true, enableCopy: true,
}; };
this.$rootScope.appEvent('show-modal', { this.$rootScope.appEvent(CoreEvents.showModal, {
src: 'public/app/partials/edit_json.html', src: 'public/app/partials/edit_json.html',
model: model, model: model,
}); });

View File

@ -4,6 +4,9 @@ import { iconMap } from './DashLinksEditorCtrl';
import { LinkSrv } from 'app/features/panel/panellinks/link_srv'; import { LinkSrv } from 'app/features/panel/panellinks/link_srv';
import { BackendSrv } from 'app/core/services/backend_srv'; import { BackendSrv } from 'app/core/services/backend_srv';
import { DashboardSrv } from '../../services/DashboardSrv'; import { DashboardSrv } from '../../services/DashboardSrv';
import { PanelEvents } from '@grafana/ui';
import { CoreEvents } from 'app/types';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
export type DashboardLink = { tags: any; target: string; keepTime: any; includeVars: any }; export type DashboardLink = { tags: any; target: string; keepTime: any; includeVars: any };
@ -84,7 +87,7 @@ function dashLink($compile: any, $sanitize: any, linkSrv: LinkSrv) {
} }
update(); update();
dashboard.events.on('refresh', update, scope); dashboard.events.on(PanelEvents.refresh, update, scope);
}, },
}; };
} }
@ -93,7 +96,7 @@ export class DashLinksContainerCtrl {
/** @ngInject */ /** @ngInject */
constructor( constructor(
$scope: any, $scope: any,
$rootScope: any, $rootScope: GrafanaRootScope,
$q: IQService, $q: IQService,
backendSrv: BackendSrv, backendSrv: BackendSrv,
dashboardSrv: DashboardSrv, dashboardSrv: DashboardSrv,
@ -184,7 +187,7 @@ export class DashLinksContainerCtrl {
}; };
updateDashLinks(); updateDashLinks();
$rootScope.onAppEvent('dash-links-updated', updateDashLinks, $scope); $rootScope.onAppEvent(CoreEvents.dashLinksUpdated, updateDashLinks, $scope);
} }
} }

View File

@ -1,6 +1,8 @@
import angular from 'angular'; import angular from 'angular';
import _ from 'lodash'; import _ from 'lodash';
import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardModel } from 'app/features/dashboard/state';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { CoreEvents } from 'app/types';
export let iconMap = { export let iconMap = {
'external link': 'fa-external-link', 'external link': 'fa-external-link',
@ -32,13 +34,13 @@ export class DashLinksEditorCtrl {
}; };
/** @ngInject */ /** @ngInject */
constructor($scope: any, $rootScope: any) { constructor($scope: any, $rootScope: GrafanaRootScope) {
this.iconMap = iconMap; this.iconMap = iconMap;
this.dashboard.links = this.dashboard.links || []; this.dashboard.links = this.dashboard.links || [];
this.mode = 'list'; this.mode = 'list';
$scope.$on('$destroy', () => { $scope.$on('$destroy', () => {
$rootScope.appEvent('dash-links-updated'); $rootScope.appEvent(CoreEvents.dashLinksUpdated);
}); });
} }

View File

@ -16,7 +16,7 @@ import { updateLocation } from 'app/core/actions';
// Types // Types
import { DashboardModel } from '../../state'; import { DashboardModel } from '../../state';
import { StoreState } from 'app/types'; import { StoreState, CoreEvents } from 'app/types';
export interface OwnProps { export interface OwnProps {
dashboard: DashboardModel; dashboard: DashboardModel;
@ -43,11 +43,11 @@ export class DashNav extends PureComponent<Props> {
} }
onDahboardNameClick = () => { onDahboardNameClick = () => {
appEvents.emit('show-dash-search'); appEvents.emit(CoreEvents.showDashSearch);
}; };
onFolderNameClick = () => { onFolderNameClick = () => {
appEvents.emit('show-dash-search', { appEvents.emit(CoreEvents.showDashSearch, {
query: 'folder:current', query: 'folder:current',
}); });
}; };
@ -67,7 +67,7 @@ export class DashNav extends PureComponent<Props> {
}; };
onToggleTVMode = () => { onToggleTVMode = () => {
appEvents.emit('toggle-kiosk-mode'); appEvents.emit(CoreEvents.toggleKioskMode);
}; };
onSave = () => { onSave = () => {
@ -112,7 +112,7 @@ export class DashNav extends PureComponent<Props> {
modalScope.tabIndex = 0; modalScope.tabIndex = 0;
modalScope.dashboard = this.props.dashboard; modalScope.dashboard = this.props.dashboard;
appEvents.emit('show-modal', { appEvents.emit(CoreEvents.showModal, {
src: 'public/app/features/dashboard/components/ShareModal/template.html', src: 'public/app/features/dashboard/components/ShareModal/template.html',
scope: modalScope, scope: modalScope,
}); });

View File

@ -4,7 +4,7 @@ import { dateMath } from '@grafana/data';
// Types // Types
import { DashboardModel } from '../../state'; import { DashboardModel } from '../../state';
import { LocationState } from 'app/types'; import { LocationState, CoreEvents } from 'app/types';
import { TimeRange, TimeOption, RawTimeRange } from '@grafana/data'; import { TimeRange, TimeOption, RawTimeRange } from '@grafana/data';
// State // State
@ -43,10 +43,10 @@ export class DashNavTimeControls extends Component<Props> {
}; };
onMoveBack = () => { onMoveBack = () => {
this.$rootScope.appEvent('shift-time', -1); this.$rootScope.appEvent(CoreEvents.shiftTime, -1);
}; };
onMoveForward = () => { onMoveForward = () => {
this.$rootScope.appEvent('shift-time', 1); this.$rootScope.appEvent(CoreEvents.shiftTime, 1);
}; };
onChangeTimePicker = (timeRange: TimeRange) => { onChangeTimePicker = (timeRange: TimeRange) => {
@ -65,7 +65,7 @@ export class DashNavTimeControls extends Component<Props> {
}; };
onZoom = () => { onZoom = () => {
this.$rootScope.appEvent('zoom-out', 2); this.$rootScope.appEvent(CoreEvents.zoomOut, 2);
}; };
setActiveTimeOption = (timeOptions: TimeOption[], rawTimeRange: RawTimeRange): TimeOption[] => { setActiveTimeOption = (timeOptions: TimeOption[], rawTimeRange: RawTimeRange): TimeOption[] => {

View File

@ -4,6 +4,7 @@ import { PanelModel } from '../../state/PanelModel';
import { DashboardModel } from '../../state/DashboardModel'; import { DashboardModel } from '../../state/DashboardModel';
import templateSrv from 'app/features/templating/template_srv'; import templateSrv from 'app/features/templating/template_srv';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { CoreEvents } from 'app/types';
export interface DashboardRowProps { export interface DashboardRowProps {
panel: PanelModel; panel: PanelModel;
@ -18,11 +19,11 @@ export class DashboardRow extends React.Component<DashboardRowProps, any> {
collapsed: this.props.panel.collapsed, collapsed: this.props.panel.collapsed,
}; };
this.props.dashboard.on('template-variable-value-updated', this.onVariableUpdated); this.props.dashboard.on(CoreEvents.templateVariableValueUpdated, this.onVariableUpdated);
} }
componentWillUnmount() { componentWillUnmount() {
this.props.dashboard.off('template-variable-value-updated', this.onVariableUpdated); this.props.dashboard.off(CoreEvents.templateVariableValueUpdated, this.onVariableUpdated);
} }
onVariableUpdated = () => { onVariableUpdated = () => {
@ -43,7 +44,7 @@ export class DashboardRow extends React.Component<DashboardRowProps, any> {
}; };
onOpenSettings = () => { onOpenSettings = () => {
appEvents.emit('show-modal', { appEvents.emit(CoreEvents.showModal, {
templateHtml: `<row-options row="model.row" on-updated="model.onUpdated()" dismiss="dismiss()"></row-options>`, templateHtml: `<row-options row="model.row" on-updated="model.onUpdated()" dismiss="dismiss()"></row-options>`,
modalClass: 'modal--narrow', modalClass: 'modal--narrow',
model: { model: {
@ -54,7 +55,7 @@ export class DashboardRow extends React.Component<DashboardRowProps, any> {
}; };
onDelete = () => { onDelete = () => {
appEvents.emit('confirm-modal', { appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Delete Row', title: 'Delete Row',
text: 'Are you sure you want to remove this row and all its panels?', text: 'Are you sure you want to remove this row and all its panels?',
altActionText: 'Delete row only', altActionText: 'Delete row only',

View File

@ -6,6 +6,9 @@ import angular, { ILocationService } from 'angular';
import config from 'app/core/config'; import config from 'app/core/config';
import { BackendSrv } from 'app/core/services/backend_srv'; import { BackendSrv } from 'app/core/services/backend_srv';
import { DashboardSrv } from '../../services/DashboardSrv'; import { DashboardSrv } from '../../services/DashboardSrv';
import { CoreEvents } from 'app/types';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { AppEvents } from '@grafana/data';
export class SettingsCtrl { export class SettingsCtrl {
dashboard: DashboardModel; dashboard: DashboardModel;
@ -24,7 +27,7 @@ export class SettingsCtrl {
private $scope: any, private $scope: any,
private $route: any, private $route: any,
private $location: ILocationService, private $location: ILocationService,
private $rootScope: any, private $rootScope: GrafanaRootScope,
private backendSrv: BackendSrv, private backendSrv: BackendSrv,
private dashboardSrv: DashboardSrv private dashboardSrv: DashboardSrv
) { ) {
@ -35,7 +38,7 @@ export class SettingsCtrl {
this.$scope.$on('$destroy', () => { this.$scope.$on('$destroy', () => {
this.dashboard.updateSubmenuVisibility(); this.dashboard.updateSubmenuVisibility();
setTimeout(() => { setTimeout(() => {
this.$rootScope.appEvent('dash-scroll', { restore: true }); this.$rootScope.appEvent(CoreEvents.dashScroll, { restore: true });
this.dashboard.startRefresh(); this.dashboard.startRefresh();
}); });
}); });
@ -47,9 +50,9 @@ export class SettingsCtrl {
this.buildSectionList(); this.buildSectionList();
this.onRouteUpdated(); this.onRouteUpdated();
this.$rootScope.onAppEvent('$routeUpdate', this.onRouteUpdated.bind(this), $scope); this.$rootScope.onAppEvent(CoreEvents.routeUpdated, this.onRouteUpdated.bind(this), $scope);
this.$rootScope.appEvent('dash-scroll', { animate: false, pos: 0 }); this.$rootScope.appEvent(CoreEvents.dashScroll, { animate: false, pos: 0 });
this.$rootScope.onAppEvent('dashboard-saved', this.onPostSave.bind(this), $scope); this.$rootScope.onAppEvent(CoreEvents.dashboardSaved, this.onPostSave.bind(this), $scope);
} }
buildSectionList() { buildSectionList() {
@ -185,7 +188,7 @@ export class SettingsCtrl {
let text2 = this.dashboard.title; let text2 = this.dashboard.title;
if (this.dashboard.meta.provisioned) { if (this.dashboard.meta.provisioned) {
appEvents.emit('confirm-modal', { appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Cannot delete provisioned dashboard', title: 'Cannot delete provisioned dashboard',
text: ` text: `
This dashboard is managed by Grafanas provisioning and cannot be deleted. Remove the dashboard from the This dashboard is managed by Grafanas provisioning and cannot be deleted. Remove the dashboard from the
@ -213,7 +216,7 @@ export class SettingsCtrl {
text2 = `This dashboard contains ${alerts} alerts. Deleting this dashboard will also delete those alerts`; text2 = `This dashboard contains ${alerts} alerts. Deleting this dashboard will also delete those alerts`;
} }
appEvents.emit('confirm-modal', { appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Delete', title: 'Delete',
text: 'Do you want to delete this dashboard?', text: 'Do you want to delete this dashboard?',
text2: text2, text2: text2,
@ -229,7 +232,7 @@ export class SettingsCtrl {
deleteDashboardConfirmed() { deleteDashboardConfirmed() {
this.backendSrv.deleteDashboard(this.dashboard.uid, false).then(() => { this.backendSrv.deleteDashboard(this.dashboard.uid, false).then(() => {
appEvents.emit('alert-success', ['Dashboard Deleted', this.dashboard.title + ' has been deleted']); appEvents.emit(AppEvents.alertSuccess, ['Dashboard Deleted', this.dashboard.title + ' has been deleted']);
this.$location.url('/'); this.$location.url('/');
}); });
} }

View File

@ -2,6 +2,7 @@ import angular from 'angular';
import * as fileExport from 'app/core/utils/file_export'; import * as fileExport from 'app/core/utils/file_export';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { DashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { DashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { CoreEvents } from 'app/types';
export class ExportDataModalCtrl { export class ExportDataModalCtrl {
private data: any; private data: any;
@ -34,7 +35,7 @@ export class ExportDataModalCtrl {
} }
dismiss() { dismiss() {
appEvents.emit('hide-modal'); appEvents.emit(CoreEvents.hideModal);
} }
} }

View File

@ -4,6 +4,7 @@ import appEvents from 'app/core/app_events';
import { BackendSrv } from 'app/core/services/backend_srv'; import { BackendSrv } from 'app/core/services/backend_srv';
import { ValidationSrv } from 'app/features/manage-dashboards'; import { ValidationSrv } from 'app/features/manage-dashboards';
import { ContextSrv } from 'app/core/services/context_srv'; import { ContextSrv } from 'app/core/services/context_srv';
import { AppEvents } from '@grafana/data';
export class FolderPickerCtrl { export class FolderPickerCtrl {
initialTitle: string; initialTitle: string;
@ -105,7 +106,7 @@ export class FolderPickerCtrl {
} }
return this.backendSrv.createFolder({ title: this.newFolderName }).then((result: { title: string; id: number }) => { return this.backendSrv.createFolder({ title: this.newFolderName }).then((result: { title: string; id: number }) => {
appEvents.emit('alert-success', ['Folder Created', 'OK']); appEvents.emit(AppEvents.alertSuccess, ['Folder Created', 'OK']);
this.closeCreateFolder(); this.closeCreateFolder();
this.folder = { text: result.title, value: result.id }; this.folder = { text: result.title, value: result.id };

View File

@ -45,7 +45,7 @@ describe('ShareModalCtrl', () => {
// @ts-ignore // @ts-ignore
ctx.ctrl = new ShareModalCtrl( ctx.ctrl = new ShareModalCtrl(
ctx.scope, ctx.scope,
{}, {} as any,
ctx.$location, ctx.$location,
{}, {},
ctx.timeSrv, ctx.timeSrv,

View File

@ -5,11 +5,12 @@ import { appendQueryToUrl, toUrlParams } from 'app/core/utils/url';
import { TimeSrv } from '../../services/TimeSrv'; import { TimeSrv } from '../../services/TimeSrv';
import { TemplateSrv } from 'app/features/templating/template_srv'; import { TemplateSrv } from 'app/features/templating/template_srv';
import { LinkSrv } from 'app/features/panel/panellinks/link_srv'; import { LinkSrv } from 'app/features/panel/panellinks/link_srv';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
/** @ngInject */ /** @ngInject */
export function ShareModalCtrl( export function ShareModalCtrl(
$scope: any, $scope: any,
$rootScope: any, $rootScope: GrafanaRootScope,
$location: ILocationService, $location: ILocationService,
$timeout: any, $timeout: any,
timeSrv: TimeSrv, timeSrv: TimeSrv,

View File

@ -4,12 +4,13 @@ import { BackendSrv } from 'app/core/services/backend_srv';
import { TimeSrv } from '../../services/TimeSrv'; import { TimeSrv } from '../../services/TimeSrv';
import { DashboardModel } from '../../state/DashboardModel'; import { DashboardModel } from '../../state/DashboardModel';
import { PanelModel } from '../../state/PanelModel'; import { PanelModel } from '../../state/PanelModel';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
export class ShareSnapshotCtrl { export class ShareSnapshotCtrl {
/** @ngInject */ /** @ngInject */
constructor( constructor(
$scope: any, $scope: any,
$rootScope: any, $rootScope: GrafanaRootScope,
$location: ILocationService, $location: ILocationService,
backendSrv: BackendSrv, backendSrv: BackendSrv,
$timeout: any, $timeout: any,

View File

@ -3,6 +3,7 @@ import { HistoryListCtrl } from './HistoryListCtrl';
import { versions, compare, restore } from './__mocks__/history'; import { versions, compare, restore } from './__mocks__/history';
// @ts-ignore // @ts-ignore
import $q from 'q'; import $q from 'q';
import { CoreEvents } from 'app/types';
describe('HistoryListCtrl', () => { describe('HistoryListCtrl', () => {
const RESTORE_ID = 4; const RESTORE_ID = 4;
@ -118,9 +119,9 @@ describe('HistoryListCtrl', () => {
historyListCtrl.resetFromSource = jest.fn(); historyListCtrl.resetFromSource = jest.fn();
}); });
it('should listen for the `dashboard-saved` appEvent', () => { it('should listen for the `dashboardSaved` appEvent', () => {
expect($rootScope.onAppEvent).toHaveBeenCalledTimes(1); expect($rootScope.onAppEvent).toHaveBeenCalledTimes(1);
expect($rootScope.onAppEvent.mock.calls[0][0]).toBe('dashboard-saved'); expect($rootScope.onAppEvent.mock.calls[0][0]).toBe(CoreEvents.dashboardSaved);
}); });
it('should call `onDashboardSaved` when the appEvent is received', () => { it('should call `onDashboardSaved` when the appEvent is received', () => {
@ -292,7 +293,7 @@ describe('HistoryListCtrl', () => {
it('should display a modal allowing the user to restore or cancel', () => { it('should display a modal allowing the user to restore or cancel', () => {
expect($rootScope.appEvent).toHaveBeenCalledTimes(1); expect($rootScope.appEvent).toHaveBeenCalledTimes(1);
expect($rootScope.appEvent.mock.calls[0][0]).toBe('confirm-modal'); expect($rootScope.appEvent.mock.calls[0][0]).toBe(CoreEvents.showConfirmModal);
}); });
describe('and restore fails to fetch', () => { describe('and restore fails to fetch', () => {

View File

@ -4,7 +4,9 @@ import angular, { ILocationService, IQService } from 'angular';
import locationUtil from 'app/core/utils/location_util'; import locationUtil from 'app/core/utils/location_util';
import { DashboardModel } from '../../state/DashboardModel'; import { DashboardModel } from '../../state/DashboardModel';
import { HistoryListOpts, RevisionsModel, CalculateDiffOptions, HistorySrv } from './HistorySrv'; import { HistoryListOpts, RevisionsModel, CalculateDiffOptions, HistorySrv } from './HistorySrv';
import { dateTime, toUtc, DateTimeInput } from '@grafana/data'; import { dateTime, toUtc, DateTimeInput, AppEvents } from '@grafana/data';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { CoreEvents } from 'app/types';
export class HistoryListCtrl { export class HistoryListCtrl {
appending: boolean; appending: boolean;
@ -25,7 +27,7 @@ export class HistoryListCtrl {
/** @ngInject */ /** @ngInject */
constructor( constructor(
private $route: any, private $route: any,
private $rootScope: any, private $rootScope: GrafanaRootScope,
private $location: ILocationService, private $location: ILocationService,
private $q: IQService, private $q: IQService,
private historySrv: HistorySrv, private historySrv: HistorySrv,
@ -40,7 +42,7 @@ export class HistoryListCtrl {
this.start = 0; this.start = 0;
this.canCompare = false; this.canCompare = false;
this.$rootScope.onAppEvent('dashboard-saved', this.onDashboardSaved.bind(this), $scope); this.$rootScope.onAppEvent(CoreEvents.dashboardSaved, this.onDashboardSaved.bind(this), $scope);
this.resetFromSource(); this.resetFromSource();
} }
@ -56,7 +58,7 @@ export class HistoryListCtrl {
} }
dismiss() { dismiss() {
this.$rootScope.appEvent('hide-dash-editor'); this.$rootScope.appEvent(CoreEvents.hideDashEditor);
} }
addToLog() { addToLog() {
@ -172,7 +174,7 @@ export class HistoryListCtrl {
} }
restore(version: number) { restore(version: number) {
this.$rootScope.appEvent('confirm-modal', { this.$rootScope.appEvent(CoreEvents.showConfirmModal, {
title: 'Restore version', title: 'Restore version',
text: '', text: '',
text2: `Are you sure you want to restore the dashboard to version ${version}? All unsaved changes will be lost.`, text2: `Are you sure you want to restore the dashboard to version ${version}? All unsaved changes will be lost.`,
@ -189,7 +191,7 @@ export class HistoryListCtrl {
.then((response: any) => { .then((response: any) => {
this.$location.url(locationUtil.stripBaseFromUrl(response.url)).replace(); this.$location.url(locationUtil.stripBaseFromUrl(response.url)).replace();
this.$route.reload(); this.$route.reload();
this.$rootScope.appEvent('alert-success', ['Dashboard restored', 'Restored from version ' + version]); this.$rootScope.appEvent(AppEvents.alertSuccess, ['Dashboard restored', 'Restored from version ' + version]);
}) })
.catch(() => { .catch(() => {
this.mode = 'list'; this.mode = 'list';

View File

@ -10,6 +10,9 @@ import sizeMe from 'react-sizeme';
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants'; import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants';
import { DashboardPanel } from './DashboardPanel'; import { DashboardPanel } from './DashboardPanel';
import { DashboardModel, PanelModel } from '../state'; import { DashboardModel, PanelModel } from '../state';
import { CoreEvents } from 'app/types';
import { PanelEvents } from '@grafana/ui';
import { panelAdded, panelRemoved } from '../state/PanelModel';
let lastGridWidth = 1200; let lastGridWidth = 1200;
let ignoreNextWidthChange = false; let ignoreNextWidthChange = false;
@ -93,22 +96,22 @@ export class DashboardGrid extends PureComponent<Props> {
componentDidMount() { componentDidMount() {
const { dashboard } = this.props; const { dashboard } = this.props;
dashboard.on('panel-added', this.triggerForceUpdate); dashboard.on(panelAdded, this.triggerForceUpdate);
dashboard.on('panel-removed', this.triggerForceUpdate); dashboard.on(panelRemoved, this.triggerForceUpdate);
dashboard.on('repeats-processed', this.triggerForceUpdate); dashboard.on(CoreEvents.repeatsProcessed, this.triggerForceUpdate);
dashboard.on('view-mode-changed', this.onViewModeChanged); dashboard.on(PanelEvents.viewModeChanged, this.onViewModeChanged);
dashboard.on('row-collapsed', this.triggerForceUpdate); dashboard.on(CoreEvents.rowCollapsed, this.triggerForceUpdate);
dashboard.on('row-expanded', this.triggerForceUpdate); dashboard.on(CoreEvents.rowExpanded, this.triggerForceUpdate);
} }
componentWillUnmount() { componentWillUnmount() {
const { dashboard } = this.props; const { dashboard } = this.props;
dashboard.off('panel-added', this.triggerForceUpdate); dashboard.off(panelAdded, this.triggerForceUpdate);
dashboard.off('panel-removed', this.triggerForceUpdate); dashboard.off(panelRemoved, this.triggerForceUpdate);
dashboard.off('repeats-processed', this.triggerForceUpdate); dashboard.off(CoreEvents.repeatsProcessed, this.triggerForceUpdate);
dashboard.off('view-mode-changed', this.onViewModeChanged); dashboard.off(PanelEvents.viewModeChanged, this.onViewModeChanged);
dashboard.off('row-collapsed', this.triggerForceUpdate); dashboard.off(CoreEvents.rowCollapsed, this.triggerForceUpdate);
dashboard.off('row-expanded', this.triggerForceUpdate); dashboard.off(CoreEvents.rowExpanded, this.triggerForceUpdate);
} }
buildLayout() { buildLayout() {

View File

@ -14,7 +14,8 @@ import templateSrv from 'app/features/templating/template_srv';
import config from 'app/core/config'; import config from 'app/core/config';
// Types // Types
import { DashboardModel, PanelModel } from '../state'; import { DashboardModel, PanelModel } from '../state';
import { LoadingState, ScopedVars, AbsoluteTimeRange, toUtc, toDataFrameDTO, DefaultTimeRange } from '@grafana/data'; import { LoadingState, ScopedVars, AbsoluteTimeRange, DefaultTimeRange, toUtc, toDataFrameDTO } from '@grafana/data';
import { PanelEvents } from '@grafana/ui';
const DEFAULT_PLUGIN_ERROR = 'Error in plugin'; const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
@ -59,8 +60,8 @@ export class PanelChrome extends PureComponent<Props, State> {
componentDidMount() { componentDidMount() {
const { panel, dashboard } = this.props; const { panel, dashboard } = this.props;
panel.events.on('refresh', this.onRefresh); panel.events.on(PanelEvents.refresh, this.onRefresh);
panel.events.on('render', this.onRender); panel.events.on(PanelEvents.render, this.onRender);
dashboard.panelInitialized(this.props.panel); dashboard.panelInitialized(this.props.panel);
// Move snapshot data into the query response // Move snapshot data into the query response
@ -79,8 +80,7 @@ export class PanelChrome extends PureComponent<Props, State> {
} }
componentWillUnmount() { componentWillUnmount() {
this.props.panel.events.off('refresh', this.onRefresh); this.props.panel.events.off(PanelEvents.refresh, this.onRefresh);
if (this.querySubscription) { if (this.querySubscription) {
this.querySubscription.unsubscribe(); this.querySubscription.unsubscribe();
this.querySubscription = null; this.querySubscription = null;

View File

@ -9,7 +9,7 @@ import { Emitter } from 'app/core/utils/emitter';
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
// Types // Types
import { PanelModel } from '../state/PanelModel'; import { PanelModel } from '../state/PanelModel';
import { DataQuery, DataSourceApi, PanelData, DataQueryRequest, ErrorBoundaryAlert } from '@grafana/ui'; import { DataQuery, DataSourceApi, PanelData, DataQueryRequest, ErrorBoundaryAlert, PanelEvents } from '@grafana/ui';
import { TimeRange, LoadingState, toLegacyResponseData } from '@grafana/data'; import { TimeRange, LoadingState, toLegacyResponseData } from '@grafana/data';
import { DashboardModel } from '../state/DashboardModel'; import { DashboardModel } from '../state/DashboardModel';
@ -273,9 +273,9 @@ function notifyAngularQueryEditorsOfData(panel: PanelModel, data: PanelData, edi
if (data.state === LoadingState.Done) { if (data.state === LoadingState.Done) {
const legacy = data.series.map(v => toLegacyResponseData(v)); const legacy = data.series.map(v => toLegacyResponseData(v));
panel.events.emit('data-received', legacy); panel.events.emit(PanelEvents.dataReceived, legacy);
} else if (data.state === LoadingState.Error) { } else if (data.state === LoadingState.Error) {
panel.events.emit('data-error', data.error); panel.events.emit(PanelEvents.dataError, data.error);
} }
// Some query controllers listen to data error events and need a digest // Some query controllers listen to data error events and need a digest

View File

@ -1,7 +1,9 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard'; import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
import { LoadingPlaceholder, JSONFormatter } from '@grafana/ui'; import { LoadingPlaceholder, JSONFormatter, PanelEvents } from '@grafana/ui';
import { CoreEvents } from 'app/types';
import { AppEvents } from '@grafana/data';
interface DsQuery { interface DsQuery {
isLoading: boolean; isLoading: boolean;
@ -39,20 +41,20 @@ export class QueryInspector extends PureComponent<Props, State> {
componentDidMount() { componentDidMount() {
const { panel } = this.props; const { panel } = this.props;
appEvents.on('ds-request-response', this.onDataSourceResponse); appEvents.on(CoreEvents.dsRequestResponse, this.onDataSourceResponse);
appEvents.on('ds-request-error', this.onRequestError); appEvents.on(CoreEvents.dsRequestError, this.onRequestError);
panel.events.on('refresh', this.onPanelRefresh); panel.events.on(PanelEvents.refresh, this.onPanelRefresh);
panel.refresh(); panel.refresh();
} }
componentWillUnmount() { componentWillUnmount() {
const { panel } = this.props; const { panel } = this.props;
appEvents.off('ds-request-response', this.onDataSourceResponse); appEvents.off(CoreEvents.dsRequestResponse, this.onDataSourceResponse);
appEvents.on('ds-request-error', this.onRequestError); appEvents.on(CoreEvents.dsRequestError, this.onRequestError);
panel.events.off('refresh', this.onPanelRefresh); panel.events.off(PanelEvents.refresh, this.onPanelRefresh);
} }
handleMocking(response: any) { handleMocking(response: any) {
@ -61,7 +63,7 @@ export class QueryInspector extends PureComponent<Props, State> {
try { try {
mockedData = JSON.parse(mockedResponse); mockedData = JSON.parse(mockedResponse);
} catch (err) { } catch (err) {
appEvents.emit('alert-error', ['R: Failed to parse mocked response']); appEvents.emit(AppEvents.alertError, ['R: Failed to parse mocked response']);
return; return;
} }
@ -134,7 +136,7 @@ export class QueryInspector extends PureComponent<Props, State> {
}; };
onClipboardSuccess = () => { onClipboardSuccess = () => {
appEvents.emit('alert-success', ['Content copied to clipboard']); appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']);
}; };
onToggleExpand = () => { onToggleExpand = () => {

View File

@ -10,7 +10,7 @@ jest.mock('app/core/services/context_srv', () => ({
})); }));
describe('ChangeTracker', () => { describe('ChangeTracker', () => {
let rootScope; let rootScope: any;
let location; let location;
const timeout = () => {}; const timeout = () => {};
let tracker: ChangeTracker; let tracker: ChangeTracker;
@ -57,7 +57,7 @@ describe('ChangeTracker', () => {
path: jest.fn(), path: jest.fn(),
}; };
tracker = new ChangeTracker(dash, scope, undefined, location as any, window, timeout, contextSrv, rootScope); tracker = new ChangeTracker(dash, scope as any, undefined, location as any, window, timeout, contextSrv, rootScope);
}); });
it('No changes should not have changes', () => { it('No changes should not have changes', () => {

View File

@ -1,7 +1,9 @@
import angular, { ILocationService } from 'angular'; import angular, { ILocationService, IRootScopeService } from 'angular';
import _ from 'lodash'; import _ from 'lodash';
import { DashboardModel } from '../state/DashboardModel'; import { DashboardModel } from '../state/DashboardModel';
import { ContextSrv } from 'app/core/services/context_srv'; import { ContextSrv } from 'app/core/services/context_srv';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { CoreEvents, AppEventConsumer } from 'app/types';
export class ChangeTracker { export class ChangeTracker {
current: any; current: any;
@ -14,13 +16,13 @@ export class ChangeTracker {
/** @ngInject */ /** @ngInject */
constructor( constructor(
dashboard: DashboardModel, dashboard: DashboardModel,
scope: any, scope: IRootScopeService & AppEventConsumer,
originalCopyDelay: any, originalCopyDelay: any,
private $location: ILocationService, private $location: ILocationService,
$window: any, $window: any,
private $timeout: any, private $timeout: any,
private contextSrv: ContextSrv, private contextSrv: ContextSrv,
private $rootScope: any private $rootScope: GrafanaRootScope
) { ) {
this.$location = $location; this.$location = $location;
this.$window = $window; this.$window = $window;
@ -30,7 +32,7 @@ export class ChangeTracker {
this.scope = scope; this.scope = scope;
// register events // register events
scope.onAppEvent('dashboard-saved', () => { scope.onAppEvent(CoreEvents.dashboardSaved, () => {
this.original = this.current.getSaveModelClone(); this.original = this.current.getSaveModelClone();
this.originalPath = $location.path(); this.originalPath = $location.path();
}); });
@ -161,7 +163,7 @@ export class ChangeTracker {
} }
open_modal() { open_modal() {
this.$rootScope.appEvent('show-modal', { this.$rootScope.appEvent(CoreEvents.showModal, {
templateHtml: '<unsaved-changes-modal dismiss="dismiss()"></unsaved-changes-modal>', templateHtml: '<unsaved-changes-modal dismiss="dismiss()"></unsaved-changes-modal>',
modalClass: 'modal--narrow confirm-modal', modalClass: 'modal--narrow confirm-modal',
}); });
@ -176,7 +178,7 @@ export class ChangeTracker {
}); });
}); });
this.$rootScope.appEvent('save-dashboard'); this.$rootScope.appEvent(CoreEvents.saveDashboard);
} }
gotoNext() { gotoNext() {

View File

@ -4,12 +4,13 @@ import moment from 'moment';
import _ from 'lodash'; import _ from 'lodash';
import $ from 'jquery'; import $ from 'jquery';
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';
import { dateMath } from '@grafana/data'; import { dateMath, AppEvents } from '@grafana/data';
import impressionSrv from 'app/core/services/impression_srv'; import impressionSrv from 'app/core/services/impression_srv';
import { BackendSrv } from 'app/core/services/backend_srv'; import { BackendSrv } from 'app/core/services/backend_srv';
import { DashboardSrv } from './DashboardSrv'; import { DashboardSrv } from './DashboardSrv';
import DatasourceSrv from 'app/features/plugins/datasource_srv'; import DatasourceSrv from 'app/features/plugins/datasource_srv';
import { UrlQueryValue } from '@grafana/runtime'; import { UrlQueryValue } from '@grafana/runtime';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
export class DashboardLoaderSrv { export class DashboardLoaderSrv {
/** @ngInject */ /** @ngInject */
@ -22,7 +23,7 @@ export class DashboardLoaderSrv {
private $timeout: any, private $timeout: any,
contextSrv: any, contextSrv: any,
private $routeParams: any, private $routeParams: any,
private $rootScope: any private $rootScope: GrafanaRootScope
) {} ) {}
_dashboardLoadFailed(title: string, snapshot?: boolean) { _dashboardLoadFailed(title: string, snapshot?: boolean) {
@ -54,7 +55,7 @@ export class DashboardLoaderSrv {
.getDashboardByUid(uid) .getDashboardByUid(uid)
.then((result: any) => { .then((result: any) => {
if (result.meta.isFolder) { if (result.meta.isFolder) {
this.$rootScope.appEvent('alert-error', ['Dashboard not found']); this.$rootScope.appEvent(AppEvents.alertError, ['Dashboard not found']);
throw new Error('Dashboard not found'); throw new Error('Dashboard not found');
} }
return result; return result;
@ -94,7 +95,7 @@ export class DashboardLoaderSrv {
}, },
(err: any) => { (err: any) => {
console.log('Script dashboard error ' + err); console.log('Script dashboard error ' + err);
this.$rootScope.appEvent('alert-error', [ this.$rootScope.appEvent(AppEvents.alertError, [
'Script Error', 'Script Error',
'Please make sure it exists and returns a valid dashboard', 'Please make sure it exists and returns a valid dashboard',
]); ]);

View File

@ -3,9 +3,12 @@ import { appEvents } from 'app/core/app_events';
import locationUtil from 'app/core/utils/location_util'; import locationUtil from 'app/core/utils/location_util';
import { DashboardModel } from '../state/DashboardModel'; import { DashboardModel } from '../state/DashboardModel';
import { removePanel } from '../utils/panel'; import { removePanel } from '../utils/panel';
import { DashboardMeta } from 'app/types'; import { DashboardMeta, CoreEvents } from 'app/types';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { BackendSrv } from 'app/core/services/backend_srv'; import { BackendSrv } from 'app/core/services/backend_srv';
import { ILocationService } from 'angular'; import { ILocationService } from 'angular';
import { AppEvents } from '@grafana/data';
import { PanelEvents } from '@grafana/ui';
interface DashboardSaveOptions { interface DashboardSaveOptions {
folderId?: number; folderId?: number;
@ -18,10 +21,14 @@ export class DashboardSrv {
dashboard: DashboardModel; dashboard: DashboardModel;
/** @ngInject */ /** @ngInject */
constructor(private backendSrv: BackendSrv, private $rootScope: any, private $location: ILocationService) { constructor(
appEvents.on('save-dashboard', this.saveDashboard.bind(this), $rootScope); private backendSrv: BackendSrv,
appEvents.on('panel-change-view', this.onPanelChangeView); private $rootScope: GrafanaRootScope,
appEvents.on('remove-panel', this.onRemovePanel); private $location: ILocationService
) {
appEvents.on(CoreEvents.saveDashboard, this.saveDashboard.bind(this), $rootScope);
appEvents.on(PanelEvents.panelChangeView, this.onPanelChangeView);
appEvents.on(CoreEvents.removePanel, this.onRemovePanel);
// Export to react // Export to react
setDashboardSrv(this); setDashboardSrv(this);
@ -96,7 +103,7 @@ export class DashboardSrv {
if (err.data && err.data.status === 'version-mismatch') { if (err.data && err.data.status === 'version-mismatch') {
err.isHandled = true; err.isHandled = true;
this.$rootScope.appEvent('confirm-modal', { this.$rootScope.appEvent(CoreEvents.showConfirmModal, {
title: 'Conflict', title: 'Conflict',
text: 'Someone else has updated this dashboard.', text: 'Someone else has updated this dashboard.',
text2: 'Would you still like to save this dashboard?', text2: 'Would you still like to save this dashboard?',
@ -111,7 +118,7 @@ export class DashboardSrv {
if (err.data && err.data.status === 'name-exists') { if (err.data && err.data.status === 'name-exists') {
err.isHandled = true; err.isHandled = true;
this.$rootScope.appEvent('confirm-modal', { this.$rootScope.appEvent(CoreEvents.showConfirmModal, {
title: 'Conflict', title: 'Conflict',
text: 'A dashboard with the same name in selected folder already exists.', text: 'A dashboard with the same name in selected folder already exists.',
text2: 'Would you still like to save this dashboard?', text2: 'Would you still like to save this dashboard?',
@ -126,7 +133,7 @@ export class DashboardSrv {
if (err.data && err.data.status === 'plugin-dashboard') { if (err.data && err.data.status === 'plugin-dashboard') {
err.isHandled = true; err.isHandled = true;
this.$rootScope.appEvent('confirm-modal', { this.$rootScope.appEvent(CoreEvents.showConfirmModal, {
title: 'Plugin Dashboard', title: 'Plugin Dashboard',
text: err.data.message, text: err.data.message,
text2: 'Your changes will be lost when you update the plugin. Use Save As to create custom version.', text2: 'Your changes will be lost when you update the plugin. Use Save As to create custom version.',
@ -147,8 +154,8 @@ export class DashboardSrv {
this.dashboard.version = data.version; this.dashboard.version = data.version;
// important that these happen before location redirect below // important that these happen before location redirect below
this.$rootScope.appEvent('dashboard-saved', this.dashboard); this.$rootScope.appEvent(CoreEvents.dashboardSaved, this.dashboard);
this.$rootScope.appEvent('alert-success', ['Dashboard saved']); this.$rootScope.appEvent(AppEvents.alertSuccess, ['Dashboard saved']);
const newUrl = locationUtil.stripBaseFromUrl(data.url); const newUrl = locationUtil.stripBaseFromUrl(data.url);
const currentPath = this.$location.path(); const currentPath = this.$location.path();
@ -201,20 +208,20 @@ export class DashboardSrv {
} }
showDashboardProvisionedModal() { showDashboardProvisionedModal() {
this.$rootScope.appEvent('show-modal', { this.$rootScope.appEvent(CoreEvents.showModal, {
templateHtml: '<save-provisioned-dashboard-modal dismiss="dismiss()"></save-provisioned-dashboard-modal>', templateHtml: '<save-provisioned-dashboard-modal dismiss="dismiss()"></save-provisioned-dashboard-modal>',
}); });
} }
showSaveAsModal() { showSaveAsModal() {
this.$rootScope.appEvent('show-modal', { this.$rootScope.appEvent(CoreEvents.showModal, {
templateHtml: '<save-dashboard-as-modal dismiss="dismiss()"></save-dashboard-as-modal>', templateHtml: '<save-dashboard-as-modal dismiss="dismiss()"></save-dashboard-as-modal>',
modalClass: 'modal--narrow', modalClass: 'modal--narrow',
}); });
} }
showSaveModal() { showSaveModal() {
this.$rootScope.appEvent('show-modal', { this.$rootScope.appEvent(CoreEvents.showModal, {
templateHtml: '<save-dashboard-modal dismiss="dismiss()"></save-dashboard-modal>', templateHtml: '<save-dashboard-modal dismiss="dismiss()"></save-dashboard-modal>',
modalClass: 'modal--narrow', modalClass: 'modal--narrow',
}); });

View File

@ -17,6 +17,7 @@ import {
import { ITimeoutService, ILocationService } from 'angular'; import { ITimeoutService, ILocationService } from 'angular';
import { ContextSrv } from 'app/core/services/context_srv'; import { ContextSrv } from 'app/core/services/context_srv';
import { DashboardModel } from '../state/DashboardModel'; import { DashboardModel } from '../state/DashboardModel';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { getZoomedTimeRange, getShiftedTimeRange } from 'app/core/utils/timePicker'; import { getZoomedTimeRange, getShiftedTimeRange } from 'app/core/utils/timePicker';
export class TimeSrv { export class TimeSrv {
@ -30,7 +31,7 @@ export class TimeSrv {
/** @ngInject */ /** @ngInject */
constructor( constructor(
$rootScope: any, $rootScope: GrafanaRootScope,
private $timeout: ITimeoutService, private $timeout: ITimeoutService,
private $location: ILocationService, private $location: ILocationService,
private timer: any, private timer: any,

View File

@ -2,11 +2,12 @@ import angular, { IQService, ILocationService } from 'angular';
import { ChangeTracker } from './ChangeTracker'; import { ChangeTracker } from './ChangeTracker';
import { ContextSrv } from 'app/core/services/context_srv'; import { ContextSrv } from 'app/core/services/context_srv';
import { DashboardSrv } from './DashboardSrv'; import { DashboardSrv } from './DashboardSrv';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
/** @ngInject */ /** @ngInject */
export function unsavedChangesSrv( export function unsavedChangesSrv(
this: any, this: any,
$rootScope: any, $rootScope: GrafanaRootScope,
$q: IQService, $q: IQService,
$location: ILocationService, $location: ILocationService,
$timeout: any, $timeout: any,

View File

@ -11,11 +11,12 @@ import { contextSrv } from 'app/core/services/context_srv';
import sortByKeys from 'app/core/utils/sort_by_keys'; import sortByKeys from 'app/core/utils/sort_by_keys';
// Types // Types
import { PanelModel, GridPos } from './PanelModel'; import { PanelModel, GridPos, panelAdded, panelRemoved } from './PanelModel';
import { DashboardMigrator } from './DashboardMigrator'; import { DashboardMigrator } from './DashboardMigrator';
import { TimeRange, TimeZone } from '@grafana/data'; import { TimeRange, TimeZone, AppEvent } from '@grafana/data';
import { UrlQueryValue } from '@grafana/runtime'; import { UrlQueryValue } from '@grafana/runtime';
import { KIOSK_MODE_TV, DashboardMeta } from 'app/types'; import { PanelEvents } from '@grafana/ui';
import { KIOSK_MODE_TV, DashboardMeta, CoreEvents } from 'app/types';
import { toUtc, DateTimeInput, dateTime, isDateTime } from '@grafana/data'; import { toUtc, DateTimeInput, dateTime, isDateTime } from '@grafana/data';
export interface CloneOptions { export interface CloneOptions {
@ -215,15 +216,15 @@ export class DashboardModel {
panel.setViewMode(fullscreen, this.meta.isEditing); panel.setViewMode(fullscreen, this.meta.isEditing);
this.events.emit('view-mode-changed', panel); this.events.emit(PanelEvents.viewModeChanged, panel);
} }
timeRangeUpdated(timeRange: TimeRange) { timeRangeUpdated(timeRange: TimeRange) {
this.events.emit('time-range-updated', timeRange); this.events.emit(CoreEvents.timeRangeUpdated, timeRange);
} }
startRefresh() { startRefresh() {
this.events.emit('refresh'); this.events.emit(PanelEvents.refresh);
for (const panel of this.panels) { for (const panel of this.panels) {
if (!this.otherPanelInFullscreen(panel)) { if (!this.otherPanelInFullscreen(panel)) {
@ -233,7 +234,7 @@ export class DashboardModel {
} }
render() { render() {
this.events.emit('render'); this.events.emit(PanelEvents.render);
for (const panel of this.panels) { for (const panel of this.panels) {
panel.render(); panel.render();
@ -306,7 +307,7 @@ export class DashboardModel {
this.sortPanelsByGridPos(); this.sortPanelsByGridPos();
this.events.emit('panel-added', panel); this.events.emit(panelAdded, panel);
} }
sortPanelsByGridPos() { sortPanelsByGridPos() {
@ -343,7 +344,7 @@ export class DashboardModel {
_.pull(this.panels, ...panelsToRemove); _.pull(this.panels, ...panelsToRemove);
panelsToRemove.map(p => p.destroy()); panelsToRemove.map(p => p.destroy());
this.sortPanelsByGridPos(); this.sortPanelsByGridPos();
this.events.emit('repeats-processed'); this.events.emit(CoreEvents.repeatsProcessed);
} }
processRepeats() { processRepeats() {
@ -363,7 +364,7 @@ export class DashboardModel {
} }
this.sortPanelsByGridPos(); this.sortPanelsByGridPos();
this.events.emit('repeats-processed'); this.events.emit(CoreEvents.repeatsProcessed);
} }
cleanUpRowRepeats(rowPanels: PanelModel[]) { cleanUpRowRepeats(rowPanels: PanelModel[]) {
@ -596,7 +597,7 @@ export class DashboardModel {
removePanel(panel: PanelModel) { removePanel(panel: PanelModel) {
const index = _.indexOf(this.panels, panel); const index = _.indexOf(this.panels, panel);
this.panels.splice(index, 1); this.panels.splice(index, 1);
this.events.emit('panel-removed', panel); this.events.emit(panelRemoved, panel);
} }
removeRow(row: PanelModel, removePanels: boolean) { removeRow(row: PanelModel, removePanels: boolean) {
@ -761,7 +762,7 @@ export class DashboardModel {
this.sortPanelsByGridPos(); this.sortPanelsByGridPos();
// emit change event // emit change event
this.events.emit('row-expanded'); this.events.emit(CoreEvents.rowExpanded);
return; return;
} }
@ -774,7 +775,7 @@ export class DashboardModel {
row.collapsed = true; row.collapsed = true;
// emit change event // emit change event
this.events.emit('row-collapsed'); this.events.emit(CoreEvents.rowCollapsed);
} }
/** /**
@ -798,12 +799,12 @@ export class DashboardModel {
return rowPanels; return rowPanels;
} }
on(eventName: string, callback: (payload?: any) => void) { on<T>(event: AppEvent<T>, callback: (payload?: T) => void) {
this.events.on(eventName, callback); this.events.on(event, callback);
} }
off(eventName: string, callback?: (payload?: any) => void) { off<T>(event: AppEvent<T>, callback?: (payload?: T) => void) {
this.events.off(eventName, callback); this.events.off(event, callback);
} }
cycleGraphTooltip() { cycleGraphTooltip() {
@ -911,7 +912,7 @@ export class DashboardModel {
templateVariableValueUpdated() { templateVariableValueUpdated() {
this.processRepeats(); this.processRepeats();
this.events.emit('template-variable-value-updated'); this.events.emit(CoreEvents.templateVariableValueUpdated);
} }
expandParentRowFor(panelId: number) { expandParentRowFor(panelId: number) {

View File

@ -1,5 +1,6 @@
import { PanelModel } from './PanelModel'; import { PanelModel } from './PanelModel';
import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks'; import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks';
import { PanelEvents } from '@grafana/ui';
class TablePanelCtrl {} class TablePanelCtrl {}
@ -144,7 +145,7 @@ describe('PanelModel', () => {
let tearDownPublished = false; let tearDownPublished = false;
beforeEach(() => { beforeEach(() => {
model.events.on('panel-teardown', () => { model.events.on(PanelEvents.panelTeardown, () => {
tearDownPublished = true; tearDownPublished = true;
}); });
model.changePlugin(getPanelPlugin({ id: 'graph' })); model.changePlugin(getPanelPlugin({ id: 'graph' }));

View File

@ -4,12 +4,16 @@ import _ from 'lodash';
import { Emitter } from 'app/core/utils/emitter'; import { Emitter } from 'app/core/utils/emitter';
import { getNextRefIdChar } from 'app/core/utils/query'; import { getNextRefIdChar } from 'app/core/utils/query';
// Types // Types
import { DataQuery, DataQueryResponseData, PanelPlugin } from '@grafana/ui'; import { DataQuery, DataQueryResponseData, PanelPlugin, PanelEvents } from '@grafana/ui';
import { DataLink, DataTransformerConfig, ScopedVars } from '@grafana/data'; import { DataLink, DataTransformerConfig, ScopedVars } from '@grafana/data';
import config from 'app/core/config'; import config from 'app/core/config';
import { PanelQueryRunner } from './PanelQueryRunner'; import { PanelQueryRunner } from './PanelQueryRunner';
import { eventFactory } from '@grafana/data';
export const panelAdded = eventFactory<PanelModel | undefined>('panel-added');
export const panelRemoved = eventFactory<PanelModel | undefined>('panel-removed');
export interface GridPos { export interface GridPos {
x: number; x: number;
@ -179,7 +183,7 @@ export class PanelModel {
setViewMode(fullscreen: boolean, isEditing: boolean) { setViewMode(fullscreen: boolean, isEditing: boolean) {
this.fullscreen = fullscreen; this.fullscreen = fullscreen;
this.isEditing = isEditing; this.isEditing = isEditing;
this.events.emit('view-mode-changed'); this.events.emit(PanelEvents.viewModeChanged);
} }
updateGridPos(newPos: GridPos) { updateGridPos(newPos: GridPos) {
@ -195,29 +199,29 @@ export class PanelModel {
this.gridPos.h = newPos.h; this.gridPos.h = newPos.h;
if (sizeChanged) { if (sizeChanged) {
this.events.emit('panel-size-changed'); this.events.emit(PanelEvents.panelSizeChanged);
} }
} }
resizeDone() { resizeDone() {
this.events.emit('panel-size-changed'); this.events.emit(PanelEvents.panelSizeChanged);
} }
refresh() { refresh() {
this.hasRefreshed = true; this.hasRefreshed = true;
this.events.emit('refresh'); this.events.emit(PanelEvents.refresh);
} }
render() { render() {
if (!this.hasRefreshed) { if (!this.hasRefreshed) {
this.refresh(); this.refresh();
} else { } else {
this.events.emit('render'); this.events.emit(PanelEvents.render);
} }
} }
initialized() { initialized() {
this.events.emit('panel-initialized'); this.events.emit(PanelEvents.panelInitialized);
} }
private getOptionsToRemember() { private getOptionsToRemember() {
@ -343,7 +347,7 @@ export class PanelModel {
} }
destroy() { destroy() {
this.events.emit('panel-teardown'); this.events.emit(PanelEvents.panelTeardown);
this.events.removeAllListeners(); this.events.removeAllListeners();
if (this.queryRunner) { if (this.queryRunner) {

View File

@ -3,8 +3,8 @@ import store from 'app/core/store';
// Models // Models
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
import { PanelModel } from 'app/features/dashboard/state/PanelModel'; import { PanelModel, panelRemoved, panelAdded } from 'app/features/dashboard/state/PanelModel';
import { TimeRange } from '@grafana/data'; import { TimeRange, AppEvents } from '@grafana/data';
// Utils // Utils
import { isString as _isString } from 'lodash'; import { isString as _isString } from 'lodash';
@ -18,6 +18,7 @@ import templateSrv from 'app/features/templating/template_srv';
// Constants // Constants
import { LS_PANEL_COPY_KEY, PANEL_BORDER } from 'app/core/constants'; import { LS_PANEL_COPY_KEY, PANEL_BORDER } from 'app/core/constants';
import { CoreEvents } from 'app/types';
export const removePanel = (dashboard: DashboardModel, panel: PanelModel, ask: boolean) => { export const removePanel = (dashboard: DashboardModel, panel: PanelModel, ask: boolean) => {
// confirm deletion // confirm deletion
@ -25,7 +26,7 @@ export const removePanel = (dashboard: DashboardModel, panel: PanelModel, ask: b
const text2 = panel.alert ? 'Panel includes an alert rule, removing panel will also remove alert rule' : null; const text2 = panel.alert ? 'Panel includes an alert rule, removing panel will also remove alert rule' : null;
const confirmText = panel.alert ? 'YES' : null; const confirmText = panel.alert ? 'YES' : null;
appEvents.emit('confirm-modal', { appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Remove Panel', title: 'Remove Panel',
text: 'Are you sure you want to remove this panel?', text: 'Are you sure you want to remove this panel?',
text2: text2, text2: text2,
@ -45,7 +46,7 @@ export const duplicatePanel = (dashboard: DashboardModel, panel: PanelModel) =>
export const copyPanel = (panel: PanelModel) => { export const copyPanel = (panel: PanelModel) => {
store.set(LS_PANEL_COPY_KEY, JSON.stringify(panel.getSaveModel())); store.set(LS_PANEL_COPY_KEY, JSON.stringify(panel.getSaveModel()));
appEvents.emit('alert-success', ['Panel copied. Open Add Panel to paste']); appEvents.emit(AppEvents.alertSuccess, ['Panel copied. Open Add Panel to paste']);
}; };
const replacePanel = (dashboard: DashboardModel, newPanel: PanelModel, oldPanel: PanelModel) => { const replacePanel = (dashboard: DashboardModel, newPanel: PanelModel, oldPanel: PanelModel) => {
@ -53,15 +54,15 @@ const replacePanel = (dashboard: DashboardModel, newPanel: PanelModel, oldPanel:
return panel.id === oldPanel.id; return panel.id === oldPanel.id;
}); });
const deletedPanel = dashboard.panels.splice(index, 1); const deletedPanel = dashboard.panels.splice(index, 1)[0];
dashboard.events.emit('panel-removed', deletedPanel); dashboard.events.emit(panelRemoved, deletedPanel);
newPanel = new PanelModel(newPanel); newPanel = new PanelModel(newPanel);
newPanel.id = oldPanel.id; newPanel.id = oldPanel.id;
dashboard.panels.splice(index, 0, newPanel); dashboard.panels.splice(index, 0, newPanel);
dashboard.sortPanelsByGridPos(); dashboard.sortPanelsByGridPos();
dashboard.events.emit('panel-added', newPanel); dashboard.events.emit(panelAdded, newPanel);
}; };
export const editPanelJson = (dashboard: DashboardModel, panel: PanelModel) => { export const editPanelJson = (dashboard: DashboardModel, panel: PanelModel) => {
@ -74,14 +75,14 @@ export const editPanelJson = (dashboard: DashboardModel, panel: PanelModel) => {
enableCopy: true, enableCopy: true,
}; };
appEvents.emit('show-modal', { appEvents.emit(CoreEvents.showModal, {
src: 'public/app/partials/edit_json.html', src: 'public/app/partials/edit_json.html',
model: model, model: model,
}); });
}; };
export const sharePanel = (dashboard: DashboardModel, panel: PanelModel) => { export const sharePanel = (dashboard: DashboardModel, panel: PanelModel) => {
appEvents.emit('show-modal', { appEvents.emit(CoreEvents.showModal, {
src: 'public/app/features/dashboard/components/ShareModal/template.html', src: 'public/app/features/dashboard/components/ShareModal/template.html',
model: { model: {
dashboard: dashboard, dashboard: dashboard,

View File

@ -29,7 +29,7 @@ import { getNavModel } from 'app/core/selectors/navModel';
import { getRouteParamsId } from 'app/core/selectors/location'; import { getRouteParamsId } from 'app/core/selectors/location';
// Types // Types
import { StoreState } from 'app/types/'; import { StoreState, CoreEvents } from 'app/types/';
import { UrlQueryMap } from '@grafana/runtime'; import { UrlQueryMap } from '@grafana/runtime';
import { DataSourceSettings, DataSourcePluginMeta } from '@grafana/ui'; import { DataSourceSettings, DataSourcePluginMeta } from '@grafana/ui';
import { NavModel } from '@grafana/data'; import { NavModel } from '@grafana/data';
@ -114,7 +114,7 @@ export class DataSourceSettingsPage extends PureComponent<Props, State> {
}; };
onDelete = () => { onDelete = () => {
appEvents.emit('confirm-modal', { appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Delete', title: 'Delete',
text: 'Are you sure you want to delete this data source?', text: 'Are you sure you want to delete this data source?',
yesText: 'Delete', yesText: 'Delete',

View File

@ -11,7 +11,7 @@ import {
} from 'app/core/utils/explore'; } from 'app/core/utils/explore';
import { ExploreItemState, ExploreState, ExploreId, ExploreUpdateState, ExploreMode } from 'app/types/explore'; import { ExploreItemState, ExploreState, ExploreId, ExploreUpdateState, ExploreMode } from 'app/types/explore';
import { LoadingState, toLegacyResponseData, DefaultTimeRange } from '@grafana/data'; import { LoadingState, toLegacyResponseData, DefaultTimeRange } from '@grafana/data';
import { DataQuery, DataSourceApi, PanelData, DataQueryRequest, RefreshPicker } from '@grafana/ui'; import { DataQuery, DataSourceApi, PanelData, DataQueryRequest, RefreshPicker, PanelEvents } from '@grafana/ui';
import { import {
HigherOrderAction, HigherOrderAction,
ActionTypes, ActionTypes,
@ -600,7 +600,7 @@ export const processQueryResponse = (
} }
// For Angular editors // For Angular editors
state.eventBridge.emit('data-error', error); state.eventBridge.emit(PanelEvents.dataError, error);
return { return {
...state, ...state,
@ -624,7 +624,7 @@ export const processQueryResponse = (
if (state.datasourceInstance.components.QueryCtrl) { if (state.datasourceInstance.components.QueryCtrl) {
const legacy = series.map(v => toLegacyResponseData(v)); const legacy = series.map(v => toLegacyResponseData(v));
state.eventBridge.emit('data-received', legacy); state.eventBridge.emit(PanelEvents.dataReceived, legacy);
} }
return { return {

View File

@ -4,6 +4,7 @@ import { BackendSrv } from 'app/core/services/backend_srv';
import { ILocationService } from 'angular'; import { ILocationService } from 'angular';
import { ValidationSrv } from 'app/features/manage-dashboards'; import { ValidationSrv } from 'app/features/manage-dashboards';
import { NavModelSrv } from 'app/core/nav_model_srv'; import { NavModelSrv } from 'app/core/nav_model_srv';
import { AppEvents } from '@grafana/data';
export default class CreateFolderCtrl { export default class CreateFolderCtrl {
title = ''; title = '';
@ -28,7 +29,7 @@ export default class CreateFolderCtrl {
} }
return this.backendSrv.createFolder({ title: this.title }).then((result: any) => { return this.backendSrv.createFolder({ title: this.title }).then((result: any) => {
appEvents.emit('alert-success', ['Folder Created', 'OK']); appEvents.emit(AppEvents.alertSuccess, ['Folder Created', 'OK']);
this.$location.url(locationUtil.stripBaseFromUrl(result.url)); this.$location.url(locationUtil.stripBaseFromUrl(result.url));
}); });
} }

View File

@ -6,7 +6,7 @@ import { Input } from '@grafana/ui';
import Page from 'app/core/components/Page/Page'; import Page from 'app/core/components/Page/Page';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { getNavModel } from 'app/core/selectors/navModel'; import { getNavModel } from 'app/core/selectors/navModel';
import { StoreState, FolderState } from 'app/types'; import { StoreState, FolderState, CoreEvents } from 'app/types';
import { getFolderByUid, setFolderTitle, saveFolder, deleteFolder } from './state/actions'; import { getFolderByUid, setFolderTitle, saveFolder, deleteFolder } from './state/actions';
import { getLoadingNav } from './state/navModel'; import { getLoadingNav } from './state/navModel';
@ -52,7 +52,7 @@ export class FolderSettingsPage extends PureComponent<Props, State> {
evt.stopPropagation(); evt.stopPropagation();
evt.preventDefault(); evt.preventDefault();
appEvents.emit('confirm-modal', { appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Delete', title: 'Delete',
text: `Do you want to delete this folder and all its dashboards?`, text: `Do you want to delete this folder and all its dashboards?`,
icon: 'fa-trash', icon: 'fa-trash',

View File

@ -13,6 +13,7 @@ import {
import { updateNavIndex, updateLocation } from 'app/core/actions'; import { updateNavIndex, updateLocation } from 'app/core/actions';
import { buildNavModel } from './navModel'; import { buildNavModel } from './navModel';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { AppEvents } from '@grafana/data';
export enum ActionTypes { export enum ActionTypes {
LoadFolder = 'LOAD_FOLDER', LoadFolder = 'LOAD_FOLDER',
@ -71,7 +72,7 @@ export function saveFolder(folder: FolderState): ThunkResult<void> {
}); });
// this should be redux action at some point // this should be redux action at some point
appEvents.emit('alert-success', ['Folder saved']); appEvents.emit(AppEvents.alertSuccess, ['Folder saved']);
dispatch(updateLocation({ path: `${res.url}/settings` })); dispatch(updateLocation({ path: `${res.url}/settings` }));
}; };

View File

@ -2,6 +2,8 @@ import _ from 'lodash';
import { NavModelSrv } from 'app/core/core'; import { NavModelSrv } from 'app/core/core';
import { ILocationService } from 'angular'; import { ILocationService } from 'angular';
import { BackendSrv } from '@grafana/runtime'; import { BackendSrv } from '@grafana/runtime';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { CoreEvents } from 'app/types';
export class SnapshotListCtrl { export class SnapshotListCtrl {
navModel: any; navModel: any;
@ -9,7 +11,7 @@ export class SnapshotListCtrl {
/** @ngInject */ /** @ngInject */
constructor( constructor(
private $rootScope: any, private $rootScope: GrafanaRootScope,
private backendSrv: BackendSrv, private backendSrv: BackendSrv,
navModelSrv: NavModelSrv, navModelSrv: NavModelSrv,
private $location: ILocationService private $location: ILocationService
@ -35,7 +37,7 @@ export class SnapshotListCtrl {
} }
removeSnapshot(snapshot: any) { removeSnapshot(snapshot: any) {
this.$rootScope.appEvent('confirm-modal', { this.$rootScope.appEvent(CoreEvents.showConfirmModal, {
title: 'Delete', title: 'Delete',
text: 'Are you sure you want to delete snapshot ' + snapshot.name + '?', text: 'Are you sure you want to delete snapshot ' + snapshot.name + '?',
yesText: 'Delete', yesText: 'Delete',

View File

@ -1,6 +1,7 @@
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { BackendSrv } from 'app/core/services/backend_srv'; import { BackendSrv } from 'app/core/services/backend_srv';
import { AppEvents } from '@grafana/data';
export class MoveToFolderCtrl { export class MoveToFolderCtrl {
dashboards: any; dashboards: any;
@ -23,11 +24,11 @@ export class MoveToFolderCtrl {
const msg = `${result.successCount} dashboard${result.successCount === 1 ? '' : 's'} moved to ${ const msg = `${result.successCount} dashboard${result.successCount === 1 ? '' : 's'} moved to ${
this.folder.title this.folder.title
}`; }`;
appEvents.emit('alert-success', [header, msg]); appEvents.emit(AppEvents.alertSuccess, [header, msg]);
} }
if (result.totalCount === result.alreadyInFolderCount) { if (result.totalCount === result.alreadyInFolderCount) {
appEvents.emit('alert-error', ['Error', `Dashboards already belongs to folder ${this.folder.title}`]); appEvents.emit(AppEvents.alertError, ['Error', `Dashboards already belongs to folder ${this.folder.title}`]);
} }
this.dismiss(); this.dismiss();

View File

@ -1,6 +1,7 @@
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import angular, { ILocationService } from 'angular'; import angular, { ILocationService } from 'angular';
import { AppEvents } from '@grafana/data';
const template = ` const template = `
<input type="file" id="dashupload" name="dashupload" class="hide" onchange="angular.element(this).scope().file_selected"/> <input type="file" id="dashupload" name="dashupload" class="hide" onchange="angular.element(this).scope().file_selected"/>
@ -30,7 +31,10 @@ export function uploadDashboardDirective(timer: any, $location: ILocationService
dash = JSON.parse(e.target.result); dash = JSON.parse(e.target.result);
} catch (err) { } catch (err) {
console.log(err); console.log(err);
appEvents.emit('alert-error', ['Import failed', 'JSON -> JS Serialization failed: ' + err.message]); appEvents.emit(AppEvents.alertError, [
'Import failed',
'JSON -> JS Serialization failed: ' + err.message,
]);
return; return;
} }
@ -58,7 +62,7 @@ export function uploadDashboardDirective(timer: any, $location: ILocationService
// Something // Something
elem[0].addEventListener('change', file_selected, false); elem[0].addEventListener('change', file_selected, false);
} else { } else {
appEvents.emit('alert-error', ['Oops', 'The HTML5 File APIs are not fully supported in this browser']); appEvents.emit(AppEvents.alertError, ['Oops', 'The HTML5 File APIs are not fully supported in this browser']);
} }
}, },
}; };

View File

@ -6,11 +6,12 @@ import { PanelCtrl } from 'app/features/panel/panel_ctrl';
import { getExploreUrl } from 'app/core/utils/explore'; import { getExploreUrl } from 'app/core/utils/explore';
import { applyPanelTimeOverrides, getResolution } from 'app/features/dashboard/utils/panel'; import { applyPanelTimeOverrides, getResolution } from 'app/features/dashboard/utils/panel';
import { ContextSrv } from 'app/core/services/context_srv'; import { ContextSrv } from 'app/core/services/context_srv';
import { toLegacyResponseData, TimeRange, LoadingState, DataFrame, toDataFrameDTO } from '@grafana/data'; import { toLegacyResponseData, toDataFrameDTO, TimeRange, LoadingState, DataFrame } from '@grafana/data';
import { LegacyResponseData, DataSourceApi, PanelData, DataQueryResponse } from '@grafana/ui'; import { LegacyResponseData, DataSourceApi, PanelData, DataQueryResponse, PanelEvents } from '@grafana/ui';
import { Unsubscribable } from 'rxjs'; import { Unsubscribable } from 'rxjs';
import { PanelModel } from 'app/features/dashboard/state'; import { PanelModel } from 'app/features/dashboard/state';
import { CoreEvents } from 'app/types';
class MetricsPanelCtrl extends PanelCtrl { class MetricsPanelCtrl extends PanelCtrl {
scope: any; scope: any;
@ -42,8 +43,8 @@ class MetricsPanelCtrl extends PanelCtrl {
this.scope = $scope; this.scope = $scope;
this.panel.datasource = this.panel.datasource || null; this.panel.datasource = this.panel.datasource || null;
this.events.on('refresh', this.onMetricsPanelRefresh.bind(this)); this.events.on(PanelEvents.refresh, this.onMetricsPanelRefresh.bind(this));
this.events.on('panel-teardown', this.onPanelTearDown.bind(this)); this.events.on(PanelEvents.panelTeardown, this.onPanelTearDown.bind(this));
} }
private onPanelTearDown() { private onPanelTearDown() {
@ -71,7 +72,7 @@ class MetricsPanelCtrl extends PanelCtrl {
// Defer panel rendering till the next digest cycle. // Defer panel rendering till the next digest cycle.
// For some reason snapshot panels don't init at this time, so this helps to avoid rendering issues. // For some reason snapshot panels don't init at this time, so this helps to avoid rendering issues.
return this.$timeout(() => { return this.$timeout(() => {
this.events.emit('data-snapshot-load', data); this.events.emit(PanelEvents.dataSnapshotLoad, data);
}); });
} }
@ -111,7 +112,7 @@ class MetricsPanelCtrl extends PanelCtrl {
console.log('Panel data error:', err); console.log('Panel data error:', err);
return this.$timeout(() => { return this.$timeout(() => {
this.events.emit('data-error', err); this.events.emit(PanelEvents.dataError, err);
}); });
} }
@ -214,7 +215,7 @@ class MetricsPanelCtrl extends PanelCtrl {
} }
try { try {
this.events.emit('data-frames-received', data); this.events.emit(CoreEvents.dataFramesReceived, data);
} catch (err) { } catch (err) {
this.processDataError(err); this.processDataError(err);
} }
@ -233,7 +234,7 @@ class MetricsPanelCtrl extends PanelCtrl {
} }
try { try {
this.events.emit('data-received', result.data); this.events.emit(PanelEvents.dataReceived, result.data);
} catch (err) { } catch (err) {
this.processDataError(err); this.processDataError(err);
} }

View File

@ -1,6 +1,6 @@
import _ from 'lodash'; import _ from 'lodash';
import { sanitize, escapeHtml } from 'app/core/utils/text'; import { sanitize, escapeHtml } from 'app/core/utils/text';
import { renderMarkdown } from '@grafana/data'; import { renderMarkdown, AppEvent } from '@grafana/data';
import config from 'app/core/config'; import config from 'app/core/config';
import { profiler } from 'app/core/core'; import { profiler } from 'app/core/core';
@ -14,12 +14,12 @@ import {
sharePanel as sharePanelUtil, sharePanel as sharePanelUtil,
calculateInnerPanelHeight, calculateInnerPanelHeight,
} from 'app/features/dashboard/utils/panel'; } from 'app/features/dashboard/utils/panel';
import { GRID_COLUMN_COUNT } from 'app/core/constants'; import { GRID_COLUMN_COUNT } from 'app/core/constants';
import { auto } from 'angular'; import { auto } from 'angular';
import { TemplateSrv } from '../templating/template_srv'; import { TemplateSrv } from '../templating/template_srv';
import { PanelPluginMeta } from '@grafana/ui/src/types/panel'; import { PanelPluginMeta } from '@grafana/ui/src/types/panel';
import { getPanelLinksSupplier } from './panellinks/linkSuppliers'; import { getPanelLinksSupplier } from './panellinks/linkSuppliers';
import { PanelEvents } from '@grafana/ui';
export class PanelCtrl { export class PanelCtrl {
panel: any; panel: any;
@ -41,6 +41,7 @@ export class PanelCtrl {
timing: any; timing: any;
maxPanelsPerRowOptions: number[]; maxPanelsPerRowOptions: number[];
/** @ngInject */
constructor($scope: any, $injector: auto.IInjectorService) { constructor($scope: any, $injector: auto.IInjectorService) {
this.$injector = $injector; this.$injector = $injector;
this.$location = $injector.get('$location'); this.$location = $injector.get('$location');
@ -56,11 +57,11 @@ export class PanelCtrl {
this.pluginName = plugin.name; this.pluginName = plugin.name;
} }
$scope.$on('component-did-mount', () => this.panelDidMount()); $scope.$on(PanelEvents.componentDidMount.name, () => this.panelDidMount());
} }
panelDidMount() { panelDidMount() {
this.events.emit('component-did-mount'); this.events.emit(PanelEvents.componentDidMount);
this.dashboard.panelInitialized(this.panel); this.dashboard.panelInitialized(this.panel);
} }
@ -72,12 +73,12 @@ export class PanelCtrl {
this.panel.refresh(); this.panel.refresh();
} }
publishAppEvent(evtName: string, evt: any) { publishAppEvent<T>(event: AppEvent<T>, payload?: T) {
this.$scope.$root.appEvent(evtName, evt); this.$scope.$root.appEvent(event, payload);
} }
changeView(fullscreen: boolean, edit: boolean) { changeView(fullscreen: boolean, edit: boolean) {
this.publishAppEvent('panel-change-view', { this.publishAppEvent(PanelEvents.panelChangeView, {
fullscreen, fullscreen,
edit, edit,
panelId: this.panel.id, panelId: this.panel.id,
@ -99,7 +100,7 @@ export class PanelCtrl {
initEditMode() { initEditMode() {
if (!this.editModeInitiated) { if (!this.editModeInitiated) {
this.editModeInitiated = true; this.editModeInitiated = true;
this.events.emit('init-edit-mode', null); this.events.emit(PanelEvents.editModeInitialized);
this.maxPanelsPerRowOptions = getFactors(GRID_COLUMN_COUNT); this.maxPanelsPerRowOptions = getFactors(GRID_COLUMN_COUNT);
} }
} }
@ -193,7 +194,7 @@ export class PanelCtrl {
click: 'ctrl.editPanelJson(); dismiss();', click: 'ctrl.editPanelJson(); dismiss();',
}); });
this.events.emit('init-panel-actions', menu); this.events.emit(PanelEvents.initPanelActions, menu);
return menu; return menu;
} }
@ -212,7 +213,7 @@ export class PanelCtrl {
} }
render(payload?: any) { render(payload?: any) {
this.events.emit('render', payload); this.events.emit(PanelEvents.render, payload);
} }
duplicate() { duplicate() {

View File

@ -4,6 +4,7 @@ import $ from 'jquery';
import Drop from 'tether-drop'; import Drop from 'tether-drop';
// @ts-ignore // @ts-ignore
import baron from 'baron'; import baron from 'baron';
import { PanelEvents } from '@grafana/ui';
const module = angular.module('grafana.directives'); const module = angular.module('grafana.directives');
@ -73,7 +74,7 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => {
} }
// update scrollbar after mounting // update scrollbar after mounting
ctrl.events.on('component-did-mount', () => { ctrl.events.on(PanelEvents.componentDidMount, () => {
if (ctrl.__proto__.constructor.scrollable) { if (ctrl.__proto__.constructor.scrollable) {
const scrollRootClass = 'baron baron__root baron__clipper panel-content--scrollable'; const scrollRootClass = 'baron baron__root baron__clipper panel-content--scrollable';
const scrollerClass = 'baron__scroller'; const scrollerClass = 'baron__scroller';
@ -102,7 +103,7 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => {
} }
}); });
ctrl.events.on('panel-size-changed', () => { ctrl.events.on(PanelEvents.panelSizeChanged, () => {
ctrl.calculatePanelHeight(panelContainer[0].offsetHeight); ctrl.calculatePanelHeight(panelContainer[0].offsetHeight);
$timeout(() => { $timeout(() => {
resizeScrollableContent(); resizeScrollableContent();
@ -110,7 +111,7 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => {
}); });
}); });
ctrl.events.on('view-mode-changed', () => { ctrl.events.on(PanelEvents.viewModeChanged, () => {
// first wait one pass for dashboard fullscreen view mode to take effect (classses being applied) // first wait one pass for dashboard fullscreen view mode to take effect (classses being applied)
setTimeout(() => { setTimeout(() => {
// then recalc style // then recalc style
@ -123,7 +124,7 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => {
}, 10); }, 10);
}); });
ctrl.events.on('render', () => { ctrl.events.on(PanelEvents.render, () => {
// set initial height // set initial height
if (!ctrl.height) { if (!ctrl.height) {
ctrl.calculatePanelHeight(panelContainer[0].offsetHeight); ctrl.calculatePanelHeight(panelContainer[0].offsetHeight);

View File

@ -3,6 +3,8 @@ import coreModule from '../../core/core_module';
import { ILocationService } from 'angular'; import { ILocationService } from 'angular';
import { BackendSrv } from 'app/core/services/backend_srv'; import { BackendSrv } from 'app/core/services/backend_srv';
import { NavModelSrv } from 'app/core/nav_model_srv'; import { NavModelSrv } from 'app/core/nav_model_srv';
import { AppEventEmitter } from 'app/types';
import { AppEvents } from '@grafana/data';
export interface PlaylistItem { export interface PlaylistItem {
value: any; value: any;
@ -27,7 +29,7 @@ export class PlaylistEditCtrl {
/** @ngInject */ /** @ngInject */
constructor( constructor(
private $scope: any, private $scope: AppEventEmitter,
private backendSrv: BackendSrv, private backendSrv: BackendSrv,
private $location: ILocationService, private $location: ILocationService,
$route: any, $route: any,
@ -102,11 +104,11 @@ export class PlaylistEditCtrl {
savePromise.then( savePromise.then(
() => { () => {
this.$scope.appEvent('alert-success', ['Playlist saved', '']); this.$scope.appEvent(AppEvents.alertSuccess, ['Playlist saved']);
this.$location.path('/playlists'); this.$location.path('/playlists');
}, },
() => { () => {
this.$scope.appEvent('alert-error', ['Unable to save playlist', '']); this.$scope.appEvent(AppEvents.alertError, ['Unable to save playlist']);
} }
); );
} }

View File

@ -8,6 +8,7 @@ import appEvents from 'app/core/app_events';
import locationUtil from 'app/core/utils/location_util'; import locationUtil from 'app/core/utils/location_util';
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';
import { store } from 'app/store/store'; import { store } from 'app/store/store';
import { CoreEvents } from 'app/types';
export const queryParamsToPreserve: { [key: string]: boolean } = { export const queryParamsToPreserve: { [key: string]: boolean } = {
kiosk: true, kiosk: true,
@ -86,7 +87,7 @@ export class PlaylistSrv {
this.storeUnsub = store.subscribe(() => this.storeUpdated()); this.storeUnsub = store.subscribe(() => this.storeUpdated());
this.validPlaylistUrl = this.$location.path(); this.validPlaylistUrl = this.$location.path();
appEvents.emit('playlist-started'); appEvents.emit(CoreEvents.playlistStarted);
return this.backendSrv.get(`/api/playlists/${playlistId}`).then((playlist: any) => { return this.backendSrv.get(`/api/playlists/${playlistId}`).then((playlist: any) => {
return this.backendSrv.get(`/api/playlists/${playlistId}/dashboards`).then((dashboards: any) => { return this.backendSrv.get(`/api/playlists/${playlistId}/dashboards`).then((dashboards: any) => {
@ -101,7 +102,7 @@ export class PlaylistSrv {
if (this.isPlaying) { if (this.isPlaying) {
const queryParams = this.$location.search(); const queryParams = this.$location.search();
if (queryParams.kiosk) { if (queryParams.kiosk) {
appEvents.emit('toggle-kiosk-mode', { exit: true }); appEvents.emit(CoreEvents.toggleKioskMode, { exit: true });
} }
} }
@ -116,7 +117,7 @@ export class PlaylistSrv {
this.$timeout.cancel(this.cancelPromise); this.$timeout.cancel(this.cancelPromise);
} }
appEvents.emit('playlist-stopped'); appEvents.emit(CoreEvents.playlistStopped);
} }
} }

View File

@ -2,6 +2,8 @@ import _ from 'lodash';
import coreModule from '../../core/core_module'; import coreModule from '../../core/core_module';
import { BackendSrv } from '@grafana/runtime'; import { BackendSrv } from '@grafana/runtime';
import { NavModelSrv } from 'app/core/nav_model_srv'; import { NavModelSrv } from 'app/core/nav_model_srv';
import { CoreEvents } from 'app/types';
import { AppEvents } from '@grafana/data';
export class PlaylistsCtrl { export class PlaylistsCtrl {
playlists: any; playlists: any;
@ -24,17 +26,17 @@ export class PlaylistsCtrl {
this.backendSrv.delete('/api/playlists/' + playlist.id).then( this.backendSrv.delete('/api/playlists/' + playlist.id).then(
() => { () => {
this.$scope.appEvent('alert-success', ['Playlist deleted', '']); this.$scope.appEvent(AppEvents.alertSuccess, ['Playlist deleted']);
}, },
() => { () => {
this.$scope.appEvent('alert-error', ['Unable to delete playlist', '']); this.$scope.appEvent(AppEvents.alertError, ['Unable to delete playlist']);
this.playlists.push(playlist); this.playlists.push(playlist);
} }
); );
} }
removePlaylist(playlist: any) { removePlaylist(playlist: any) {
this.$scope.appEvent('confirm-modal', { this.$scope.appEvent(CoreEvents.showConfirmModal, {
title: 'Delete', title: 'Delete',
text: 'Are you sure you want to delete playlist ' + playlist.name + '?', text: 'Are you sure you want to delete playlist ' + playlist.name + '?',
yesText: 'Delete', yesText: 'Delete',

View File

@ -11,7 +11,7 @@ import Page from 'app/core/components/Page/Page';
import { getPluginSettings } from './PluginSettingsCache'; import { getPluginSettings } from './PluginSettingsCache';
import { importAppPlugin } from './plugin_loader'; import { importAppPlugin } from './plugin_loader';
import { AppPlugin, AppPluginMeta, PluginType } from '@grafana/ui'; import { AppPlugin, AppPluginMeta, PluginType } from '@grafana/ui';
import { NavModel } from '@grafana/data'; import { NavModel, AppEvents } from '@grafana/data';
import { getLoadingNav } from './PluginPage'; import { getLoadingNav } from './PluginPage';
import { getNotFoundNav, getWarningNav } from 'app/core/nav_model_srv'; import { getNotFoundNav, getWarningNav } from 'app/core/nav_model_srv';
import { appEvents } from 'app/core/core'; import { appEvents } from 'app/core/core';
@ -58,7 +58,7 @@ class AppRootPage extends Component<Props, State> {
const app = await getPluginSettings(pluginId).then(info => { const app = await getPluginSettings(pluginId).then(info => {
const error = getAppPluginPageError(info); const error = getAppPluginPageError(info);
if (error) { if (error) {
appEvents.emit('alert-error', [error]); appEvents.emit(AppEvents.alertError, [error]);
this.setState({ nav: getWarningNav(error) }); this.setState({ nav: getWarningNav(error) });
return null; return null;
} }

View File

@ -7,6 +7,7 @@ import { PluginDashboard } from 'app/types';
import { getBackendSrv } from 'app/core/services/backend_srv'; import { getBackendSrv } from 'app/core/services/backend_srv';
import { appEvents } from 'app/core/core'; import { appEvents } from 'app/core/core';
import DashboardsTable from 'app/features/datasources/DashboardsTable'; import DashboardsTable from 'app/features/datasources/DashboardsTable';
import { AppEvents } from '@grafana/data';
interface Props { interface Props {
plugin: PluginMeta; plugin: PluginMeta;
@ -79,7 +80,7 @@ export class PluginDashboards extends PureComponent<Props, State> {
return getBackendSrv() return getBackendSrv()
.post(`/api/dashboards/import`, installCmd) .post(`/api/dashboards/import`, installCmd)
.then((res: PluginDashboard) => { .then((res: PluginDashboard) => {
appEvents.emit('alert-success', ['Dashboard Imported', dash.title]); appEvents.emit(AppEvents.alertSuccess, ['Dashboard Imported', dash.title]);
extend(dash, res); extend(dash, res);
this.setState({ dashboards: [...this.state.dashboards] }); this.setState({ dashboards: [...this.state.dashboards] });
}); });

View File

@ -5,7 +5,7 @@ import { connect } from 'react-redux';
import find from 'lodash/find'; import find from 'lodash/find';
// Types // Types
import { UrlQueryMap } from '@grafana/runtime'; import { UrlQueryMap } from '@grafana/runtime';
import { AppNotificationSeverity, StoreState } from 'app/types'; import { StoreState, AppNotificationSeverity, CoreEvents } from 'app/types';
import { import {
Alert, Alert,
AppPlugin, AppPlugin,
@ -173,7 +173,7 @@ class PluginPage extends PureComponent<Props, State> {
} }
showUpdateInfo = () => { showUpdateInfo = () => {
appEvents.emit('show-modal', { appEvents.emit(CoreEvents.showModal, {
src: 'public/app/features/plugins/partials/update_instructions.html', src: 'public/app/features/plugins/partials/update_instructions.html',
model: this.state.plugin.meta, model: this.state.plugin.meta,
}); });

View File

@ -9,9 +9,10 @@ import { DataSourceSrv as DataSourceService, getDataSourceSrv as getDataSourceSe
// Types // Types
import { DataSourceApi, DataSourceSelectItem } from '@grafana/ui'; import { DataSourceApi, DataSourceSelectItem } from '@grafana/ui';
import { ScopedVars } from '@grafana/data'; import { ScopedVars, AppEvents } from '@grafana/data';
import { auto } from 'angular'; import { auto } from 'angular';
import { TemplateSrv } from '../templating/template_srv'; import { TemplateSrv } from '../templating/template_srv';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
export class DatasourceSrv implements DataSourceService { export class DatasourceSrv implements DataSourceService {
datasources: { [name: string]: DataSourceApi }; datasources: { [name: string]: DataSourceApi };
@ -20,7 +21,7 @@ export class DatasourceSrv implements DataSourceService {
constructor( constructor(
private $q: any, private $q: any,
private $injector: auto.IInjectorService, private $injector: auto.IInjectorService,
private $rootScope: any, private $rootScope: GrafanaRootScope,
private templateSrv: TemplateSrv private templateSrv: TemplateSrv
) { ) {
this.init(); this.init();
@ -86,7 +87,7 @@ export class DatasourceSrv implements DataSourceService {
deferred.resolve(instance); deferred.resolve(instance);
}) })
.catch(err => { .catch(err => {
this.$rootScope.appEvent('alert-error', [dsConfig.name + ' plugin failed', err.toString()]); this.$rootScope.appEvent(AppEvents.alertError, [dsConfig.name + ' plugin failed', err.toString()]);
}); });
return deferred.promise; return deferred.promise;

View File

@ -7,12 +7,13 @@ import coreModule from 'app/core/core_module';
import { DataSourceApi } from '@grafana/ui'; import { DataSourceApi } from '@grafana/ui';
import { importPanelPlugin, importDataSourcePlugin, importAppPlugin } from './plugin_loader'; import { importPanelPlugin, importDataSourcePlugin, importAppPlugin } from './plugin_loader';
import DatasourceSrv from './datasource_srv'; import DatasourceSrv from './datasource_srv';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
/** @ngInject */ /** @ngInject */
function pluginDirectiveLoader( function pluginDirectiveLoader(
$compile: any, $compile: any,
datasourceSrv: DatasourceSrv, datasourceSrv: DatasourceSrv,
$rootScope: any, $rootScope: GrafanaRootScope,
$q: IQService, $q: IQService,
$http: any, $http: any,
$templateCache: any, $templateCache: any,

View File

@ -4,6 +4,8 @@ import _ from 'lodash';
import { getPluginSettings } from './PluginSettingsCache'; import { getPluginSettings } from './PluginSettingsCache';
import { PluginMeta } from '@grafana/ui'; import { PluginMeta } from '@grafana/ui';
import { NavModelSrv } from 'app/core/core'; import { NavModelSrv } from 'app/core/core';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { AppEvents } from '@grafana/data';
export class AppPageCtrl { export class AppPageCtrl {
page: any; page: any;
@ -14,7 +16,7 @@ export class AppPageCtrl {
/** @ngInject */ /** @ngInject */
constructor( constructor(
private $routeParams: any, private $routeParams: any,
private $rootScope: any, private $rootScope: GrafanaRootScope,
private navModelSrv: NavModelSrv, private navModelSrv: NavModelSrv,
private $q: IQService private $q: IQService
) { ) {
@ -26,7 +28,7 @@ export class AppPageCtrl {
this.initPage(settings); this.initPage(settings);
}) })
.catch(err => { .catch(err => {
this.$rootScope.appEvent('alert-error', ['Unknown Plugin', '']); this.$rootScope.appEvent(AppEvents.alertError, ['Unknown Plugin']);
this.navModel = this.navModelSrv.getNotFoundNav(); this.navModel = this.navModelSrv.getNotFoundNav();
}); });
} }
@ -36,12 +38,12 @@ export class AppPageCtrl {
this.page = _.find(app.includes, { slug: this.$routeParams.slug }); this.page = _.find(app.includes, { slug: this.$routeParams.slug });
if (!this.page) { if (!this.page) {
this.$rootScope.appEvent('alert-error', ['App Page Not Found', '']); this.$rootScope.appEvent(AppEvents.alertError, ['App Page Not Found']);
this.navModel = this.navModelSrv.getNotFoundNav(); this.navModel = this.navModelSrv.getNotFoundNav();
return; return;
} }
if (app.type !== 'app' || !app.enabled) { if (app.type !== 'app' || !app.enabled) {
this.$rootScope.appEvent('alert-error', ['Application Not Enabled', '']); this.$rootScope.appEvent(AppEvents.alertError, ['Application Not Enabled']);
this.navModel = this.navModelSrv.getNotFoundNav(); this.navModel = this.navModelSrv.getNotFoundNav();
return; return;
} }

View File

@ -17,7 +17,7 @@ const templateSrv: any = {
}; };
describe('datasource_srv', () => { describe('datasource_srv', () => {
const _datasourceSrv = new DatasourceSrv({}, {} as any, {}, templateSrv); const _datasourceSrv = new DatasourceSrv({}, {} as any, {} as any, templateSrv);
describe('when loading external datasources', () => { describe('when loading external datasources', () => {
beforeEach(() => { beforeEach(() => {

View File

@ -5,6 +5,7 @@ import appEvents from 'app/core/app_events';
import DatasourceSrv from '../plugins/datasource_srv'; import DatasourceSrv from '../plugins/datasource_srv';
import { VariableSrv } from './all'; import { VariableSrv } from './all';
import { TemplateSrv } from './template_srv'; import { TemplateSrv } from './template_srv';
import { AppEvents } from '@grafana/data';
export class VariableEditorCtrl { export class VariableEditorCtrl {
/** @ngInject */ /** @ngInject */
@ -85,13 +86,16 @@ export class VariableEditorCtrl {
} }
if (!$scope.current.name.match(/^\w+$/)) { if (!$scope.current.name.match(/^\w+$/)) {
appEvents.emit('alert-warning', ['Validation', 'Only word and digit characters are allowed in variable names']); appEvents.emit(AppEvents.alertWarning, [
'Validation',
'Only word and digit characters are allowed in variable names',
]);
return false; return false;
} }
const sameName: any = _.find($scope.variables, { name: $scope.current.name }); const sameName: any = _.find($scope.variables, { name: $scope.current.name });
if (sameName && sameName !== $scope.current) { if (sameName && sameName !== $scope.current) {
appEvents.emit('alert-warning', ['Validation', 'Variable with the same name already exists']); appEvents.emit(AppEvents.alertWarning, ['Validation', 'Variable with the same name already exists']);
return false; return false;
} }
@ -100,7 +104,7 @@ export class VariableEditorCtrl {
_.isString($scope.current.query) && _.isString($scope.current.query) &&
$scope.current.query.match(new RegExp('\\$' + $scope.current.name + '(/| |$)')) $scope.current.query.match(new RegExp('\\$' + $scope.current.name + '(/| |$)'))
) { ) {
appEvents.emit('alert-warning', [ appEvents.emit(AppEvents.alertWarning, [
'Validation', 'Validation',
'Query cannot contain a reference to itself. Variable: $' + $scope.current.name, 'Query cannot contain a reference to itself. Variable: $' + $scope.current.name,
]); ]);
@ -128,7 +132,10 @@ export class VariableEditorCtrl {
if (err.data && err.data.message) { if (err.data && err.data.message) {
err.message = err.data.message; err.message = err.data.message;
} }
appEvents.emit('alert-error', ['Templating', 'Template variables could not be initialized: ' + err.message]); appEvents.emit(AppEvents.alertError, [
'Templating',
'Template variables could not be initialized: ' + err.message,
]);
}); });
}; };

View File

@ -1,5 +1,6 @@
import { VariableEditorCtrl } from '../editor_ctrl'; import { VariableEditorCtrl } from '../editor_ctrl';
import { TemplateSrv } from '../template_srv'; import { TemplateSrv } from '../template_srv';
import { AppEvents } from '@grafana/data';
let mockEmit: any; let mockEmit: any;
jest.mock('app/core/app_events', () => { jest.mock('app/core/app_events', () => {
@ -32,7 +33,7 @@ describe('VariableEditorCtrl', () => {
it('should emit an error', () => { it('should emit an error', () => {
return scope.runQuery().then(res => { return scope.runQuery().then(res => {
expect(mockEmit).toBeCalled(); expect(mockEmit).toBeCalled();
expect(mockEmit.mock.calls[0][0]).toBe('alert-error'); expect(mockEmit.mock.calls[0][0]).toBe(AppEvents.alertError);
expect(mockEmit.mock.calls[0][1][0]).toBe('Templating'); expect(mockEmit.mock.calls[0][1][0]).toBe('Templating');
expect(mockEmit.mock.calls[0][1][1]).toBe('Template variables could not be initialized: error'); expect(mockEmit.mock.calls[0][1][1]).toBe('Template variables could not be initialized: error');
}); });

Some files were not shown because too many files have changed in this diff Show More