Angular: More isolation and removing of unused components (#41630)

* Angular: More isolation and removing of unused components

* Moved some more and fixed a test
This commit is contained in:
Torkel Ödegaard 2021-11-15 15:12:45 +01:00 committed by GitHub
parent 8ea75c9401
commit dbcefb70f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 17 additions and 350 deletions

View File

@ -8,7 +8,7 @@ coreModule.directive('datasourceHttpSettings', () => {
noDirectAccess: '@',
showForwardOAuthIdentityOption: '@',
},
templateUrl: 'public/app/features/datasources/partials/http_settings_next.html',
templateUrl: 'public/app/angular/partials/http_settings_next.html',
link: {
pre: ($scope: any) => {
// do not show access option if direct access is disabled

View File

@ -5,6 +5,6 @@ coreModule.directive('datasourceTlsAuthSettings', () => {
scope: {
current: '=',
},
templateUrl: 'public/app/features/datasources/partials/tls_auth_settings.html',
templateUrl: 'public/app/angular/partials/tls_auth_settings.html',
};
});

View File

@ -5,21 +5,11 @@ import config from 'app/core/config';
import coreModule from 'app/angular/core_module';
import { DataSourceApi, PanelEvents } from '@grafana/data';
import { importDataSourcePlugin, importAppPlugin } from './plugin_loader';
import { importPanelPlugin } from './importPanelPlugin';
import DatasourceSrv from './datasource_srv';
import { GrafanaRootScope } from 'app/angular/GrafanaCtrl';
import { importDataSourcePlugin, importAppPlugin } from '../../features/plugins/plugin_loader';
import { importPanelPlugin } from '../../features/plugins/importPanelPlugin';
/** @ngInject */
function pluginDirectiveLoader(
$compile: any,
datasourceSrv: DatasourceSrv,
$rootScope: GrafanaRootScope,
$http: any,
$templateCache: any,
$timeout: any,
$location: ILocationService
) {
function pluginDirectiveLoader($compile: any, $http: any, $templateCache: any, $location: ILocationService) {
function getTemplate(component: { template: any; templateUrl: any }) {
if (component.template) {
return Promise.resolve(component.template);

View File

@ -31,6 +31,9 @@ import './components/info_popover';
import './components/spectrum_picker';
import './components/code_editor/code_editor';
import './components/sql_part/sql_part_editor';
import './components/HttpSettingsCtrl';
import './components/TlsAuthSettingsCtrl';
import './components/plugin_component';
import './GrafanaCtrl';
export { AngularApp } from './AngularApp';

View File

@ -1,6 +1,7 @@
import { getBackendSrv, getDataSourceSrv } from '@grafana/runtime';
import { contextSrv } from 'app/core/core';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { validationSrv } from 'app/features/manage-dashboards/services/ValidationSrv';
import { getLinkSrv } from 'app/features/panel/panellinks/link_srv';
import coreModule from './core_module';
import { AnnotationsSrv } from './services/annotations_srv';
@ -11,5 +12,6 @@ export function registerComponents() {
coreModule.factory('dashboardSrv', () => getDashboardSrv());
coreModule.factory('datasourceSrv', () => getDataSourceSrv());
coreModule.factory('linkSrv', () => getLinkSrv());
coreModule.factory('validationSrv', () => validationSrv);
coreModule.service('annotationsSrv', AnnotationsSrv);
}

View File

@ -1,6 +1,3 @@
import './plugins/all';
import './dashboard';
import './manage-dashboards';
import './profile/all';
import './datasources/settings/HttpSettingsCtrl';
import './datasources/settings/TlsAuthSettingsCtrl';

View File

@ -1,205 +0,0 @@
import { map, find } from 'lodash';
import { IScope } from 'angular';
import { AppEvents } from '@grafana/data';
import coreModule from 'app/angular/core_module';
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 { promiseToDigest } from '../../../../angular/promiseToDigest';
import { createFolder } from 'app/features/manage-dashboards/state/actions';
export class FolderPickerCtrl {
declare initialTitle: string;
initialFolderId?: number;
labelClass?: string;
onChange: any;
onLoad: any;
onCreateFolder: any;
enterFolderCreation: any;
exitFolderCreation: any;
declare enableCreateNew: boolean;
declare enableReset: boolean;
rootName = 'General';
folder: any;
createNewFolder?: boolean;
newFolderName?: string;
newFolderNameTouched?: boolean;
hasValidationError?: boolean;
validationError: any;
isEditor: boolean;
dashboardId?: number;
/** @ngInject */
constructor(private validationSrv: ValidationSrv, private contextSrv: ContextSrv, private $scope: IScope) {
this.isEditor = this.contextSrv.isEditor;
if (this.labelClass === undefined) {
this.labelClass = 'width-7';
}
this.loadInitialValue();
}
getOptions(query: string) {
const params = {
query,
type: 'dash-folder',
permission: 'Edit',
};
return promiseToDigest(this.$scope)(
backendSrv.get('api/search', params).then((result: any) => {
if (
this.isEditor &&
(query === '' ||
query.toLowerCase() === 'g' ||
query.toLowerCase() === 'ge' ||
query.toLowerCase() === 'gen' ||
query.toLowerCase() === 'gene' ||
query.toLowerCase() === 'gener' ||
query.toLowerCase() === 'genera' ||
query.toLowerCase() === 'general')
) {
result.unshift({ title: this.rootName, id: 0 });
}
if (this.isEditor && this.enableCreateNew && query === '') {
result.unshift({ title: '-- New folder --', id: -1 });
}
if (this.enableReset && query === '' && this.initialTitle !== '') {
result.unshift({ title: this.initialTitle, id: null });
}
return map(result, (item) => {
return { text: item.title, value: item.id };
});
})
);
}
onFolderChange(option: { value: number; text: string }) {
if (!option) {
option = { value: 0, text: this.rootName };
} else if (option.value === -1) {
this.createNewFolder = true;
this.enterFolderCreation();
return;
}
this.onChange({ $folder: { id: option.value, title: option.text } });
}
newFolderNameChanged() {
this.newFolderNameTouched = true;
this.validationSrv
.validateNewFolderName(this.newFolderName)
.then(() => {
this.hasValidationError = false;
})
.catch((err: any) => {
this.hasValidationError = true;
this.validationError = err.message;
});
}
createFolder(evt: any) {
if (evt) {
evt.stopPropagation();
evt.preventDefault();
}
return promiseToDigest(this.$scope)(
createFolder({ title: this.newFolderName }).then((result: { title: string; id: number }) => {
appEvents.emit(AppEvents.alertSuccess, ['Folder created', 'OK']);
this.closeCreateFolder();
this.folder = { text: result.title, value: result.id };
this.onFolderChange(this.folder);
})
);
}
cancelCreateFolder(evt: any) {
if (evt) {
evt.stopPropagation();
evt.preventDefault();
}
this.closeCreateFolder();
this.loadInitialValue();
}
private closeCreateFolder() {
this.exitFolderCreation();
this.createNewFolder = false;
this.hasValidationError = false;
this.validationError = null;
this.newFolderName = '';
this.newFolderNameTouched = false;
}
private loadInitialValue() {
const resetFolder: { text: string; value: any } = { text: this.initialTitle, value: null };
const rootFolder: { text: string; value: any } = { text: this.rootName, value: 0 };
this.getOptions('').then((result: any[]) => {
let folder: { text: string; value: any } | undefined;
if (this.initialFolderId) {
// @ts-ignore
folder = find(result, { value: this.initialFolderId });
} else if (this.enableReset && this.initialTitle && this.initialFolderId === null) {
folder = resetFolder;
}
if (!folder) {
if (this.isEditor) {
folder = rootFolder;
} else {
// We shouldn't assign a random folder without the user actively choosing it on a persisted dashboard
const isPersistedDashBoard = this.dashboardId ? true : false;
if (isPersistedDashBoard) {
folder = resetFolder;
} else {
folder = result.length > 0 ? result[0] : resetFolder;
}
}
}
this.folder = folder;
// if this is not the same as our initial value notify parent
if (this.folder.value !== this.initialFolderId) {
this.onChange({ $folder: { id: this.folder.value, title: this.folder.text } });
}
});
}
}
export function folderPicker() {
return {
restrict: 'E',
templateUrl: 'public/app/features/dashboard/components/FolderPicker/template.html',
controller: FolderPickerCtrl,
bindToController: true,
controllerAs: 'ctrl',
scope: {
initialTitle: '<',
initialFolderId: '<',
labelClass: '@',
rootName: '@',
onChange: '&',
onCreateFolder: '&',
enterFolderCreation: '&',
exitFolderCreation: '&',
enableCreateNew: '@',
enableReset: '@',
dashboardId: '<?',
},
};
}
coreModule.directive('folderPicker', folderPicker);

View File

@ -8,7 +8,9 @@ import * as api from 'app/features/manage-dashboards/state/actions';
jest.mock('app/features/plugins/datasource_srv', () => ({}));
jest.mock('app/features/expressions/ExpressionDatasource', () => ({}));
jest.mock('app/features/manage-dashboards/services/ValidationSrv', () => ({
validateNewDashboardName: () => true,
validationSrv: {
validateNewDashboardName: () => true,
},
}));
jest.spyOn(api, 'searchFolders').mockResolvedValue([]);

View File

@ -3,7 +3,7 @@ import { Button, Input, Switch, Form, Field, InputControl, Modal } from '@grafan
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
import { SaveDashboardFormProps } from '../types';
import validationSrv from 'app/features/manage-dashboards/services/ValidationSrv';
import { validationSrv } from 'app/features/manage-dashboards/services/ValidationSrv';
interface SaveDashboardAsFormDTO {
title: string;

View File

@ -5,7 +5,7 @@ import Page from 'app/core/components/Page/Page';
import { createNewFolder } from '../state/actions';
import { getNavModel } from 'app/core/selectors/navModel';
import { StoreState } from 'app/types';
import validationSrv from '../../manage-dashboards/services/ValidationSrv';
import { validationSrv } from '../../manage-dashboards/services/ValidationSrv';
const mapStateToProps = (state: StoreState) => ({
navModel: getNavModel(state.navIndex, 'manage-dashboards'),

View File

@ -1 +0,0 @@
export { uploadDashboardDirective } from './uploadDashboardDirective';

View File

@ -1,71 +0,0 @@
import coreModule from 'app/angular/core_module';
import appEvents from 'app/core/app_events';
import angular 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"/>
<label class="btn btn-primary" for="dashupload">
{{btnText}}
</label>
`;
/** @ngInject */
export function uploadDashboardDirective() {
return {
restrict: 'E',
template: template,
scope: {
onUpload: '&',
btnText: '@?',
},
link: (scope: any, elem: JQuery) => {
scope.btnText = angular.isDefined(scope.btnText) ? scope.btnText : 'Upload .json file';
function file_selected(evt: any) {
const files = evt.target.files; // FileList object
const readerOnload = () => {
return (e: any) => {
let dash: any;
try {
dash = JSON.parse(e.target.result);
} catch (err) {
console.error(err);
appEvents.emit(AppEvents.alertError, [
'Import failed',
'JSON -> JS serialization failed: ' + err.message,
]);
return;
}
scope.$apply(() => {
scope.onUpload({ dash });
});
};
};
let i = 0;
let file = files[i];
while (file) {
const reader = new FileReader();
reader.onload = readerOnload();
reader.readAsText(file);
i += 1;
file = files[i];
}
}
const wnd: any = window;
// Check for the various File API support.
if (wnd.File && wnd.FileReader && wnd.FileList && wnd.Blob) {
// Something
elem[0].addEventListener('change', file_selected, false);
} else {
appEvents.emit(AppEvents.alertError, ['Oops', 'The HTML5 file APIs are not fully supported in this browser']);
}
},
};
}
coreModule.directive('dashUpload', uploadDashboardDirective);

View File

@ -1,5 +0,0 @@
// Services
export { ValidationSrv } from './services/ValidationSrv';
// Components
export * from './components/UploadDashboard';

View File

@ -1,4 +1,3 @@
import coreModule from 'app/angular/core_module';
import { backendSrv } from 'app/core/services/backend_srv';
const hitTypes = {
@ -63,8 +62,4 @@ export class ValidationSrv {
}
}
const validationSrv = new ValidationSrv();
export default validationSrv;
coreModule.service('validationSrv', ValidationSrv);
export const validationSrv = new ValidationSrv();

View File

@ -1,4 +1,4 @@
import validationSrv from '../services/ValidationSrv';
import { validationSrv } from '../services/ValidationSrv';
import { getBackendSrv } from '@grafana/runtime';
export const validateDashboardJson = (json: string) => {

View File

@ -1,3 +1 @@
import './datasource_srv';
import './plugin_component';
import './variableQueryEditorLoader';

View File

@ -1,38 +0,0 @@
import coreModule from 'app/angular/core_module';
import { importDataSourcePlugin } from './plugin_loader';
import React from 'react';
import ReactDOM from 'react-dom';
import { LegacyVariableQueryEditor } from '../variables/editor/LegacyVariableQueryEditor';
import { DataSourcePluginMeta } from '@grafana/data';
import { TemplateSrv } from '@grafana/runtime';
async function loadComponent(meta: DataSourcePluginMeta) {
const dsPlugin = await importDataSourcePlugin(meta);
if (dsPlugin.components.VariableQueryEditor) {
return dsPlugin.components.VariableQueryEditor;
} else {
return LegacyVariableQueryEditor;
}
}
/** @ngInject */
function variableQueryEditorLoader(templateSrv: TemplateSrv) {
return {
restrict: 'E',
link: async (scope: any, elem: JQuery) => {
const Component = await loadComponent(scope.currentDatasource.meta);
const props = {
datasource: scope.currentDatasource,
query: scope.current.query,
onChange: scope.onQueryChange,
templateSrv,
};
ReactDOM.render(<Component {...props} />, elem[0]);
scope.$on('$destroy', () => {
ReactDOM.unmountComponentAtNode(elem[0]);
});
},
};
}
coreModule.directive('variableQueryEditorLoader', variableQueryEditorLoader);