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

View File

@ -1,6 +1,7 @@
import React, { PureComponent, SyntheticEvent, ChangeEvent } from 'react';
import { Tooltip } from '@grafana/ui';
import appEvents from 'app/core/app_events';
import { AppEvents } from '@grafana/data';
interface Props {
onSubmit: (pw: string) => void;
@ -42,7 +43,7 @@ export class ChangePassword extends PureComponent<Props, State> {
if (valid) {
this.props.onSubmit(newPassword);
} 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 { hot } from 'react-hot-loader';
import appEvents from 'app/core/app_events';
import { AppEvents } from '@grafana/data';
const isOauthEnabled = () => {
return !!config.oauth && Object.keys(config.oauth).length > 0;
@ -52,7 +53,7 @@ export class LoginCtrl extends PureComponent<Props, State> {
};
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 appEvents from 'app/core/app_events';
import { NavModel, NavModelItem, NavModelBreadcrumb } from '@grafana/data';
import { CoreEvents } from 'app/types';
export interface Props {
model: NavModel;
@ -15,7 +16,7 @@ const SelectNav = ({ main, customCss }: { main: NavModelItem; customCss: string
const gotoUrl = (evt: FormEvent) => {
const element = evt.target as HTMLSelectElement;
const url = element.options[element.selectedIndex].value;
appEvents.emit('location-change', { href: url });
appEvents.emit(CoreEvents.locationChange, { href: url });
};
return (

View File

@ -1,5 +1,7 @@
import store from 'app/core/store';
import coreModule from 'app/core/core_module';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { CoreEvents } from 'app/types';
const template = `
<div class="layout-selector">
@ -16,20 +18,20 @@ export class LayoutSelectorCtrl {
mode: string;
/** @ngInject */
constructor(private $rootScope: any) {
constructor(private $rootScope: GrafanaRootScope) {
this.mode = store.get('grafana.list.layout.mode') || 'grid';
}
listView() {
this.mode = 'list';
store.set('grafana.list.layout.mode', 'list');
this.$rootScope.appEvent('layout-mode-changed', 'list');
this.$rootScope.appEvent(CoreEvents.layoutModeChanged, 'list');
}
gridView() {
this.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 */
export function layoutMode($rootScope: any) {
export function layoutMode($rootScope: GrafanaRootScope) {
return {
restrict: 'A',
scope: {},
@ -56,7 +58,7 @@ export function layoutMode($rootScope: any) {
elem.addClass(className);
$rootScope.onAppEvent(
'layout-mode-changed',
CoreEvents.layoutModeChanged,
(evt: any, newLayout: any) => {
elem.removeClass(className);
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 { NavModelSrv } from 'app/core/nav_model_srv';
import { ContextSrv } from 'app/core/services/context_srv';
import { CoreEvents } from 'app/types';
export interface Section {
id: number;
@ -200,7 +201,7 @@ export class ManageDashboardsCtrl {
text += `selected dashboard${dashCount === 1 ? '' : 's'}?`;
}
appEvents.emit('confirm-modal', {
appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Delete',
text: text,
text2: text2,
@ -236,7 +237,7 @@ export class ManageDashboardsCtrl {
'<move-to-folder-modal dismiss="dismiss()" ' +
'dashboards="model.dashboards" after-save="model.afterSave()">' +
'</move-to-folder-modal>';
appEvents.emit('show-modal', {
appEvents.emit(CoreEvents.showModal, {
templateHtml: template,
modalClass: 'modal--narrow',
model: {

View File

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

View File

@ -1,6 +1,7 @@
import _ from 'lodash';
import coreModule from '../../core_module';
import appEvents from 'app/core/app_events';
import { CoreEvents } from 'app/types';
export class SearchResultsCtrl {
results: any;
@ -65,7 +66,7 @@ export class SearchResultsCtrl {
onItemClick(item: any) {
//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) {
appEvents.emit('hide-dash-search');
appEvents.emit(CoreEvents.hideDashSearch);
}
}

View File

@ -2,6 +2,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import BottomNavLinks from './BottomNavLinks';
import appEvents from '../../app_events';
import { CoreEvents } from 'app/types';
jest.mock('../../app_events', () => ({
emit: jest.fn(),
@ -93,7 +94,7 @@ describe('Functions', () => {
const instance = wrapper.instance() as BottomNavLinks;
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 { User } from '../../services/context_srv';
import { NavModelItem } from '@grafana/data';
import { CoreEvents } from 'app/types';
export interface Props {
link: NavModelItem;
@ -12,14 +13,14 @@ class BottomNavLinks extends PureComponent<Props> {
itemClicked = (event: React.SyntheticEvent, child: NavModelItem) => {
if (child.url === '/shortcuts') {
event.preventDefault();
appEvents.emit('show-modal', {
appEvents.emit(CoreEvents.showModal, {
templateHtml: '<help-modal></help-modal>',
});
}
};
switchOrg = () => {
appEvents.emit('show-modal', {
appEvents.emit(CoreEvents.showModal, {
templateHtml: '<org-switcher dismiss="dismiss()"></org-switcher>',
});
};

View File

@ -2,6 +2,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import { SideMenu } from './SideMenu';
import appEvents from '../../app_events';
import { CoreEvents } from 'app/types';
jest.mock('../../app_events', () => ({
emit: jest.fn(),
@ -58,7 +59,7 @@ describe('Functions', () => {
instance.toggleSideMenuSmallBreakpoint();
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 BottomSection from './BottomSection';
import config from 'app/core/config';
import { CoreEvents } from 'app/types';
const homeUrl = config.appSubUrl || '/';
export class SideMenu extends PureComponent {
toggleSideMenuSmallBreakpoint = () => {
appEvents.emit('toggle-sidemenu-mobile');
appEvents.emit(CoreEvents.toggleSidemenuMobile);
};
render() {

View File

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

View File

@ -1,6 +1,7 @@
import coreModule from '../core_module';
import config from 'app/core/config';
import { BackendSrv } from '../services/backend_srv';
import { AppEvents } from '@grafana/data';
export class ResetPasswordCtrl {
/** @ngInject */
@ -41,7 +42,7 @@ export class ResetPasswordCtrl {
}
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;
}

View File

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

View File

@ -1,14 +1,16 @@
import angular from 'angular';
import coreModule from '../core_module';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { CoreEvents } from 'app/types';
export class DeltaCtrl {
observer: any;
/** @ngInject */
constructor(private $rootScope: any) {
constructor(private $rootScope: GrafanaRootScope) {
const waitForCompile = (mutations: any) => {
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
export class LinkJSONCtrl {
/** @ngInject */
constructor(private $scope: any, private $rootScope: any, private $anchorScroll: any) {}
constructor(private $scope: any, private $rootScope: GrafanaRootScope, private $anchorScroll: any) {}
goToLine(line: number) {
let unbind: () => void;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,9 +2,10 @@ import _ from 'lodash';
import coreModule from 'app/core/core_module';
// @ts-ignore
import Drop from 'tether-drop';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
/** @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;
this.close = () => {

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import { SearchResultsCtrl } from '../components/search/search_results';
import { beforeEach, afterEach } from 'test/lib/common';
import appEvents from 'app/core/app_events';
import { CoreEvents } from 'app/types';
jest.mock('app/core/app_events', () => {
return {
@ -121,7 +122,7 @@ describe('SearchResultsCtrl', () => {
it('should close the search', () => {
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 { AppEvent } from '@grafana/data';
export class Emitter {
private emitter: EventEmitter;
@ -7,29 +8,83 @@ export class Emitter {
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) {
const unbind = scope.$on('$destroy', () => {
this.emitter.off(name, handler);
this.emitter.off(event.name, handler);
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) {
this.emitter.removeAllListeners(evt);
}
off(name: string, handler: (payload?: any) => void) {
this.emitter.off(name, handler);
}
getEventCount(): number {
return (this.emitter as any)._eventsCount;
}

View File

@ -1,13 +1,19 @@
import _ from 'lodash';
import { dateTime } from '@grafana/data';
import { BackendSrv } from 'app/core/services/backend_srv';
import { NavModelSrv } from 'app/core/core';
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 {
/** @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.sessions = [];
$scope.newOrg = { name: '', role: 'Editor' };
@ -162,7 +168,7 @@ export default class AdminEditUserCtrl {
};
$scope.deleteUser = (user: any) => {
$scope.appEvent('confirm-modal', {
$scope.appEvent(CoreEvents.showConfirmModal, {
title: 'Delete',
text: 'Do you want to delete ' + user.login + '?',
icon: 'fa-trash',

View File

@ -1,9 +1,10 @@
import { BackendSrv } from 'app/core/services/backend_srv';
import { NavModelSrv } from 'app/core/core';
import { Scope, CoreEvents, AppEventEmitter } from 'app/types';
export default class AdminListOrgsCtrl {
/** @ngInject */
constructor($scope: any, backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
constructor($scope: Scope & AppEventEmitter, backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
$scope.init = () => {
$scope.navModel = navModelSrv.getNav('admin', 'global-orgs', 0);
$scope.getOrgs();
@ -16,9 +17,9 @@ export default class AdminListOrgsCtrl {
};
$scope.deleteOrg = (org: any) => {
$scope.appEvent('confirm-modal', {
$scope.appEvent(CoreEvents.showConfirmModal, {
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!',
icon: 'fa-trash',
yesText: 'Delete',

View File

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

View File

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

View File

@ -19,6 +19,7 @@ import { TestRuleResult } from './TestRuleResult';
import { AppNotificationSeverity, StoreState } from 'app/types';
import { PanelEditorTabIds, getPanelEditorTab } from '../dashboard/panel_editor/state/reducers';
import { changePanelEditorTab } from '../dashboard/panel_editor/state/actions';
import { CoreEvents } from 'app/types';
interface Props {
angularPanel?: AngularComponent;
@ -116,7 +117,7 @@ class UnConnectedAlertTab extends PureComponent<Props, State> {
title: 'Delete',
btnType: 'danger',
onClick: () => {
appEvents.emit('confirm-modal', {
appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Delete Alert',
text: 'Are you sure you want to delete this alert rule?',
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 { PanelModel } from 'app/features/dashboard/state';
import { getDefaultCondition } from './getAlertingValidationMessage';
import { CoreEvents } from 'app/types';
export class AlertTabCtrl {
panel: PanelModel;
@ -58,11 +59,11 @@ export class AlertTabCtrl {
// subscribe to graph threshold handle changes
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
this.$scope.$on('$destroy', () => {
this.panelCtrl.events.off('threshold-changed', thresholdChangedEventHandler);
this.panelCtrl.events.off(CoreEvents.thresholdChanged, thresholdChangedEventHandler);
this.panelCtrl.editingThresholds = false;
this.panelCtrl.render();
});
@ -352,7 +353,7 @@ export class AlertTabCtrl {
}
delete() {
appEvents.emit('confirm-modal', {
appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Delete Alert',
text: 'Are you sure you want to delete this alert rule?',
text2: 'You need to save dashboard for the delete to take effect',
@ -402,7 +403,7 @@ export class AlertTabCtrl {
}
clearHistory() {
appEvents.emit('confirm-modal', {
appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Delete Alert History',
text: 'Are you sure you want to remove all history & annotations for this alert?',
icon: 'fa-trash',

View File

@ -1,6 +1,7 @@
import _ from 'lodash';
import { appEvents, coreModule, NavModelSrv } from 'app/core/core';
import { BackendSrv } from 'app/core/services/backend_srv';
import { AppEvents } from '@grafana/data';
export class AlertNotificationEditCtrl {
theForm: any;
@ -78,23 +79,23 @@ export class AlertNotificationEditCtrl {
.put(`/api/alert-notifications/${this.model.id}`, this.model)
.then((res: any) => {
this.model = res;
appEvents.emit('alert-success', ['Notification updated', '']);
appEvents.emit(AppEvents.alertSuccess, ['Notification updated']);
})
.catch((err: any) => {
if (err.data && err.data.error) {
appEvents.emit('alert-error', [err.data.error]);
appEvents.emit(AppEvents.alertError, [err.data.error]);
}
});
} else {
this.backendSrv
.post(`/api/alert-notifications`, this.model)
.then((res: any) => {
appEvents.emit('alert-success', ['Notification created', '']);
appEvents.emit(AppEvents.alertSuccess, ['Notification created']);
this.$location.path('alerting/notifications');
})
.catch((err: any) => {
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 { DashboardModel } from '../dashboard/state/DashboardModel';
import appEvents from '../../core/app_events';
import { CoreEvents } from 'app/types';
interface Props {
dashboard: DashboardModel;
@ -42,7 +43,7 @@ class StateHistory extends PureComponent<Props, State> {
clearHistory = () => {
const { dashboard, onRefresh, panelId } = this.props;
appEvents.emit('confirm-modal', {
appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Delete Alert History',
text: 'Are you sure you want to remove all history & annotations for this alert?',
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 { DashboardModel } from '../dashboard/state/DashboardModel';
import { getBackendSrv, BackendSrv } from '@grafana/runtime';
import { AppEvents } from '@grafana/data';
export interface Props {
panelId: number;
@ -55,7 +56,7 @@ export class TestRuleResult extends PureComponent<Props, State> {
};
onClipboardSuccess = () => {
appEvents.emit('alert-success', ['Content copied to clipboard']);
appEvents.emit(AppEvents.alertSuccess, ['Content copied to clipboard']);
};
onToggleExpand = () => {

View File

@ -11,11 +11,12 @@ import { dedupAnnotations } from './events_processing';
// Types
import { DashboardModel } from '../dashboard/state/DashboardModel';
import { AnnotationEvent } from '@grafana/data';
import DatasourceSrv from '../plugins/datasource_srv';
import { BackendSrv } from 'app/core/services/backend_srv';
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 {
globalAnnotationsPromise: any;
@ -24,7 +25,7 @@ export class AnnotationsSrv {
/** @ngInject */
constructor(
private $rootScope: any,
private $rootScope: GrafanaRootScope,
private $q: IQService,
private datasourceSrv: DatasourceSrv,
private backendSrv: BackendSrv,
@ -35,7 +36,7 @@ export class AnnotationsSrv {
// always clearPromiseCaches when loading new dashboard
this.clearPromiseCaches();
// clear promises on refresh events
dashboard.on('refresh', this.clearPromiseCaches.bind(this));
dashboard.on(PanelEvents.refresh, this.clearPromiseCaches.bind(this));
}
clearPromiseCaches() {
@ -75,7 +76,7 @@ export class AnnotationsSrv {
err.message = err.data.message;
}
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 [];
});
}

View File

@ -5,6 +5,7 @@ import coreModule from 'app/core/core_module';
import { DashboardModel } from 'app/features/dashboard/state';
import DatasourceSrv from '../plugins/datasource_srv';
import appEvents from 'app/core/app_events';
import { AppEvents } from '@grafana/data';
export class AnnotationsEditorCtrl {
mode: any;
@ -103,7 +104,7 @@ export class AnnotationsEditorCtrl {
add() {
const sameName: any = _.find(this.annotations, { name: this.currentAnnotation.name });
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;
}
this.annotations.push(this.currentAnnotation);

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import coreModule from 'app/core/core_module';
import { DashboardModel } from 'app/features/dashboard/state';
import DatasourceSrv from 'app/features/plugins/datasource_srv';
import { VariableSrv } from 'app/features/templating/all';
import { CoreEvents } from 'app/types';
export class AdHocFiltersCtrl {
segments: any;
@ -24,7 +25,7 @@ export class AdHocFiltersCtrl {
value: '-- remove filter --',
});
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() {

View File

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

View File

@ -4,6 +4,9 @@ import { iconMap } from './DashLinksEditorCtrl';
import { LinkSrv } from 'app/features/panel/panellinks/link_srv';
import { BackendSrv } from 'app/core/services/backend_srv';
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 };
@ -84,7 +87,7 @@ function dashLink($compile: any, $sanitize: any, linkSrv: LinkSrv) {
}
update();
dashboard.events.on('refresh', update, scope);
dashboard.events.on(PanelEvents.refresh, update, scope);
},
};
}
@ -93,7 +96,7 @@ export class DashLinksContainerCtrl {
/** @ngInject */
constructor(
$scope: any,
$rootScope: any,
$rootScope: GrafanaRootScope,
$q: IQService,
backendSrv: BackendSrv,
dashboardSrv: DashboardSrv,
@ -184,7 +187,7 @@ export class DashLinksContainerCtrl {
};
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 _ from 'lodash';
import { DashboardModel } from 'app/features/dashboard/state';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { CoreEvents } from 'app/types';
export let iconMap = {
'external link': 'fa-external-link',
@ -32,13 +34,13 @@ export class DashLinksEditorCtrl {
};
/** @ngInject */
constructor($scope: any, $rootScope: any) {
constructor($scope: any, $rootScope: GrafanaRootScope) {
this.iconMap = iconMap;
this.dashboard.links = this.dashboard.links || [];
this.mode = 'list';
$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
import { DashboardModel } from '../../state';
import { StoreState } from 'app/types';
import { StoreState, CoreEvents } from 'app/types';
export interface OwnProps {
dashboard: DashboardModel;
@ -43,11 +43,11 @@ export class DashNav extends PureComponent<Props> {
}
onDahboardNameClick = () => {
appEvents.emit('show-dash-search');
appEvents.emit(CoreEvents.showDashSearch);
};
onFolderNameClick = () => {
appEvents.emit('show-dash-search', {
appEvents.emit(CoreEvents.showDashSearch, {
query: 'folder:current',
});
};
@ -67,7 +67,7 @@ export class DashNav extends PureComponent<Props> {
};
onToggleTVMode = () => {
appEvents.emit('toggle-kiosk-mode');
appEvents.emit(CoreEvents.toggleKioskMode);
};
onSave = () => {
@ -112,7 +112,7 @@ export class DashNav extends PureComponent<Props> {
modalScope.tabIndex = 0;
modalScope.dashboard = this.props.dashboard;
appEvents.emit('show-modal', {
appEvents.emit(CoreEvents.showModal, {
src: 'public/app/features/dashboard/components/ShareModal/template.html',
scope: modalScope,
});

View File

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

View File

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

View File

@ -6,6 +6,9 @@ import angular, { ILocationService } from 'angular';
import config from 'app/core/config';
import { BackendSrv } from 'app/core/services/backend_srv';
import { DashboardSrv } from '../../services/DashboardSrv';
import { CoreEvents } from 'app/types';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
import { AppEvents } from '@grafana/data';
export class SettingsCtrl {
dashboard: DashboardModel;
@ -24,7 +27,7 @@ export class SettingsCtrl {
private $scope: any,
private $route: any,
private $location: ILocationService,
private $rootScope: any,
private $rootScope: GrafanaRootScope,
private backendSrv: BackendSrv,
private dashboardSrv: DashboardSrv
) {
@ -35,7 +38,7 @@ export class SettingsCtrl {
this.$scope.$on('$destroy', () => {
this.dashboard.updateSubmenuVisibility();
setTimeout(() => {
this.$rootScope.appEvent('dash-scroll', { restore: true });
this.$rootScope.appEvent(CoreEvents.dashScroll, { restore: true });
this.dashboard.startRefresh();
});
});
@ -47,9 +50,9 @@ export class SettingsCtrl {
this.buildSectionList();
this.onRouteUpdated();
this.$rootScope.onAppEvent('$routeUpdate', this.onRouteUpdated.bind(this), $scope);
this.$rootScope.appEvent('dash-scroll', { animate: false, pos: 0 });
this.$rootScope.onAppEvent('dashboard-saved', this.onPostSave.bind(this), $scope);
this.$rootScope.onAppEvent(CoreEvents.routeUpdated, this.onRouteUpdated.bind(this), $scope);
this.$rootScope.appEvent(CoreEvents.dashScroll, { animate: false, pos: 0 });
this.$rootScope.onAppEvent(CoreEvents.dashboardSaved, this.onPostSave.bind(this), $scope);
}
buildSectionList() {
@ -185,7 +188,7 @@ export class SettingsCtrl {
let text2 = this.dashboard.title;
if (this.dashboard.meta.provisioned) {
appEvents.emit('confirm-modal', {
appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Cannot delete provisioned dashboard',
text: `
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`;
}
appEvents.emit('confirm-modal', {
appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Delete',
text: 'Do you want to delete this dashboard?',
text2: text2,
@ -229,7 +232,7 @@ export class SettingsCtrl {
deleteDashboardConfirmed() {
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('/');
});
}

View File

@ -2,6 +2,7 @@ import angular from 'angular';
import * as fileExport from 'app/core/utils/file_export';
import appEvents from 'app/core/app_events';
import { DashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { CoreEvents } from 'app/types';
export class ExportDataModalCtrl {
private data: any;
@ -34,7 +35,7 @@ export class ExportDataModalCtrl {
}
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 { ValidationSrv } from 'app/features/manage-dashboards';
import { ContextSrv } from 'app/core/services/context_srv';
import { AppEvents } from '@grafana/data';
export class FolderPickerCtrl {
initialTitle: string;
@ -105,7 +106,7 @@ export class FolderPickerCtrl {
}
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.folder = { text: result.title, value: result.id };

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ import { HistoryListCtrl } from './HistoryListCtrl';
import { versions, compare, restore } from './__mocks__/history';
// @ts-ignore
import $q from 'q';
import { CoreEvents } from 'app/types';
describe('HistoryListCtrl', () => {
const RESTORE_ID = 4;
@ -118,9 +119,9 @@ describe('HistoryListCtrl', () => {
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.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', () => {
@ -292,7 +293,7 @@ describe('HistoryListCtrl', () => {
it('should display a modal allowing the user to restore or cancel', () => {
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', () => {

View File

@ -4,7 +4,9 @@ import angular, { ILocationService, IQService } from 'angular';
import locationUtil from 'app/core/utils/location_util';
import { DashboardModel } from '../../state/DashboardModel';
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 {
appending: boolean;
@ -25,7 +27,7 @@ export class HistoryListCtrl {
/** @ngInject */
constructor(
private $route: any,
private $rootScope: any,
private $rootScope: GrafanaRootScope,
private $location: ILocationService,
private $q: IQService,
private historySrv: HistorySrv,
@ -40,7 +42,7 @@ export class HistoryListCtrl {
this.start = 0;
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();
}
@ -56,7 +58,7 @@ export class HistoryListCtrl {
}
dismiss() {
this.$rootScope.appEvent('hide-dash-editor');
this.$rootScope.appEvent(CoreEvents.hideDashEditor);
}
addToLog() {
@ -172,7 +174,7 @@ export class HistoryListCtrl {
}
restore(version: number) {
this.$rootScope.appEvent('confirm-modal', {
this.$rootScope.appEvent(CoreEvents.showConfirmModal, {
title: 'Restore version',
text: '',
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) => {
this.$location.url(locationUtil.stripBaseFromUrl(response.url)).replace();
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(() => {
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 { DashboardPanel } from './DashboardPanel';
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 ignoreNextWidthChange = false;
@ -93,22 +96,22 @@ export class DashboardGrid extends PureComponent<Props> {
componentDidMount() {
const { dashboard } = this.props;
dashboard.on('panel-added', this.triggerForceUpdate);
dashboard.on('panel-removed', this.triggerForceUpdate);
dashboard.on('repeats-processed', this.triggerForceUpdate);
dashboard.on('view-mode-changed', this.onViewModeChanged);
dashboard.on('row-collapsed', this.triggerForceUpdate);
dashboard.on('row-expanded', this.triggerForceUpdate);
dashboard.on(panelAdded, this.triggerForceUpdate);
dashboard.on(panelRemoved, this.triggerForceUpdate);
dashboard.on(CoreEvents.repeatsProcessed, this.triggerForceUpdate);
dashboard.on(PanelEvents.viewModeChanged, this.onViewModeChanged);
dashboard.on(CoreEvents.rowCollapsed, this.triggerForceUpdate);
dashboard.on(CoreEvents.rowExpanded, this.triggerForceUpdate);
}
componentWillUnmount() {
const { dashboard } = this.props;
dashboard.off('panel-added', this.triggerForceUpdate);
dashboard.off('panel-removed', this.triggerForceUpdate);
dashboard.off('repeats-processed', this.triggerForceUpdate);
dashboard.off('view-mode-changed', this.onViewModeChanged);
dashboard.off('row-collapsed', this.triggerForceUpdate);
dashboard.off('row-expanded', this.triggerForceUpdate);
dashboard.off(panelAdded, this.triggerForceUpdate);
dashboard.off(panelRemoved, this.triggerForceUpdate);
dashboard.off(CoreEvents.repeatsProcessed, this.triggerForceUpdate);
dashboard.off(PanelEvents.viewModeChanged, this.onViewModeChanged);
dashboard.off(CoreEvents.rowCollapsed, this.triggerForceUpdate);
dashboard.off(CoreEvents.rowExpanded, this.triggerForceUpdate);
}
buildLayout() {

View File

@ -14,7 +14,8 @@ import templateSrv from 'app/features/templating/template_srv';
import config from 'app/core/config';
// Types
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';
@ -59,8 +60,8 @@ export class PanelChrome extends PureComponent<Props, State> {
componentDidMount() {
const { panel, dashboard } = this.props;
panel.events.on('refresh', this.onRefresh);
panel.events.on('render', this.onRender);
panel.events.on(PanelEvents.refresh, this.onRefresh);
panel.events.on(PanelEvents.render, this.onRender);
dashboard.panelInitialized(this.props.panel);
// Move snapshot data into the query response
@ -79,8 +80,7 @@ export class PanelChrome extends PureComponent<Props, State> {
}
componentWillUnmount() {
this.props.panel.events.off('refresh', this.onRefresh);
this.props.panel.events.off(PanelEvents.refresh, this.onRefresh);
if (this.querySubscription) {
this.querySubscription.unsubscribe();
this.querySubscription = null;

View File

@ -9,7 +9,7 @@ import { Emitter } from 'app/core/utils/emitter';
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
// Types
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 { DashboardModel } from '../state/DashboardModel';
@ -273,9 +273,9 @@ function notifyAngularQueryEditorsOfData(panel: PanelModel, data: PanelData, edi
if (data.state === LoadingState.Done) {
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) {
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

View File

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

View File

@ -10,7 +10,7 @@ jest.mock('app/core/services/context_srv', () => ({
}));
describe('ChangeTracker', () => {
let rootScope;
let rootScope: any;
let location;
const timeout = () => {};
let tracker: ChangeTracker;
@ -57,7 +57,7 @@ describe('ChangeTracker', () => {
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', () => {

View File

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

View File

@ -4,12 +4,13 @@ import moment from 'moment';
import _ from 'lodash';
import $ from 'jquery';
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 { BackendSrv } from 'app/core/services/backend_srv';
import { DashboardSrv } from './DashboardSrv';
import DatasourceSrv from 'app/features/plugins/datasource_srv';
import { UrlQueryValue } from '@grafana/runtime';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
export class DashboardLoaderSrv {
/** @ngInject */
@ -22,7 +23,7 @@ export class DashboardLoaderSrv {
private $timeout: any,
contextSrv: any,
private $routeParams: any,
private $rootScope: any
private $rootScope: GrafanaRootScope
) {}
_dashboardLoadFailed(title: string, snapshot?: boolean) {
@ -54,7 +55,7 @@ export class DashboardLoaderSrv {
.getDashboardByUid(uid)
.then((result: any) => {
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');
}
return result;
@ -94,7 +95,7 @@ export class DashboardLoaderSrv {
},
(err: any) => {
console.log('Script dashboard error ' + err);
this.$rootScope.appEvent('alert-error', [
this.$rootScope.appEvent(AppEvents.alertError, [
'Script Error',
'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 { DashboardModel } from '../state/DashboardModel';
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 { ILocationService } from 'angular';
import { AppEvents } from '@grafana/data';
import { PanelEvents } from '@grafana/ui';
interface DashboardSaveOptions {
folderId?: number;
@ -18,10 +21,14 @@ export class DashboardSrv {
dashboard: DashboardModel;
/** @ngInject */
constructor(private backendSrv: BackendSrv, private $rootScope: any, private $location: ILocationService) {
appEvents.on('save-dashboard', this.saveDashboard.bind(this), $rootScope);
appEvents.on('panel-change-view', this.onPanelChangeView);
appEvents.on('remove-panel', this.onRemovePanel);
constructor(
private backendSrv: BackendSrv,
private $rootScope: GrafanaRootScope,
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
setDashboardSrv(this);
@ -96,7 +103,7 @@ export class DashboardSrv {
if (err.data && err.data.status === 'version-mismatch') {
err.isHandled = true;
this.$rootScope.appEvent('confirm-modal', {
this.$rootScope.appEvent(CoreEvents.showConfirmModal, {
title: 'Conflict',
text: 'Someone else has updated 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') {
err.isHandled = true;
this.$rootScope.appEvent('confirm-modal', {
this.$rootScope.appEvent(CoreEvents.showConfirmModal, {
title: 'Conflict',
text: 'A dashboard with the same name in selected folder already exists.',
text2: 'Would you still like to save this dashboard?',
@ -126,7 +133,7 @@ export class DashboardSrv {
if (err.data && err.data.status === 'plugin-dashboard') {
err.isHandled = true;
this.$rootScope.appEvent('confirm-modal', {
this.$rootScope.appEvent(CoreEvents.showConfirmModal, {
title: 'Plugin Dashboard',
text: err.data.message,
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;
// important that these happen before location redirect below
this.$rootScope.appEvent('dashboard-saved', this.dashboard);
this.$rootScope.appEvent('alert-success', ['Dashboard saved']);
this.$rootScope.appEvent(CoreEvents.dashboardSaved, this.dashboard);
this.$rootScope.appEvent(AppEvents.alertSuccess, ['Dashboard saved']);
const newUrl = locationUtil.stripBaseFromUrl(data.url);
const currentPath = this.$location.path();
@ -201,20 +208,20 @@ export class DashboardSrv {
}
showDashboardProvisionedModal() {
this.$rootScope.appEvent('show-modal', {
this.$rootScope.appEvent(CoreEvents.showModal, {
templateHtml: '<save-provisioned-dashboard-modal dismiss="dismiss()"></save-provisioned-dashboard-modal>',
});
}
showSaveAsModal() {
this.$rootScope.appEvent('show-modal', {
this.$rootScope.appEvent(CoreEvents.showModal, {
templateHtml: '<save-dashboard-as-modal dismiss="dismiss()"></save-dashboard-as-modal>',
modalClass: 'modal--narrow',
});
}
showSaveModal() {
this.$rootScope.appEvent('show-modal', {
this.$rootScope.appEvent(CoreEvents.showModal, {
templateHtml: '<save-dashboard-modal dismiss="dismiss()"></save-dashboard-modal>',
modalClass: 'modal--narrow',
});

View File

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

View File

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

View File

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

View File

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

View File

@ -3,8 +3,8 @@ import store from 'app/core/store';
// Models
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
import { TimeRange } from '@grafana/data';
import { PanelModel, panelRemoved, panelAdded } from 'app/features/dashboard/state/PanelModel';
import { TimeRange, AppEvents } from '@grafana/data';
// Utils
import { isString as _isString } from 'lodash';
@ -18,6 +18,7 @@ import templateSrv from 'app/features/templating/template_srv';
// 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) => {
// 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 confirmText = panel.alert ? 'YES' : null;
appEvents.emit('confirm-modal', {
appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Remove Panel',
text: 'Are you sure you want to remove this panel?',
text2: text2,
@ -45,7 +46,7 @@ export const duplicatePanel = (dashboard: DashboardModel, panel: PanelModel) =>
export const copyPanel = (panel: PanelModel) => {
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) => {
@ -53,15 +54,15 @@ const replacePanel = (dashboard: DashboardModel, newPanel: PanelModel, oldPanel:
return panel.id === oldPanel.id;
});
const deletedPanel = dashboard.panels.splice(index, 1);
dashboard.events.emit('panel-removed', deletedPanel);
const deletedPanel = dashboard.panels.splice(index, 1)[0];
dashboard.events.emit(panelRemoved, deletedPanel);
newPanel = new PanelModel(newPanel);
newPanel.id = oldPanel.id;
dashboard.panels.splice(index, 0, newPanel);
dashboard.sortPanelsByGridPos();
dashboard.events.emit('panel-added', newPanel);
dashboard.events.emit(panelAdded, newPanel);
};
export const editPanelJson = (dashboard: DashboardModel, panel: PanelModel) => {
@ -74,14 +75,14 @@ export const editPanelJson = (dashboard: DashboardModel, panel: PanelModel) => {
enableCopy: true,
};
appEvents.emit('show-modal', {
appEvents.emit(CoreEvents.showModal, {
src: 'public/app/partials/edit_json.html',
model: model,
});
};
export const sharePanel = (dashboard: DashboardModel, panel: PanelModel) => {
appEvents.emit('show-modal', {
appEvents.emit(CoreEvents.showModal, {
src: 'public/app/features/dashboard/components/ShareModal/template.html',
model: {
dashboard: dashboard,

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import { BackendSrv } from 'app/core/services/backend_srv';
import { ILocationService } from 'angular';
import { ValidationSrv } from 'app/features/manage-dashboards';
import { NavModelSrv } from 'app/core/nav_model_srv';
import { AppEvents } from '@grafana/data';
export default class CreateFolderCtrl {
title = '';
@ -28,7 +29,7 @@ export default class CreateFolderCtrl {
}
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));
});
}

View File

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

View File

@ -13,6 +13,7 @@ import {
import { updateNavIndex, updateLocation } from 'app/core/actions';
import { buildNavModel } from './navModel';
import appEvents from 'app/core/app_events';
import { AppEvents } from '@grafana/data';
export enum ActionTypes {
LoadFolder = 'LOAD_FOLDER',
@ -71,7 +72,7 @@ export function saveFolder(folder: FolderState): ThunkResult<void> {
});
// 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` }));
};

View File

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

View File

@ -1,6 +1,7 @@
import coreModule from 'app/core/core_module';
import appEvents from 'app/core/app_events';
import { BackendSrv } from 'app/core/services/backend_srv';
import { AppEvents } from '@grafana/data';
export class MoveToFolderCtrl {
dashboards: any;
@ -23,11 +24,11 @@ export class MoveToFolderCtrl {
const msg = `${result.successCount} dashboard${result.successCount === 1 ? '' : 's'} moved to ${
this.folder.title
}`;
appEvents.emit('alert-success', [header, msg]);
appEvents.emit(AppEvents.alertSuccess, [header, msg]);
}
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();

View File

@ -1,6 +1,7 @@
import coreModule from 'app/core/core_module';
import appEvents from 'app/core/app_events';
import angular, { ILocationService } from 'angular';
import { AppEvents } from '@grafana/data';
const template = `
<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);
} catch (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;
}
@ -58,7 +62,7 @@ export function uploadDashboardDirective(timer: any, $location: ILocationService
// Something
elem[0].addEventListener('change', file_selected, false);
} 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 { applyPanelTimeOverrides, getResolution } from 'app/features/dashboard/utils/panel';
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 { PanelModel } from 'app/features/dashboard/state';
import { CoreEvents } from 'app/types';
class MetricsPanelCtrl extends PanelCtrl {
scope: any;
@ -42,8 +43,8 @@ class MetricsPanelCtrl extends PanelCtrl {
this.scope = $scope;
this.panel.datasource = this.panel.datasource || null;
this.events.on('refresh', this.onMetricsPanelRefresh.bind(this));
this.events.on('panel-teardown', this.onPanelTearDown.bind(this));
this.events.on(PanelEvents.refresh, this.onMetricsPanelRefresh.bind(this));
this.events.on(PanelEvents.panelTeardown, this.onPanelTearDown.bind(this));
}
private onPanelTearDown() {
@ -71,7 +72,7 @@ class MetricsPanelCtrl extends PanelCtrl {
// 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.
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);
return this.$timeout(() => {
this.events.emit('data-error', err);
this.events.emit(PanelEvents.dataError, err);
});
}
@ -214,7 +215,7 @@ class MetricsPanelCtrl extends PanelCtrl {
}
try {
this.events.emit('data-frames-received', data);
this.events.emit(CoreEvents.dataFramesReceived, data);
} catch (err) {
this.processDataError(err);
}
@ -233,7 +234,7 @@ class MetricsPanelCtrl extends PanelCtrl {
}
try {
this.events.emit('data-received', result.data);
this.events.emit(PanelEvents.dataReceived, result.data);
} catch (err) {
this.processDataError(err);
}

View File

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

View File

@ -4,6 +4,7 @@ import $ from 'jquery';
import Drop from 'tether-drop';
// @ts-ignore
import baron from 'baron';
import { PanelEvents } from '@grafana/ui';
const module = angular.module('grafana.directives');
@ -73,7 +74,7 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => {
}
// update scrollbar after mounting
ctrl.events.on('component-did-mount', () => {
ctrl.events.on(PanelEvents.componentDidMount, () => {
if (ctrl.__proto__.constructor.scrollable) {
const scrollRootClass = 'baron baron__root baron__clipper panel-content--scrollable';
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);
$timeout(() => {
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)
setTimeout(() => {
// then recalc style
@ -123,7 +124,7 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => {
}, 10);
});
ctrl.events.on('render', () => {
ctrl.events.on(PanelEvents.render, () => {
// set initial height
if (!ctrl.height) {
ctrl.calculatePanelHeight(panelContainer[0].offsetHeight);

View File

@ -3,6 +3,8 @@ import coreModule from '../../core/core_module';
import { ILocationService } from 'angular';
import { BackendSrv } from 'app/core/services/backend_srv';
import { NavModelSrv } from 'app/core/nav_model_srv';
import { AppEventEmitter } from 'app/types';
import { AppEvents } from '@grafana/data';
export interface PlaylistItem {
value: any;
@ -27,7 +29,7 @@ export class PlaylistEditCtrl {
/** @ngInject */
constructor(
private $scope: any,
private $scope: AppEventEmitter,
private backendSrv: BackendSrv,
private $location: ILocationService,
$route: any,
@ -102,11 +104,11 @@ export class PlaylistEditCtrl {
savePromise.then(
() => {
this.$scope.appEvent('alert-success', ['Playlist saved', '']);
this.$scope.appEvent(AppEvents.alertSuccess, ['Playlist saved']);
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 kbn from 'app/core/utils/kbn';
import { store } from 'app/store/store';
import { CoreEvents } from 'app/types';
export const queryParamsToPreserve: { [key: string]: boolean } = {
kiosk: true,
@ -86,7 +87,7 @@ export class PlaylistSrv {
this.storeUnsub = store.subscribe(() => this.storeUpdated());
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}/dashboards`).then((dashboards: any) => {
@ -101,7 +102,7 @@ export class PlaylistSrv {
if (this.isPlaying) {
const queryParams = this.$location.search();
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);
}
appEvents.emit('playlist-stopped');
appEvents.emit(CoreEvents.playlistStopped);
}
}

View File

@ -2,6 +2,8 @@ import _ from 'lodash';
import coreModule from '../../core/core_module';
import { BackendSrv } from '@grafana/runtime';
import { NavModelSrv } from 'app/core/nav_model_srv';
import { CoreEvents } from 'app/types';
import { AppEvents } from '@grafana/data';
export class PlaylistsCtrl {
playlists: any;
@ -24,17 +26,17 @@ export class PlaylistsCtrl {
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);
}
);
}
removePlaylist(playlist: any) {
this.$scope.appEvent('confirm-modal', {
this.$scope.appEvent(CoreEvents.showConfirmModal, {
title: 'Delete',
text: 'Are you sure you want to delete playlist ' + playlist.name + '?',
yesText: 'Delete',

View File

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

View File

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

View File

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

View File

@ -9,9 +9,10 @@ import { DataSourceSrv as DataSourceService, getDataSourceSrv as getDataSourceSe
// Types
import { DataSourceApi, DataSourceSelectItem } from '@grafana/ui';
import { ScopedVars } from '@grafana/data';
import { ScopedVars, AppEvents } from '@grafana/data';
import { auto } from 'angular';
import { TemplateSrv } from '../templating/template_srv';
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
export class DatasourceSrv implements DataSourceService {
datasources: { [name: string]: DataSourceApi };
@ -20,7 +21,7 @@ export class DatasourceSrv implements DataSourceService {
constructor(
private $q: any,
private $injector: auto.IInjectorService,
private $rootScope: any,
private $rootScope: GrafanaRootScope,
private templateSrv: TemplateSrv
) {
this.init();
@ -86,7 +87,7 @@ export class DatasourceSrv implements DataSourceService {
deferred.resolve(instance);
})
.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;

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import appEvents from 'app/core/app_events';
import DatasourceSrv from '../plugins/datasource_srv';
import { VariableSrv } from './all';
import { TemplateSrv } from './template_srv';
import { AppEvents } from '@grafana/data';
export class VariableEditorCtrl {
/** @ngInject */
@ -85,13 +86,16 @@ export class VariableEditorCtrl {
}
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;
}
const sameName: any = _.find($scope.variables, { name: $scope.current.name });
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;
}
@ -100,7 +104,7 @@ export class VariableEditorCtrl {
_.isString($scope.current.query) &&
$scope.current.query.match(new RegExp('\\$' + $scope.current.name + '(/| |$)'))
) {
appEvents.emit('alert-warning', [
appEvents.emit(AppEvents.alertWarning, [
'Validation',
'Query cannot contain a reference to itself. Variable: $' + $scope.current.name,
]);
@ -128,7 +132,10 @@ export class VariableEditorCtrl {
if (err.data && 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 { TemplateSrv } from '../template_srv';
import { AppEvents } from '@grafana/data';
let mockEmit: any;
jest.mock('app/core/app_events', () => {
@ -32,7 +33,7 @@ describe('VariableEditorCtrl', () => {
it('should emit an error', () => {
return scope.runQuery().then(res => {
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][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