Merge branch 'master' into 10630_folder_api

This commit is contained in:
Marcus Efraimsson
2018-02-12 15:11:58 +01:00
123 changed files with 1773 additions and 790 deletions

View File

@@ -1,4 +1,5 @@
import appEvents from 'app/core/app_events';
import locationUtil from 'app/core/utils/location_util';
export class CreateFolderCtrl {
title = '';
@@ -19,7 +20,7 @@ export class CreateFolderCtrl {
return this.backendSrv.createFolder({ title: this.title }).then(result => {
appEvents.emit('alert-success', ['Folder Created', 'OK']);
this.$location.url(result.url);
this.$location.url(locationUtil.stripBaseFromUrl(result.url));
});
}
@@ -27,7 +28,7 @@ export class CreateFolderCtrl {
this.titleTouched = true;
this.validationSrv
.validateNewDashboardOrFolderName(this.title)
.validateNewFolderName(this.title)
.then(() => {
this.hasValidationError = false;
})

View File

@@ -18,7 +18,7 @@ export class DashboardImportCtrl {
nameValidationError: any;
/** @ngInject */
constructor(private backendSrv, private validationSrv, navModelSrv, private $location, private $scope, $routeParams) {
constructor(private backendSrv, private validationSrv, navModelSrv, private $location, $routeParams) {
this.navModel = navModelSrv.getNav('create', 'import');
this.step = 1;
@@ -93,7 +93,7 @@ export class DashboardImportCtrl {
this.nameExists = false;
this.validationSrv
.validateNewDashboardOrFolderName(this.dash.title)
.validateNewDashboardName(0, this.dash.title)
.then(() => {
this.hasNameValidationError = false;
})
@@ -124,8 +124,7 @@ export class DashboardImportCtrl {
inputs: inputs,
})
.then(res => {
this.$location.url('dashboard/' + res.importedUri);
this.$scope.dismiss();
this.$location.url(res.importedUrl);
});
}

View File

@@ -1,5 +1,6 @@
import coreModule from 'app/core/core_module';
import { DashboardModel } from './dashboard_model';
import locationUtil from 'app/core/utils/location_util';
export class DashboardSrv {
dash: any;
@@ -19,7 +20,10 @@ export class DashboardSrv {
return this.dash;
}
handleSaveDashboardError(clone, err) {
handleSaveDashboardError(clone, options, err) {
options = options || {};
options.overwrite = true;
if (err.data && err.data.status === 'version-mismatch') {
err.isHandled = true;
@@ -30,7 +34,7 @@ export class DashboardSrv {
yesText: 'Save & Overwrite',
icon: 'fa-warning',
onConfirm: () => {
this.save(clone, { overwrite: true });
this.save(clone, options);
},
});
}
@@ -40,12 +44,12 @@ export class DashboardSrv {
this.$rootScope.appEvent('confirm-modal', {
title: 'Conflict',
text: 'Dashboard with the same name exists.',
text: 'A dashboard with the same name in selected folder already exists.',
text2: 'Would you still like to save this dashboard?',
yesText: 'Save & Overwrite',
icon: 'fa-warning',
onConfirm: () => {
this.save(clone, { overwrite: true });
this.save(clone, options);
},
});
}
@@ -74,7 +78,7 @@ export class DashboardSrv {
this.dash.version = data.version;
if (data.url !== this.$location.path()) {
this.$location.url(data.url);
this.$location.url(locationUtil.stripBaseFromUrl(data.url)).replace();
}
this.$rootScope.appEvent('dashboard-saved', this.dash);
@@ -90,7 +94,7 @@ export class DashboardSrv {
return this.backendSrv
.saveDashboard(clone, options)
.then(this.postSave.bind(this, clone))
.catch(this.handleSaveDashboardError.bind(this, clone));
.catch(this.handleSaveDashboardError.bind(this, clone, options));
}
saveDashboard(options, clone) {

View File

@@ -93,7 +93,6 @@ export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelP
}
renderPanelItem(panel, index) {
console.log('render panel', index);
return (
<div key={index} className="add-panel__item" onClick={() => this.onAddPanel(panel)} title={panel.name}>
<img className="add-panel__item-img" src={panel.info.logos.small} />

View File

@@ -67,7 +67,7 @@ export class FolderPickerCtrl {
this.newFolderNameTouched = true;
this.validationSrv
.validateNewDashboardOrFolderName(this.newFolderName)
.validateNewFolderName(this.newFolderName)
.then(() => {
this.hasValidationError = false;
})

View File

@@ -4,6 +4,7 @@ import _ from 'lodash';
import angular from 'angular';
import moment from 'moment';
import locationUtil from 'app/core/utils/location_util';
import { DashboardModel } from '../dashboard_model';
import { HistoryListOpts, RevisionsModel, CalculateDiffOptions, HistorySrv } from './history_srv';
@@ -185,7 +186,7 @@ export class HistoryListCtrl {
return this.historySrv
.restoreDashboard(this.dashboard, version)
.then(response => {
this.$location.path('dashboard/db/' + response.slug);
this.$location.url(locationUtil.stripBaseFromUrl(response.url)).replace();
this.$route.reload();
this.$rootScope.appEvent('alert-success', ['Dashboard restored', 'Restored from version ' + version]);
})

View File

@@ -96,13 +96,14 @@
</div>
<div class="dashboard-settings__content" ng-if="ctrl.viewId === 'permissions'" >
<dashboard-permissions ng-if="ctrl.dashboard"
<dashboard-permissions ng-if="ctrl.dashboard && !ctrl.hasUnsavedFolderChange"
dashboardId="ctrl.dashboard.id"
backendSrv="ctrl.backendSrv"
folderTitle="ctrl.dashboard.meta.folderTitle"
folderSlug="ctrl.dashboard.meta.folderSlug"
folderId="ctrl.dashboard.meta.folderId"
folder="ctrl.getFolder()"
/>
<div ng-if="ctrl.hasUnsavedFolderChange">
<h5>You have changed folder, please save to view permissions.</h5>
</div>
</div>
<div class="dashboard-settings__content" ng-if="ctrl.viewId === '404'">

View File

@@ -14,6 +14,7 @@ export class SettingsCtrl {
canSave: boolean;
canDelete: boolean;
sections: any[];
hasUnsavedFolderChange: boolean;
/** @ngInject */
constructor(private $scope, private $location, private $rootScope, private backendSrv, private dashboardSrv) {
@@ -38,6 +39,7 @@ export class SettingsCtrl {
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);
}
buildSectionList() {
@@ -135,6 +137,10 @@ export class SettingsCtrl {
this.dashboardSrv.saveDashboard();
}
onPostSave() {
this.hasUnsavedFolderChange = false;
}
hideSettings() {
var urlParams = this.$location.search();
delete urlParams.editview;
@@ -195,7 +201,15 @@ export class SettingsCtrl {
onFolderChange(folder) {
this.dashboard.meta.folderId = folder.id;
this.dashboard.meta.folderTitle = folder.title;
this.dashboard.meta.folderSlug = folder.slug;
this.hasUnsavedFolderChange = true;
}
getFolder() {
return {
id: this.dashboard.meta.folderId,
title: this.dashboard.meta.folderTitle,
url: this.dashboard.meta.folderUrl,
};
}
}

View File

@@ -19,10 +19,10 @@ describe('DashboardImportCtrl', function() {
};
validationSrv = {
validateNewDashboardOrFolderName: jest.fn().mockReturnValue(Promise.resolve()),
validateNewDashboardName: jest.fn().mockReturnValue(Promise.resolve()),
};
ctx.ctrl = new DashboardImportCtrl(backendSrv, validationSrv, navModelSrv, {}, {}, {});
ctx.ctrl = new DashboardImportCtrl(backendSrv, validationSrv, navModelSrv, {}, {});
});
describe('when uploading json', function() {

View File

@@ -1,13 +1,27 @@
import coreModule from 'app/core/core_module';
const hitTypes = {
FOLDER: 'dash-folder',
DASHBOARD: 'dash-db',
};
export class ValidationSrv {
rootName = 'general';
/** @ngInject */
constructor(private $q, private backendSrv) {}
validateNewDashboardOrFolderName(name) {
validateNewDashboardName(folderId, name) {
return this.validate(folderId, name, 'A dashboard in this folder with the same name already exists');
}
validateNewFolderName(name) {
return this.validate(0, name, 'A folder or dashboard in the general folder with the same name already exists');
}
private validate(folderId, name, existingErrorMessage) {
name = (name || '').trim();
const nameLowerCased = name.toLowerCase();
if (name.length === 0) {
return this.$q.reject({
@@ -16,7 +30,7 @@ export class ValidationSrv {
});
}
if (name.toLowerCase() === this.rootName) {
if (folderId === 0 && nameLowerCased === this.rootName) {
return this.$q.reject({
type: 'EXISTING',
message: 'This is a reserved name and cannot be used for a folder.',
@@ -25,12 +39,26 @@ export class ValidationSrv {
let deferred = this.$q.defer();
this.backendSrv.search({ query: name }).then(res => {
for (let hit of res) {
if (name.toLowerCase() === hit.title.toLowerCase()) {
const promises = [];
promises.push(this.backendSrv.search({ type: hitTypes.FOLDER, folderIds: [folderId], query: name }));
promises.push(this.backendSrv.search({ type: hitTypes.DASHBOARD, folderIds: [folderId], query: name }));
this.$q.all(promises).then(res => {
let hits = [];
if (res.length > 0 && res[0].length > 0) {
hits = res[0];
}
if (res.length > 1 && res[1].length > 0) {
hits = hits.concat(res[1]);
}
for (let hit of hits) {
if (nameLowerCased === hit.title.toLowerCase()) {
deferred.reject({
type: 'EXISTING',
message: 'A folder or dashboard with the same name already exists',
message: existingErrorMessage,
});
break;
}

View File

@@ -33,7 +33,7 @@
Old picker
<user-picker user-picked="ctrl.userPicked($user)"></user-picker>
-->
<select-user-picker handlePicked="ctrl.userPicked" backendSrv="ctrl.backendSrv"></select-user-picker>
<select-user-picker class="width-7" handlePicked="ctrl.userPicked" backendSrv="ctrl.backendSrv"></select-user-picker>
</div>
</form>

View File

@@ -100,7 +100,9 @@ module.directive('grafanaPanel', function($rootScope, $document, $timeout) {
// update scrollbar after mounting
ctrl.events.on('component-did-mount', () => {
if (ctrl.__proto__.constructor.scrollable) {
panelScrollbar = new PerfectScrollbar(panelContent[0]);
panelScrollbar = new PerfectScrollbar(panelContent[0], {
wheelPropagation: true,
});
}
});

View File

@@ -9,7 +9,7 @@ export class SoloPanelCtrl {
$scope.init = function() {
contextSrv.sidemenu = false;
appEvents.emit('toggle-sidemenu');
appEvents.emit('toggle-sidemenu-hidden');
var params = $location.search();
panelId = parseInt(params.panelId);

View File

@@ -6,7 +6,7 @@
<i class="icon-gf icon-gf-dashboard"></i>
</td>
<td>
<a href="dashboard/{{dash.importedUri}}" ng-show="dash.imported">
<a href="{{dash.importedUrl}}" ng-show="dash.imported">
{{dash.title}}
</a>
<span ng-show="!dash.imported">

View File

@@ -52,7 +52,7 @@
<empty-list-cta model="{
title: 'There are no data sources defined yet',
buttonIcon: 'gicon gicon-add-datasources',
buttonLink: '/datasources/new',
buttonLink: 'datasources/new',
buttonTitle: 'Add data source',
proTip: 'You can also define data sources through configuration files.',
proTipLink: 'http://docs.grafana.org/administration/provisioning/#datasources?utm_source=grafana_ds_list',