blob: 6c882dc0285f8604d9ecd1690f2f77fcf622e9f6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Licensed under the Apache License, Version 2.0
* See accompanying LICENSE file.
*/
angular.module('dashboard')
.config(['$stateProvider',
function ($stateProvider) {
'use strict';
$stateProvider
.state('compose_app', {
url: '/apps/compose',
templateUrl: 'views/apps/compose/compose.html',
controller: 'ComposeAppCtrl',
resolve: {
partitioners: ['models', function (models) {
return models.$get.partitioners();
}]
}
});
}])
// todo: remove the leading $ and rename $visNetworkStyle to vis, internal module does not have a $.
.controller('ComposeAppCtrl', ['$scope', '$state', '$modal', '$contextmenu',
'models', 'partitioners', '$visNetworkStyle', 'composeAppDialogs',
function ($scope, $state, $modal, $contextmenu, models, partitioners, $vis, dialogs) {
'use strict';
var chooseProcessorDialog = dialogs.create({
scope: $scope.$new(true), // isolated child scope
templateUrl: 'views/apps/compose/popups/choose_processor.html',
controller: 'ComposeAppChooseProcessorCtrl'
});
var chooseEdgeDialog = dialogs.create({
scope: $scope.$new(true), // isolated child scope
templateUrl: 'views/apps/compose/popups/choose_edge.html',
controller: 'ComposeAppChooseEdgeCtrl'
});
$scope.appName = "";
$scope.chooseProcessor = function (processor) {
var args = {
processor: processor
};
chooseProcessorDialog.show(args, function (processor) {
if (!processor.hasOwnProperty('id')) {
processor.id = newProcessorId();
}
processor.label = $vis.processorNameAsLabel(processor);
$scope.visGraph.data.nodes.update(processor);
});
};
$scope.chooseEdge = function (edge) {
var args = {
edge: edge,
partitioners: partitioners,
processors: {}
};
angular.forEach($scope.visGraph.data.nodes, function (processor) {
args.processors[processor.id] = {
text: 'Processor ' + processor.id,
subtext: processor.taskClass
};
});
chooseEdgeDialog.show(args, function (edge) {
$scope.visGraph.data.edges.update(edge);
});
};
var processorId = 0;
function newProcessorId() {
return processorId++;
}
$scope.visGraph = {
options: $vis.newOptions(/*height=*/'400px'),
data: $vis.newData(),
events: {
onDoubleClick: function (data) {
if (data.nodes.length === 1) {
var processor = $scope.visGraph.data.nodes.get(data.nodes[0]);
$scope.chooseProcessor(processor);
} else if (data.edges.length === 1) {
var edge = $scope.visGraph.data.edges.get(data.edges[0]);
$scope.chooseEdge(edge);
} else if (data.nodes.length + data.edges.length === 0) {
$scope.chooseProcessor();
}
},
onContext: function (data) {
if (data.hasOwnProperty('node')) {
$scope.selectItemModify = function () {
$scope.chooseProcessor($scope.visGraph.data.nodes.get(data.node));
};
$scope.selectItemDelete = function () {
deleteProcessor(data.node);
};
} else if (data.hasOwnProperty('edge')) {
$scope.selectItemModify = function () {
$scope.chooseEdge($scope.visGraph.data.edges.get(data.edge));
};
$scope.selectItemDelete = function () {
deleteEdge(data.edge);
};
} else {
return;
}
var elem = document.getElementById('contextmenu');
$contextmenu.popup(elem, data.pointer.DOM);
},
onDeletePressed: function (selection) {
if (selection.nodes.length === 1) {
deleteProcessor(selection.nodes[0]);
} else if (selection.edges.length === 1) {
deleteEdge(selection.edges[0]);
}
}
}
};
function deleteProcessor(processorId) {
$scope.visGraph.data.nodes.remove(processorId);
var edgeIds = _.chain($scope.visGraph.data.edges.get())
.filter(function (edge) {
return processorId == edge.from || processorId == edge.to;
})
.map('id')
.value();
$scope.visGraph.data.edges.remove(edgeIds);
}
function deleteEdge(edgeId) {
$scope.visGraph.data.edges.remove(edgeId);
}
$scope.files = {};
$scope.submitted = false;
$scope.$watch('uploads', function (uploads) {
$scope.files = {}; // todo: only one file can be uploaded once (issue 1450)
angular.forEach(uploads, function (file) {
if (_.endsWith(file.name, '.jar')) {
$scope.files[file.name] = file;
}
});
});
$scope.removeFile = function (name) {
delete $scope.files[name];
};
$scope.canSubmit = function () {
return !$scope.submitted &&
$scope.appName.length > 0 &&
$scope.visGraph.data.nodes.length > 0 &&
Object.keys($scope.files).length > 0;
};
$scope.submit = function () {
var data = $scope.visGraph.data;
var processors = data.nodes.get();
var edges = data.edges.get();
var args = {
appName: $scope.appName,
processors: processors.map(function (processor) {
return [processor.id, {
id: processor.id,
taskClass: processor.taskClass,
parallelism: processor.parallelism,
description: processor.description
}];
}),
dag: {
vertexList: _.map(processors, 'id'),
edgeList: edges.map(function (edge) {
return [edge.from, edge.partitionerClass, edge.to]
})
},
userconfig: null
};
$scope.submitting = true;
models.submitDag($scope.files, args, function (response) {
$scope.submitting = false;
$scope.submitted = response.success;
$scope.shouldNoticeSubmitFailed = !response.success;
if (response.success) {
$scope.appId = response.appId;
} else {
$scope.error = response.error;
$scope.hasStackTrace = response.stackTrace.length > 0;
$scope.showErrorInNewWin = function () {
if ($scope.hasStackTrace) {
var popup = window.open('', 'Error Log');
var html = [$scope.error].concat(response.stackTrace).join('\n');
popup.document.open();
popup.document.write('<pre>' + html + '</pre>');
popup.document.close();
}
}
}
});
};
$scope.view = function () {
$state.go('streamingapp.overview', {appId: $scope.appId});
};
// Angular template cannot call the function directly, so export a function.
$scope.keys = Object.keys;
}])
.factory('composeAppDialogs', ['$modal', function ($modal) {
'use strict';
return {
create: function (options) {
var dialog = $modal({
scope: options.scope,
templateUrl: options.templateUrl,
controller: options.controller,
backdrop: 'static',
keyboard: true,
show: false
});
var showDialogFn = dialog.show;
dialog.show = function (args, onChange) {
dialog.$options.scope.onChange = onChange;
angular.forEach(args, function (value, key) {
dialog.$options.scope[key] = value;
});
showDialogFn();
};
return dialog;
}
};
}])
;