Initial commit

This commit is contained in:
zhongjin
2020-06-15 10:58:47 +08:00
commit 4f1dfe7564
8590 changed files with 1516878 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
var $ = require('jquery');
var cdb = require('internal-carto.js');
var CoreView = require('backbone/core-view');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view.js');
var NotificationErrorMessageHandler = require('builder/editor/layers/notification-error-message-handler');
module.exports = CoreView.extend({
initialize: function (opts) {
if (!opts.analysisNode) throw new Error('analysisNode is required');
if (!opts.element) throw new Error('element is required');
if (!opts.triggerSelector) throw new Error('triggerSelector is required');
this.analysisNode = opts.analysisNode;
this.$el = opts.element;
this.selector = opts.triggerSelector;
this.$el.on('mouseover', this.selector, this._showTooltip.bind(this));
this.$el.on('mouseout', this.selector, this._destroyTooltip.bind(this));
this.$el.on('mouseleave', this.selector, this._destroyTooltip.bind(this));
},
_showTooltip: function (e) {
var status = this.analysisNode.get('status');
if (status === 'failed') {
var message = NotificationErrorMessageHandler.extractErrorFromAnalysisNode(this.analysisNode);
this.tooltip = this._createTooltip({
$el: $(e.target),
msg: message.message
});
this.tooltip.showTipsy();
}
},
_createTooltip: function (opts) {
return new TipsyTooltipView({
el: opts.$el,
title: function () {
return opts.msg;
}
});
},
_destroyTooltip: function () {
if (this.tooltip) {
this.tooltip.hideTipsy();
this.tooltip.destroyTipsy();
delete this.tooltip;
}
},
clean: function () {
this.$el.off('mouseover', this.selector);
this.$el.off('mouseout', this.selector);
this.$el.off('mouseleave', this.selector);
this._destroyTooltip();
cdb.core.View.prototype.clean.call(this);
}
});

View File

@@ -0,0 +1,130 @@
var _ = require('underscore');
var Notifier = require('builder/components/notifier/notifier');
var NotificationErrorMessageHandler = require('builder/editor/layers/notification-error-message-handler');
var DEFAULT_DELAY = Notifier.DEFAULT_DELAY;
var STATUS_READY = 'ready';
var STATUS_PENDING = 'pending';
var STATUS_RUNNING = 'running';
var STATUS_WAITING = 'waiting';
var STATUS_FAILED = 'failed';
var AnalysisNotifications = {
track: function (analysisNode, layerDefModel) {
analysisNode.on('change:status', this._addStatusChangedNotification, this);
analysisNode.on('change:error', this._onErrorChanged, this);
analysisNode.once('destroy', this._addRemovedNotification, this);
this._layerDefModel = layerDefModel;
if (analysisNode.get('status') && analysisNode.get('status') !== STATUS_READY) {
this._addStatusChangedNotification(analysisNode);
}
},
_addRemovedNotification: function (analysisNode) {
if (analysisNode.get('avoidNotification') === true) {
return;
}
var nodeId = analysisNode.get('id');
var notificationAttrs = {
status: 'success',
info: _t('notifications.analysis.removed', {
nodeId: nodeId.toUpperCase()
}),
closable: true,
delay: DEFAULT_DELAY
};
this._addOrUpdateNotification(analysisNode.cid, notificationAttrs);
},
_addStatusChangedNotification: function (analysisNode) {
var status = analysisNode.get('status');
if (status === STATUS_WAITING || status === STATUS_PENDING) {
this._addAnalysisWaitingNotification(analysisNode);
}
if (status === STATUS_RUNNING) {
this._addAnalysisRunningNotification(analysisNode);
}
if (status === STATUS_READY) {
this._addAnalysisReadyNotification(analysisNode);
}
if (status === STATUS_FAILED) {
this._addAnalysisFailedNotification(analysisNode);
}
},
_addAnalysisWaitingNotification: function (analysisNode) {
var notificationAttrs = {
status: 'loading',
info: _t('notifications.analysis.waiting', {
nodeId: analysisNode.get('id').toUpperCase()
})
};
this._addOrUpdateNotification(analysisNode.cid, notificationAttrs);
},
_addAnalysisRunningNotification: function (analysisNode) {
var notificationAttrs = {
status: 'loading',
info: _t('notifications.analysis.running', {
nodeId: analysisNode.get('id').toUpperCase()
})
};
this._addOrUpdateNotification(analysisNode.cid, notificationAttrs);
},
_addAnalysisReadyNotification: function (analysisNode) {
var notificationAttrs = {
status: 'success',
info: _t('notifications.analysis.completed', {
nodeId: analysisNode.get('id').toUpperCase()
}),
closable: true,
delay: DEFAULT_DELAY
};
this._addOrUpdateNotification(analysisNode.cid, notificationAttrs);
},
_addAnalysisFailedNotification: function (analysisNode) {
var message = NotificationErrorMessageHandler.extractErrorFromAnalysisNode(analysisNode, this._layerDefModel);
var notificationAttrs = {
status: message.type,
info: message.message,
closable: true,
autoclosable: false
};
this._addOrUpdateNotification(analysisNode.cid, notificationAttrs);
},
_addOrUpdateNotification: function (notificationId, notificationAttrs) {
var notification = this._getNotification(notificationId);
if (notification) {
notification.set(notificationAttrs);
} else {
this._addNotification(notificationId, notificationAttrs);
}
},
_addNotification: function (notificationId, notificationAttrs) {
notificationAttrs = _.extend({
id: notificationId
}, notificationAttrs);
Notifier.addNotification(notificationAttrs);
},
_getNotification: function (notificationId) {
return Notifier.getNotification(notificationId);
},
_onErrorChanged: function (analysisNode, error) {
if (error) {
this._addAnalysisFailedNotification(analysisNode);
}
}
};
module.exports = AnalysisNotifications;

View File

@@ -0,0 +1,82 @@
var CoreView = require('backbone/core-view');
var SourceLayerAnalysisView = require('./source-layer-analysis-view');
var DefaultLayerAnalysisView = require('./default-layer-analysis-view');
var template = require('./composite-layer-analysis-view.tpl');
/**
* View for an analysis node which have two source nodes as input.
* The primary source node is rendered separately, this view renders the own node + the secondary one.
* _____________ ___________________
* | own node | ------ | secondary node |
* |____________| |__________________|
* |
* ________________
* | primary node |
* |________________|
*
* this.model is expected to be a analysis-definition-node-nodel
*/
module.exports = CoreView.extend({
tagName: 'li',
className: 'Editor-ListAnalysis-item',
initialize: function (opts) {
if (!opts.analysisDefinitionNodesCollection) throw new Error('analysisDefinitionNodesCollection is required');
if (!opts.layerDefinitionModel) throw new Error('layerDefinitionModel is required');
this._analysisDefinitionNodesCollection = opts.analysisDefinitionNodesCollection;
this._layerDefinitionModel = opts.layerDefinitionModel;
},
render: function () {
this.clearSubViews();
this.$el.html(template());
this._renderOwnNode();
this._renderSecondaryNode();
return this;
},
_renderOwnNode: function () {
var view = new DefaultLayerAnalysisView({
tagName: 'div',
model: this.model,
analysisNode: this._analysisDefinitionNodesCollection.get(this.model.id),
layerDefinitionModel: this._layerDefinitionModel
});
this.addView(view);
this.$('.js-primary-source').append(view.render().el);
},
_renderSecondaryNode: function () {
var nodeDefModel = this.model.getSecondarySource();
var layerDefModel = this._layerDefinitionModel.collection.findOwnerOfAnalysisNode(nodeDefModel);
if (nodeDefModel) {
var view = nodeDefModel.get('type') === 'source'
? new SourceLayerAnalysisView({
model: nodeDefModel,
analysisNode: this._analysisDefinitionNodesCollection.get(nodeDefModel.id),
layerDefinitionModel: this._layerDefinitionModel,
showId: !!(layerDefModel && this._isOwnedByOtherLayer(layerDefModel))
})
: new DefaultLayerAnalysisView({
tagName: 'div',
model: nodeDefModel,
analysisNode: this._analysisDefinitionNodesCollection.get(nodeDefModel.id),
layerDefinitionModel: layerDefModel
});
this.addView(view);
this.$('.js-secondary-source').append(view.render().el);
}
},
_isOwnedByOtherLayer: function (layerDefModel) {
return layerDefModel !== this._layerDefinitionModel;
}
});

View File

@@ -0,0 +1,4 @@
<ul class="Editor-ListAnalysis-inner">
<li class="Editor-ListAnalysis-innerItem js-primary-source"></li>
<li class="Editor-ListAnalysis-innerItem js-secondary-source"></li>
</ul>

View File

@@ -0,0 +1,102 @@
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var nodeIds = require('builder/value-objects/analysis-node-ids');
var layerColors = require('builder/data/layer-colors');
var template = require('./default-layer-analysis-view.tpl');
var Analyses = require('builder/data/analyses');
var AnalysisTooltip = require('./analyses-tooltip-error');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
/**
* View for an analysis node with a single input
*
* this.model is expected to be a analysis-definition-node-nodel
*/
module.exports = CoreView.extend({
tagName: 'li',
className: 'Editor-ListAnalysis-item Editor-ListAnalysis-layer CDB-Text is-semibold CDB-Size-small js-analysis-node',
events: {
'mouseenter': '_onMouseEnter',
'mouseleave': '_onMouseLeave'
},
initialize: function (opts) {
if (!opts.analysisNode) throw new Error('analysisNode is required');
this._analysisNode = opts.analysisNode;
this._analysisNode.on('change:status', this.render, this);
this.add_related_model(this._analysisNode);
this._analysisTooltip = new AnalysisTooltip({
analysisNode: this._analysisNode,
element: this.$el,
triggerSelector: '.Editor-ListAnalysis-itemError'
});
this.addView(this._analysisTooltip);
this._stateModel = new Backbone.Model({
highlighted: false
});
this._initBinds();
},
_initBinds: function () {
this._stateModel.on('change:highlighted', this._toggleHover, this);
this.add_related_model(this._stateModel);
},
render: function () {
var status = this._analysisNode.get('status');
this.$el.html(template({
id: this.model.id,
bgColor: this._bgColor(),
isDone: status === 'ready' || status === 'failed',
title: Analyses.title(this.model),
hasError: status === 'failed'
}));
this.$el.toggleClass('has-error', status === 'failed');
this.el.dataset.analysisNodeId = this.model.id;
this._initViews();
return this;
},
_initViews: function () {
var sourceTooltip = new TipsyTooltipView({
el: this.$el,
gravity: 'w',
title: function () {
return _t('edit-analysis');
}
});
this.addView(sourceTooltip);
},
_bgColor: function () {
var letter = nodeIds.letter(this._analysisNode.id);
return layerColors.getColorForLetter(letter);
},
_onMouseEnter: function () {
this._stateModel.set('highlighted', true);
},
_onMouseLeave: function () {
this._stateModel.set('highlighted', false);
},
_toggleHover: function () {
var $layer = this.$el.closest('.js-layer');
var $title = $layer.find('.js-Editor-ListLayer-titleText');
var highlighted = this._stateModel.get('highlighted');
$layer.toggleClass('is-hover', highlighted);
$title.toggleClass('is-hover', highlighted);
}
});

View File

@@ -0,0 +1,19 @@
<div class="Editor-ListAnalysis-itemInfo u-rSpace--m CDB-Text is-semibold CDB-Size-small u-upperCase" style="background: <%- bgColor %>; color: #fff">
<% if (isDone) { %>
<span class="CDB-Text u-rSpace">
<%- id %>
</span>
<i class="CDB-IconFont CDB-IconFont-ray CDB-Size-medium"></i>
<% } else { %>
<div class="CDB-LoaderIcon">
<svg class="CDB-LoaderIcon-spinner" viewBox="0 0 50 50">
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"></circle>
</svg>
</div>
<% } %>
</div>
<p class="Editor-ListAnalysis-title CDB-Text CDB-Size-small u-secondaryTextColor u-ellipsis" title="<%- title %>"><%- title %></p>
<% if (hasError) { %>
<div class="Editor-ListAnalysis-itemError"></div>
<% } %>

View File

@@ -0,0 +1,79 @@
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var analyses = require('builder/data/analyses');
var template = require('./ref-layer-analysis-view.tpl');
/**
* View for an analysis node that belongs to another layer
*
* this.model is expected to be a analysis-definition-node-nodel
*/
module.exports = CoreView.extend({
tagName: 'li',
className: 'Editor-ListAnalysis-item Editor-ListAnalysis-layer CDB-Text is-semibold CDB-Size-small js-analysis-node',
events: {
'mouseenter': '_onMouseEnter',
'mouseleave': '_onMouseLeave'
},
initialize: function (opts) {
if (!opts.layerDefinitionModel) throw new Error('layerDefinitionModel is required');
if (!opts.analysisNode) throw new Error('analysisNode is required');
this._layerDefinitionModel = opts.layerDefinitionModel;
this._analysisNode = opts.analysisNode;
this._stateModel = new Backbone.Model({
highlighted: false
});
this._initBinds();
},
_initBinds: function () {
this.listenTo(this._analysisNode, 'change:status', this.render);
this.add_related_model(this._analysisNode);
this.listenTo(this._layerDefinitionModel, 'change', this.render);
this.add_related_model(this._layerDefinitionModel);
this.listenTo(this._stateModel, 'change:highlighted', this._toggleHover);
this.add_related_model(this._stateModel);
},
render: function () {
var status = this._analysisNode.get('status');
this.$el.html(template({
id: this.model.id,
layerName: this._layerDefinitionModel.getName(),
bgColor: this.model.getColor(),
isDone: status === 'ready' || status === 'failed',
title: analyses.title(this.model)
}));
this.$el.toggleClass('has-error', status === 'failed');
this.el.dataset.analysisNodeId = this.model.id;
return this;
},
_onMouseEnter: function () {
this._stateModel.set('highlighted', true);
},
_onMouseLeave: function () {
this._stateModel.set('highlighted', false);
},
_toggleHover: function () {
var $layer = this.$el.closest('.js-layer');
var $title = $layer.find('.js-Editor-ListLayer-titleText');
var highlighted = this._stateModel.get('highlighted');
$layer.toggleClass('is-hover', highlighted);
$title.toggleClass('is-hover', highlighted);
}
});

View File

@@ -0,0 +1,17 @@
<div class="Editor-ListAnalysis-itemInfo u-rSpace--m CDB-Text is-semibold CDB-Size-small u-upperCase" style="background: <%- bgColor %>; color: #fff">
<% if (isDone) { %>
<span class="CDB-Text u-rSpace">
<%- id %>
</span>
<i class="CDB-IconFont CDB-IconFont-ray CDB-Size-medium"></i>
<% } else { %>
<div class="CDB-LoaderIcon">
<svg class="CDB-LoaderIcon-spinner" viewBox="0 0 50 50">
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"></circle>
</svg>
</div>
<% } %>
</div>
<p class="Editor-ListAnalysis-title CDB-Text CDB-Size-small u-secondaryTextColor u-ellipsis u-flex" title="<%- title %>">
<%- title %> <span class="u-altTextColor u-lSpace u-ellipsis"><%- layerName %></span>
</p>

View File

@@ -0,0 +1,137 @@
var $ = require('jquery');
var _ = require('underscore');
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var template = require('./source-layer-analysis-view.tpl');
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
var moment = require('moment');
/**
* View for a analysis source (i.e. SQL query).
*
* this.model is expected to be a analysis-definition-node-model and belong to the given layer-definition-model
*/
module.exports = CoreView.extend({
tagName: 'li',
className: 'Editor-ListAnalysis-item Editor-ListAnalysis-layer js-base is-base u-flex u-justifySpace',
events: {
'mouseenter': '_onMouseEnter',
'mouseleave': '_onMouseLeave'
},
initialize: function (opts) {
if (!opts.layerDefinitionModel) throw new Error('layerDefinitionModel is required');
if (!opts.analysisNode) throw new Error('analysisNode is required');
this._layerDefinitionModel = opts.layerDefinitionModel;
this._analysisNode = opts.analysisNode;
this._tableNodeModel = this._analysisNode.getTableModel();
this._stateModel = new Backbone.Model({
highlighted: false
});
this._initBinds();
},
options: {
showId: true
},
render: function () {
this.clearSubViews();
var isSync = this._tableNodeModel && this._tableNodeModel.isSync();
var syncData = {};
if (isSync) {
var syncModel = this._tableNodeModel.getSyncModel();
var runAt = syncModel.get('run_at');
syncData = {
ranAt: moment(syncModel.get('ran_at') || new Date()).fromNow(),
runAt: moment(runAt).fromNow(),
state: syncModel.get('state'),
errorCode: syncModel.get('error_code'),
errorMessage: syncModel.get('error_message')
};
// Due to the time we need to polling, we have to display to the user
// that the sync will be in a moment
if (!runAt || (new Date(runAt) <= new Date())) {
syncData.runAt = _t('dataset.sync.in-a-moment');
}
}
var isCustomQueryApplied = this._analysisNode.isCustomQueryApplied();
this.$el.html(template(_.extend({
id: this.options.showId ? this.model.id : '',
tableName: this.model.get('table_name'),
customQueryApplied: isCustomQueryApplied,
isSync: isSync,
bgColor: this._analysisNode.getColor()
}, syncData)));
var sourceTooltip = new TipsyTooltipView({
el: this.$el,
gravity: 'w',
title: function () {
return _t('data-source');
}
});
this.addView(sourceTooltip);
if (isSync) {
var syncTooltipTitle = (syncData.errorCode || syncData.errorMessage) ? _t('dataset.sync.error-code', { errorCode: syncData.errorCode }) + ':' + syncData.errorMessage : $(this).data('tooltip');
var syncTooltip = new TipsyTooltipView({
el: this.$('.js-sync'),
gravity: 's',
offset: 0,
title: syncTooltipTitle
});
this.addView(syncTooltip);
}
if (isCustomQueryApplied) {
var sqlTooltip = new TipsyTooltipView({
el: this.$('.js-sql'),
gravity: 'w',
title: function () {
return _t('sql-applied');
},
mouseEnterAction: function () {
sourceTooltip.hideTipsy();
},
mouseLeaveAction: function () {
sourceTooltip.showTipsy();
}
});
this.addView(sqlTooltip);
}
return this;
},
_initBinds: function () {
this.listenTo(this._stateModel, 'change:highlighted', this._toggleHover);
this.listenTo(this._tableNodeModel, 'change:synchronization', this.render);
},
_onMouseEnter: function () {
this._stateModel.set('highlighted', true);
},
_onMouseLeave: function () {
this._stateModel.set('highlighted', false);
},
_toggleHover: function () {
var $layer = this.$el.closest('.js-layer');
var $title = $layer.find('.js-Editor-ListLayer-titleText');
var highlighted = this._stateModel.get('highlighted');
$layer.toggleClass('is-hover', highlighted);
$title.toggleClass('is-hover', highlighted);
}
});

View File

@@ -0,0 +1,21 @@
<p class="Editor-ListAnalysis-itemInfoTitle CDB-Text CDB-Size-small u-ellipsis u-flex" title="<%- tableName %>">
<span class="CDB-Text is-semibold CDB-Size-small u-rSpace u-upperCase" style="color: <%- bgColor %>">
<%- id %>
</span>
Source
<span class="Editor-ListAnalysis-title u-altTextColor u-lSpace u-ellipsis">
<%- tableName %>
</span>
</p>
<% if (isSync) { %>
<span class="Editor-ListAnalysis-itemInfoIcon">
<div class="u-flex u-alignCenter CDB-Text CDB-Size-small u-altTextColor SyncInfo-message--<%- state %> js-sync" data-tooltip="<% if (errorCode || errorMessage) { %><%- _t('dataset.sync.sync-failed') %><% } else { %><%- ranAt %><% } %>">
<i class="CDB-IconFont CDB-IconFont-wifi"></i>
</div>
</span>
<% } %>
<% if (customQueryApplied) { %>
<span class="Editor-ListAnalysis-itemInfoIcon Tag Tag--outline Tag-outline--dark CDB-Text CDB-Size-small js-sql">
SQL
</span>
<% } %>