feat: Apply frontend interpreter to paragraph

This commit is contained in:
1ambda 2017-01-17 19:42:26 +09:00
parent a163044d61
commit 5810bf1de5
10 changed files with 257 additions and 135 deletions

View file

@ -40,7 +40,7 @@ export default class TranslatorInterpreter extends AbstractFrontendInterpreter {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_ACCESS_KEY_HERE',
'Authorization': 'Bearer YOUR_ACCESS_KEY',
},
body: JSON.stringify({
'q': text,
@ -48,16 +48,16 @@ export default class TranslatorInterpreter extends AbstractFrontendInterpreter {
'target': 'ko',
'format': 'text'
})
}).then((response) => {
}).then(response => {
if (response.status === 200) {
return response.json()
}
throw new Error(`https://translation.googleapis.com/language/translate/v2 ${response.status} (${response.statusText})`);
}).then((json) => {
const extracted = json.data.translations.map(t => {
return t.translatedText;
});
return extracted.join('\n');
const extracted = json.data.translations.map(t => {
return t.translatedText;
});
return extracted.join('\n');
});
}
}

View file

@ -70,7 +70,7 @@ export class AbstractFrontendInterpreter {
* Currently, `display` only allows DefaultDisplayType
* as a type of result to avoid to recursive evaluation of display results.
*
* @param paragraphText {string}
* @param paragraphText {string} which doesn't include magic
* @return {FrontendInterpreterResult}
*/
display(paragraphText) {

View file

@ -28,12 +28,14 @@ NotebookCtrl.$inject = [
'ngToast',
'noteActionSrv',
'noteVarShareService',
'TRASH_FOLDER_ID'
'TRASH_FOLDER_ID',
'heliumService',
];
function NotebookCtrl($scope, $route, $routeParams, $location, $rootScope,
$http, websocketMsgSrv, baseUrlSrv, $timeout, saveAsService,
ngToast, noteActionSrv, noteVarShareService, TRASH_FOLDER_ID) {
ngToast, noteActionSrv, noteVarShareService, TRASH_FOLDER_ID,
heliumService) {
ngToast.dismiss();

View file

@ -25,7 +25,7 @@ limitations under the License.
<!-- Run / Cancel button -->
<span ng-if="!revisionView">
<span class="icon-control-play" style="cursor:pointer;color:#3071A9" tooltip-placement="top" tooltip="Run this paragraph (Shift+Enter)"
ng-click="runParagraph(getEditorValue())"
ng-click="runParagraphFromButton(getEditorValue())"
ng-show="paragraph.status!='RUNNING' && paragraph.status!='PENDING' && paragraph.config.enabled"></span>
<span class="icon-control-pause" style="cursor:pointer;color:#CD5C5C" tooltip-placement="top"
tooltip="Cancel (Ctrl+{{ (isMac ? 'Option' : 'Alt') }}+c)"

View file

@ -12,6 +12,8 @@
* limitations under the License.
*/
import { AbstractFrontendInterpreter } from '../../frontend-interpreter'
angular.module('zeppelinWebApp').controller('ParagraphCtrl', ParagraphCtrl);
ParagraphCtrl.$inject = [
@ -29,15 +31,19 @@ ParagraphCtrl.$inject = [
'baseUrlSrv',
'ngToast',
'saveAsService',
'noteVarShareService'
'noteVarShareService',
'heliumService'
];
function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $location,
$timeout, $compile, $http, $q, websocketMsgSrv,
baseUrlSrv, ngToast, saveAsService, noteVarShareService) {
baseUrlSrv, ngToast, saveAsService, noteVarShareService,
heliumService) {
var ANGULAR_FUNCTION_OBJECT_NAME_PREFIX = '_Z_ANGULAR_FUNC_';
$scope.parentNote = null;
$scope.paragraph = null;
$scope.paragraph = {};
$scope.paragraph.results = {};
$scope.paragraph.results.msg = [];
$scope.originalText = '';
$scope.editor = null;
@ -219,21 +225,30 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
websocketMsgSrv.cancelParagraphRun(paragraph.id);
};
$scope.runParagraph = function(data) {
websocketMsgSrv.runParagraph($scope.paragraph.id, $scope.paragraph.title,
data, $scope.paragraph.config, $scope.paragraph.settings.params);
$scope.originalText = angular.copy(data);
$scope.dirtyText = undefined;
$scope.runParagraphUsingFrontendInterpreter = function(intp, paragraphText, magic) {
// clear results which is watched by `ng-repeat`. without this,
// frontend interpreter results cannot be rendered in view twice more
$scope.paragraph.results = {};
$scope.$digest();
if ($scope.paragraph.config.editorSetting.editOnDblClick) {
closeEditorAndOpenTable($scope.paragraph);
} else if (editorSetting.isOutputHidden &&
!$scope.paragraph.config.editorSetting.editOnDblClick) {
// %md/%angular repl make output to be hidden by default after running
// so should open output if repl changed from %md/%angular to another
openEditorAndOpenTable($scope.paragraph);
try {
// remove magic from paragraphText
const splited = paragraphText.split(magic);
const textWithoutMagic = splited[1];
$scope.paragraph.status = 'FINISHED';
$scope.paragraph.results.msg = intp.interpret(textWithoutMagic);
$scope.paragraph.config.tableHide = false;
// TODO(1ambda): broadcast to other clients
} catch (error) {
$scope.paragraph.status = 'ERROR';
$scope.paragraph.errorMessage = error.stack;
console.error('Failed to execute FrontendInterpreter.interpret\n', error);
}
editorSetting.isOutputHidden = $scope.paragraph.config.editorSetting.editOnDblClick;
};
$scope.runParagraphUsingBackendInterpreter = function(paragraphText) {
websocketMsgSrv.runParagraph($scope.paragraph.id, $scope.paragraph.title,
paragraphText, $scope.paragraph.config, $scope.paragraph.settings.params);
};
$scope.saveParagraph = function(paragraph) {
@ -251,10 +266,43 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
commitParagraph(paragraph);
};
$scope.run = function(paragraph, editorValue) {
if (editorValue && !$scope.isRunning(paragraph)) {
$scope.runParagraph(editorValue);
$scope.runParagraph = function(paragraphText) {
if (!paragraphText || $scope.isRunning($scope.paragraph)) {
return;
}
const magic = AbstractFrontendInterpreter.extractMagic(paragraphText);
const frontendIntp = heliumService.getFrontendInterpreterUsingMagic(magic);
if (frontendIntp) {
$scope.runParagraphUsingFrontendInterpreter(frontendIntp, paragraphText, magic);
} else {
$scope.runParagraphUsingBackendInterpreter(paragraphText);
}
$scope.originalText = angular.copy(paragraphText);
$scope.dirtyText = undefined;
if ($scope.paragraph.config.editorSetting.editOnDblClick) {
closeEditorAndOpenTable($scope.paragraph);
} else if (editorSetting.isOutputHidden &&
!$scope.paragraph.config.editorSetting.editOnDblClick) {
// %md/%angular repl make output to be hidden by default after running
// so should open output if repl changed from %md/%angular to another
openEditorAndOpenTable($scope.paragraph);
}
editorSetting.isOutputHidden = $scope.paragraph.config.editorSetting.editOnDblClick;
};
$scope.runParagraphFromShortcut = function(paragraphText) {
// we need to call `$digest()` to update view immediately
$scope.runParagraph(paragraphText);
$scope.$digest();
};
$scope.runParagraphFromButton = function(paragraphText) {
// we come here from `$scope.on`, so we don't need to call `$digest()`
$scope.runParagraph(paragraphText)
};
$scope.moveUp = function(paragraph) {
@ -452,6 +500,10 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
}
};
// $scope.text = asdasd
// dirtyText =
// originalText
$scope.aceChanged = function(_, editor) {
var session = editor.getSession();
var dirtyText = session.getValue();
@ -807,15 +859,6 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
editor.navigateFileEnd();
};
$scope.getResultType = function(paragraph) {
var pdata = (paragraph) ? paragraph : $scope.paragraph;
if (pdata.results && pdata.results.type) {
return pdata.results.type;
} else {
return 'TEXT';
}
};
$scope.parseTableCell = function(cell) {
if (!isNaN(cell)) {
if (cell.length === 0 || Number(cell) > Number.MAX_SAFE_INTEGER || Number(cell) < Number.MIN_SAFE_INTEGER) {
@ -1092,7 +1135,7 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
// move focus to next paragraph
$scope.$emit('moveFocusToNextParagraph', paragraphId);
} else if (keyEvent.shiftKey && keyCode === 13) { // Shift + Enter
$scope.run($scope.paragraph, $scope.getEditorValue());
$scope.runParagraphFromShortcut($scope.getEditorValue());
} else if (keyEvent.ctrlKey && keyEvent.altKey && keyCode === 67) { // Ctrl + Alt + c
$scope.cancelParagraph($scope.paragraph);
} else if (keyEvent.ctrlKey && keyEvent.altKey && keyCode === 68) { // Ctrl + Alt + d

View file

@ -58,9 +58,7 @@ limitations under the License.
ng-init="init(result, paragraph.config.results[$index], paragraph, $index)"
ng-include src="'app/notebook/paragraph/result/result.html'">
</div>
<div id="{{paragraph.id}}_error"
class="error text"
ng-if="paragraph.status == 'ERROR'"
<div id="{{paragraph.id}}_error" class="error text"
ng-bind="paragraph.errorMessage">
</div>
</div>

View file

@ -19,6 +19,10 @@ import PiechartVisualization from '../../../visualization/builtins/visualization
import AreachartVisualization from '../../../visualization/builtins/visualization-areachart';
import LinechartVisualization from '../../../visualization/builtins/visualization-linechart';
import ScatterchartVisualization from '../../../visualization/builtins/visualization-scatterchart';
import {
DefaultDisplayType,
FrontendInterpreterResult,
} from '../../../frontend-interpreter'
angular.module('zeppelinWebApp').controller('ResultCtrl', ResultCtrl);
@ -149,14 +153,12 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
// image data
$scope.imageData;
$scope.textRendererInitialized = false;
$scope.init = function(result, config, paragraph, index) {
console.log('result controller init %o %o %o', result, config, index);
// register helium plugin vis
var heliumVis = heliumService.get();
console.log('Helium visualizations %o', heliumVis);
heliumVis.forEach(function(vis) {
var visBundles = heliumService.getVisualizationBundles();
visBundles.forEach(function(vis) {
$scope.builtInTableDataVisualizationList.push({
id: vis.id,
name: vis.name,
@ -166,7 +168,7 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
class: vis.class
};
});
updateData(result, config, paragraph, index);
renderResult($scope.type);
};
@ -259,92 +261,170 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
}
if (activeApp) {
var app = _.find($scope.apps, {id: activeApp});
renderApp(app);
const app = _.find($scope.apps, {id: activeApp});
renderApp(app, `p${appState.id}`);
} else {
if (type === 'TABLE') {
/**
* find proper interpreter which can handle the non-default display type
* the displayed type which is generated from `display()`
* should be one of the default interpreter type
*/
// const isDefaultDisplayType = DefaultDisplayType[type];
// if (!isDefaultDisplayType) {
// const intp = heliumService.getFrontendInterpreterWithDisplayType(type);
//
// if (intp) {
// // currently display only accepts `Object` data not
// // doesn't support function and promise
// const result = intp.display(data);
// type = result.getType();
// data = result.getData();
// }
// }
if (type === DefaultDisplayType.TABLE) {
$scope.renderGraph($scope.graphMode, refresh);
} else if (type === 'HTML') {
renderHtml();
} else if (type === 'ANGULAR') {
renderAngular();
} else if (type === 'TEXT') {
renderText();
} else if (type === DefaultDisplayType.HTML) {
renderHtml(`p${$scope.id}_html`, data);
} else if (type === DefaultDisplayType.ANGULAR) {
renderAngular(`p${$scope.id}_angular`, data);
} else if (type === DefaultDisplayType.TEXT) {
renderText(`p${$scope.id}_text`, data);
} else if (type === DefaultDisplayType.ELEMENT) {
renderElem(`p${$scope.id}_elem`, data);
} else {
console.error(`Unknown Display Type: ${type}`);
}
}
};
var renderHtml = function() {
var retryRenderer = function() {
var htmlEl = angular.element('#p' + $scope.id + '_html');
if (htmlEl.length) {
try {
htmlEl.html(data);
/**
* generates actually object which will be consumed from `data` property
* feed it to the success callback.
* if error occurs, the error is passed to the failure callback
*
* @param generator {Object or Promise or Function}
* @param type {string} Display Type
* @param successCallback
* @param failureCallback
*/
const generateData = function(generator, type, successCallback, failureCallback) {
if (FrontendInterpreterResult.isFunctionGenerator(generator)) {
try {
successCallback(generator());
} catch (error) {
failureCallback(error);
console.error(`Failed to handle ${type} type, function generator\n`, error);
}
} else if (FrontendInterpreterResult.isPromiseGenerator(generator)) {
generator
.then((generated) => { successCallback(generated); })
.catch((error) => {
failureCallback(error);
console.error(`Failed to handle ${type} type, promise generator\n`, error);
});
} else if (FrontendInterpreterResult.isObjectGenerator(generator)) {
try {
successCallback(generator);
} catch (error) {
console.error(`Failed to handle ${type} type, object generator\n`, error);
}
}
};
htmlEl.find('pre code').each(function(i, e) {
const renderElem = function(targetElemId, generator) {
function retryRenderer() {
const elem = angular.element(`#${targetElemId}`);
if (!elem.length) {
$timeout(retryRenderer, 10);
return;
}
generateData(() => { generator(targetElemId) }, DefaultDisplayType.ELEMENT,
() => {}, /** HTML element will be filled in generator . thus pass empty success callback */
(error) => { elem.html(`${error.stack}`); }
);
}
$timeout(retryRenderer);
};
const renderHtml = function(targetElemId, generator) {
function retryRenderer() {
const elem = angular.element(`#${targetElemId}`);
if (!elem.length) {
$timeout(retryRenderer, 10);
return;
}
generateData(generator, DefaultDisplayType.HTML,
(generated) => {
elem.html(generated);
elem.find('pre code').each(function(i, e) {
hljs.highlightBlock(e);
});
/*eslint new-cap: [2, {"capIsNewExceptions": ["MathJax.Hub.Queue"]}]*/
MathJax.Hub.Queue(['Typeset', MathJax.Hub, htmlEl[0]]);
} catch (err) {
console.log('HTML rendering error %o', err);
}
} else {
$timeout(retryRenderer, 10);
}
};
MathJax.Hub.Queue(['Typeset', MathJax.Hub, elem[0]]);
},
(error) => { elem.html(`${error.stack}`); }
);
}
$timeout(retryRenderer);
};
var renderAngular = function() {
var retryRenderer = function() {
if (angular.element('#p' + $scope.id + '_angular').length) {
try {
angular.element('#p' + $scope.id + '_angular').html(data);
var paragraphScope = noteVarShareService.get(paragraph.id + '_paragraphScope');
$compile(angular.element('#p' + $scope.id + '_angular').contents())(paragraphScope);
} catch (err) {
console.log('ANGULAR rendering error %o', err);
}
} else {
const renderAngular = function(targetElemId, generator) {
function retryRenderer() {
const elem = angular.element(`#${targetElemId}`);
if (!elem.length) {
$timeout(retryRenderer, 10);
return;
}
};
const paragraphScope = noteVarShareService.get(`${paragraph.id}_paragraphScope`);
generateData(generator, DefaultDisplayType.ANGULAR,
(generated) => {
elem.html(generated);
$compile(elem.contents())(paragraphScope);
},
(error) => { elem.html(`${error.stack}`); }
);
}
$timeout(retryRenderer);
};
var getTextEl = function (paragraphId) {
return angular.element('#p' + $scope.id + '_text');
const getTextResultElem = function (resultId) {
return angular.element('#p' + resultId + '_text');
};
const renderText = function(targetElemId, generator) {
function retryRenderer() {
const elem = angular.element(`#${targetElemId}`);
if (!elem.length) {
$timeout(retryRenderer, 10);
return;
}
generateData(generator, DefaultDisplayType.TEXT,
(generated) => {
// clear all lines before render
clearTextOutput();
$scope.textRendererInitialized = true;
if (generated) { appendTextOutput(generated); }
else { flushAppendQueue(); }
elem.bind('mousewheel', (e) => { $scope.keepScrollDown = false; });
},
(error) => { elem.html(`${error.stack}`); }
);
}
$timeout(retryRenderer);
}
var textRendererInitialized = false;
var renderText = function() {
var retryRenderer = function() {
var textEl = getTextEl($scope.id);
if (textEl.length) {
// clear all lines before render
clearTextOutput();
textRendererInitialized = true;
if (data) {
appendTextOutput(data);
} else {
flushAppendQueue();
}
getTextEl($scope.id).bind('mousewheel', function(e) {
$scope.keepScrollDown = false;
});
} else {
$timeout(retryRenderer, 10);
}
};
$timeout(retryRenderer);
};
var clearTextOutput = function() {
var textEl = getTextEl($scope.id);
var textEl = getTextResultElem($scope.id);
if (textEl.length) {
textEl.children().remove();
}
@ -359,11 +439,11 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
};
var appendTextOutput = function(msg) {
if (!textRendererInitialized) {
if (!$scope.textRendererInitialized) {
textAppendQueueBeforeInitialize.push(msg);
} else {
flushAppendQueue();
var textEl = getTextEl($scope.id);
var textEl = getTextResultElem($scope.id);
if (textEl.length) {
var lines = msg.split('\n');
for (var i = 0; i < lines.length; i++) {
@ -371,7 +451,7 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
}
}
if ($scope.keepScrollDown) {
var doc = getTextEl($scope.id);
var doc = getTextResultElem($scope.id);
doc[0].scrollTop = doc[0].scrollHeight;
}
}
@ -725,22 +805,23 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
});
};
var renderApp = function(appState) {
var retryRenderer = function() {
var targetEl = angular.element(document.getElementById('p' + appState.id));
console.log('retry renderApp %o', targetEl);
if (targetEl.length) {
const renderApp = function(targetElemId, appState) {
function retryRenderer() {
var elem = angular.element(document.getElementById(elememId));
console.log('retry renderApp %o', elem);
if (!elem.length) {
$timeout(retryRenderer, 1000);
} else {
try {
console.log('renderApp %o', appState);
targetEl.html(appState.output);
$compile(targetEl.contents())(getAppScope(appState));
elem.html(appState.output);
$compile(elem.contents())(getAppScope(appState));
} catch (err) {
console.log('App rendering error %o', err);
}
} else {
$timeout(retryRenderer, 1000);
}
};
}
$timeout(retryRenderer);
};

View file

@ -67,13 +67,15 @@ limitations under the License.
tooltip="Scroll Top"></div>
</div>
<div id="p{{id}}_html"
class="resultContained"
<div id="p{{id}}_elem" class="resultContained"
ng-if="type == 'ELEMENT'">
</div>
<div id="p{{id}}_html" class="resultContained"
ng-if="type == 'HTML'">
</div>
<div id="p{{id}}_angular"
class="resultContained"
<div id="p{{id}}_angular" class="resultContained"
ng-if="type == 'ANGULAR'">
</div>

View file

@ -108,5 +108,5 @@ import {
this.disable = function(name) {
return $http.post(baseUrlSrv.getRestApiBase() + '/helium/disable/' + name);
};
};
}
})();

View file

@ -41,10 +41,6 @@ describe('Controller: ParagraphCtrl', function() {
});
});
it('should return "TEXT" by default when getResultType() is called with no parameter', function() {
expect(scope.getResultType()).toEqual('TEXT');
});
it('should have this array of values for "colWidthOption"', function() {
expect(scope.colWidthOption).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
});