commit
1c19f7fc77
|
|
@ -28,7 +28,7 @@ migrate = Migrate(app, db, directory=APP_DIR + "/migrations")
|
|||
class MyIndexView(IndexView):
|
||||
@expose('/')
|
||||
def index(self):
|
||||
return redirect('/dashed/featured')
|
||||
return redirect('/dashed/welcome')
|
||||
|
||||
appbuilder = AppBuilder(
|
||||
app, db.session,
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
}],
|
||||
"max-depth": [2, 5],
|
||||
"max-len": [0, 80, 4],
|
||||
"max-nested-callbacks": [1, 2],
|
||||
"max-nested-callbacks": [1, 3],
|
||||
"max-params": [1, 4],
|
||||
"new-parens": [2],
|
||||
"newline-after-var": [0],
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
var $ = window.$ = require('jquery');
|
||||
var jQuery = window.jQuery = $;
|
||||
require('./modules/dashed.js');
|
||||
|
||||
require('bootstrap');
|
||||
require('datatables');
|
||||
require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#dataset-table').DataTable({
|
||||
bPaginate: false,
|
||||
order: [
|
||||
[1, "asc"]
|
||||
]
|
||||
});
|
||||
$('#dataset-table_info').remove();
|
||||
//$('input[type=search]').addClass('form-control'); # TODO get search box to look nice
|
||||
$('#dataset-table').show();
|
||||
});
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
var $ = window.$ = require('jquery');
|
||||
var jQuery = window.jQuery = $;
|
||||
|
||||
require('../stylesheets/dashed.css');
|
||||
require('../stylesheets/welcome.css');
|
||||
require('bootstrap');
|
||||
require('datatables');
|
||||
require('../node_modules/cal-heatmap/cal-heatmap.css');
|
||||
|
||||
var CalHeatMap = require('cal-heatmap');
|
||||
|
||||
function modelViewTable(selector, modelEndpoint, ordering) {
|
||||
// Builds a dataTable from a flask appbuilder api endpoint
|
||||
$.getJSON(modelEndpoint + '/api/read', function (data) {
|
||||
var tableData = jQuery.map(data.result, function (el, i) {
|
||||
var row = $.map(data.list_columns, function (col, i) {
|
||||
return el[col];
|
||||
});
|
||||
return [row];
|
||||
});
|
||||
var cols = jQuery.map(data.list_columns, function (col, i) {
|
||||
return { sTitle: data.label_columns[col] };
|
||||
});
|
||||
$(selector).DataTable({
|
||||
aaData: tableData,
|
||||
aoColumns: cols,
|
||||
bPaginate: false,
|
||||
order: ordering,
|
||||
searching: false
|
||||
});
|
||||
$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
var cal = new CalHeatMap();
|
||||
cal.init({
|
||||
start: new Date().setFullYear(new Date().getFullYear() - 1),
|
||||
range: 13,
|
||||
data: '/dashed/activity_per_day',
|
||||
domain: "month",
|
||||
subDomain: "day",
|
||||
itemName: "action",
|
||||
tooltip: true
|
||||
});
|
||||
modelViewTable('#dash_table', '/dashboardmodelviewasync');
|
||||
modelViewTable('#slice_table', '/sliceasync');
|
||||
});
|
||||
|
|
@ -43,6 +43,7 @@
|
|||
"bootstrap-datepicker": "^1.6.0",
|
||||
"bootstrap-toggle": "^2.2.1",
|
||||
"brace": "^0.7.0",
|
||||
"cal-heatmap": "3.5.4",
|
||||
"css-loader": "^0.23.1",
|
||||
"d3": "^3.5.14",
|
||||
"d3-cloud": "^1.2.1",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ body {
|
|||
font-size: 100%;
|
||||
}
|
||||
|
||||
.no-wrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
input.form-control {
|
||||
background-color: white;
|
||||
}
|
||||
|
|
@ -68,6 +72,10 @@ form div {
|
|||
}
|
||||
.navbar-brand a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
.navbar-brand a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header span {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
.welcome .widget{
|
||||
border-radius: 0;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 2px 1px 5px -2px #aaa;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.welcome .widget .header {
|
||||
background-color: #f1f1f1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcome .widget>div {
|
||||
padding: 3px;
|
||||
overflow: auto;
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.table i {
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ var config = {
|
|||
'css-theme': APP_DIR + '/javascripts/css-theme.js',
|
||||
dashboard: APP_DIR + '/javascripts/dashboard.js',
|
||||
explore: APP_DIR + '/javascripts/explore.js',
|
||||
featured: APP_DIR + '/javascripts/featured.js',
|
||||
welcome: APP_DIR + '/javascripts/welcome.js',
|
||||
sql: APP_DIR + '/javascripts/sql.js',
|
||||
standalone: APP_DIR + '/javascripts/standalone.js'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
"""log dt
|
||||
|
||||
Revision ID: 1d2ddd543133
|
||||
Revises: d2424a248d63
|
||||
Create Date: 2016-03-25 14:35:44.642576
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '1d2ddd543133'
|
||||
down_revision = 'd2424a248d63'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('logs', sa.Column('dt', sa.Date(), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('logs', 'dt')
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from copy import deepcopy, copy
|
||||
from collections import namedtuple
|
||||
from datetime import timedelta, datetime
|
||||
from datetime import timedelta, datetime, date
|
||||
import functools
|
||||
import json
|
||||
import logging
|
||||
|
|
@ -15,12 +15,13 @@ from flask import flash, request, g
|
|||
from flask.ext.appbuilder import Model
|
||||
from flask.ext.appbuilder.models.mixins import AuditMixin
|
||||
import pandas as pd
|
||||
import humanize
|
||||
from pydruid import client
|
||||
from pydruid.utils.filters import Dimension, Filter
|
||||
|
||||
import sqlalchemy as sqla
|
||||
from sqlalchemy import (
|
||||
Column, Integer, String, ForeignKey, Text, Boolean, DateTime,
|
||||
Column, Integer, String, ForeignKey, Text, Boolean, DateTime, Date,
|
||||
Table, create_engine, MetaData, desc, select, and_, func)
|
||||
from sqlalchemy.engine import reflection
|
||||
from sqlalchemy.orm import relationship
|
||||
|
|
@ -68,6 +69,22 @@ class AuditMixinNullable(AuditMixin):
|
|||
def changed_by_(self):
|
||||
return '{}'.format(self.changed_by or '')
|
||||
|
||||
@property
|
||||
def modified(self):
|
||||
s = humanize.naturaltime(datetime.now() - self.changed_on)
|
||||
return '<span class="no-wrap">{}</nobr>'.format(s)
|
||||
|
||||
@property
|
||||
def icons(self):
|
||||
return """
|
||||
<a
|
||||
href="{self.datasource_edit_url}"
|
||||
data-toggle="tooltip"
|
||||
title="{self.datasource}">
|
||||
<i class="fa fa-database"></i>
|
||||
</a>
|
||||
""".format(**locals())
|
||||
|
||||
|
||||
class Url(Model, AuditMixinNullable):
|
||||
|
||||
|
|
@ -123,6 +140,13 @@ class Slice(Model, AuditMixinNullable):
|
|||
elif self.druid_datasource:
|
||||
return self.druid_datasource.link
|
||||
|
||||
@property
|
||||
def datasource_edit_url(self):
|
||||
if self.table:
|
||||
return self.table.url
|
||||
elif self.druid_datasource:
|
||||
return self.druid_datasource.url
|
||||
|
||||
@property
|
||||
@utils.memoized
|
||||
def viz(self):
|
||||
|
|
@ -1057,6 +1081,7 @@ class Log(Model):
|
|||
json = Column(Text)
|
||||
user = relationship('User', backref='logs', foreign_keys=[user_id])
|
||||
dttm = Column(DateTime, default=func.now())
|
||||
dt = Column(Date, default=date.today())
|
||||
|
||||
@classmethod
|
||||
def log_this(cls, f):
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
{% extends "dashed/basic.html" %}
|
||||
|
||||
{% block head_js %}
|
||||
{{ super() }}
|
||||
<script src="/static/assets/javascripts/dist/featured.entry.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h3><i class='fa fa-star'></i> Featured Datasets </h3>
|
||||
</div>
|
||||
<hr/>
|
||||
<table class="table table-hover dataTable table-bordered" id="dataset-table" style="display:None">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Table</th>
|
||||
<th>Database</th>
|
||||
<th>Owner</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for dataset in featured_datasets %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="intable-longtext">
|
||||
<h4>{{ dataset.table_name }}</h4>
|
||||
<p>{{ utils.markdown(dataset.description) | safe }}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="small_table">{{ dataset.database }}</td>
|
||||
<td class="small_table">{{ dataset.owner or '' }}</td>
|
||||
<td class="small_table"><a class="btn btn-default" href="{{ dataset.default_endpoint }}"><i class='fa fa-line-chart'/></a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<hr/>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
{% extends "dashed/basic.html" %}
|
||||
|
||||
{% block head_js %}
|
||||
{{ super() }}
|
||||
<script src="/static/assets/javascripts/dist/welcome.entry.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}Welcome!{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container welcome">
|
||||
<div class="header">
|
||||
<h3><i class='fa fa-star'></i> Welcome!</h3>
|
||||
</div>
|
||||
<hr/>
|
||||
<div id="cal-heatmap"></div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="widget">
|
||||
<div class="header"><h4><i class="fa fa-dashboard"></i> Dashboards</h4></div>
|
||||
<div>
|
||||
<table id="dash_table" class="table"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="widget">
|
||||
<div class="header"><h4><i class="fa fa-bar-chart"></i> Slices</h4></div>
|
||||
<div>
|
||||
<table id="slice_table" class="table"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -183,7 +183,7 @@ def init(dashed):
|
|||
for perm in perms:
|
||||
if perm.permission.name == 'datasource_access':
|
||||
continue
|
||||
if perm.view_menu.name not in (
|
||||
if perm.view_menu and perm.view_menu.name not in (
|
||||
'UserDBModelView', 'RoleModelView', 'ResetPasswordView',
|
||||
'Security'):
|
||||
sm.add_permission_role(alpha, perm)
|
||||
|
|
@ -191,7 +191,7 @@ def init(dashed):
|
|||
gamma = sm.add_role("Gamma")
|
||||
for perm in perms:
|
||||
if(
|
||||
perm.view_menu.name not in (
|
||||
perm.view_menu and perm.view_menu.name not in (
|
||||
'ResetPasswordView',
|
||||
'RoleModelView',
|
||||
'UserDBModelView',
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from datetime import datetime
|
|||
import json
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from flask import (
|
||||
|
|
@ -67,8 +68,8 @@ class TableColumnInlineView(CompactCRUDMixin, DashedModelView): # noqa
|
|||
appbuilder.add_view_no_menu(TableColumnInlineView)
|
||||
|
||||
appbuilder.add_link(
|
||||
"Featured Datasets",
|
||||
href='/dashed/featured',
|
||||
"Welcome!",
|
||||
href='/dashed/welcome',
|
||||
category='Sources',
|
||||
category_icon='fa-table',
|
||||
icon="fa-star")
|
||||
|
|
@ -220,6 +221,10 @@ if config['DRUID_IS_ACTIVE']:
|
|||
class SliceModelView(DashedModelView, DeleteMixin): # noqa
|
||||
datamodel = SQLAInterface(models.Slice)
|
||||
can_add = False
|
||||
label_columns = {
|
||||
'created_by_': 'Creator',
|
||||
'datasource_link': 'Datasource',
|
||||
}
|
||||
list_columns = [
|
||||
'slice_link', 'viz_type',
|
||||
'datasource_link', 'created_by_', 'changed_on']
|
||||
|
|
@ -236,7 +241,6 @@ class SliceModelView(DashedModelView, DeleteMixin): # noqa
|
|||
"markdown</a>"),
|
||||
}
|
||||
|
||||
|
||||
appbuilder.add_view(
|
||||
SliceModelView,
|
||||
"Slices",
|
||||
|
|
@ -245,8 +249,19 @@ appbuilder.add_view(
|
|||
category_icon='',)
|
||||
|
||||
|
||||
class SliceAsync(SliceModelView): # noqa
|
||||
list_columns = [
|
||||
'slice_link', 'viz_type',
|
||||
'created_by_', 'modified', 'icons']
|
||||
|
||||
appbuilder.add_view_no_menu(SliceAsync)
|
||||
|
||||
|
||||
class DashboardModelView(DashedModelView, DeleteMixin): # noqa
|
||||
datamodel = SQLAInterface(models.Dashboard)
|
||||
label_columns = {
|
||||
'created_by_': 'Creator',
|
||||
}
|
||||
list_columns = ['dashboard_link', 'created_by_', 'changed_on']
|
||||
order_columns = utils.list_minus(list_columns, ['created_by_'])
|
||||
edit_columns = [
|
||||
|
|
@ -285,6 +300,12 @@ appbuilder.add_view(
|
|||
category_icon='',)
|
||||
|
||||
|
||||
class DashboardModelViewAsync(DashboardModelView): # noqa
|
||||
list_columns = ['dashboard_link', 'created_by_', 'modified']
|
||||
|
||||
appbuilder.add_view_no_menu(DashboardModelViewAsync)
|
||||
|
||||
|
||||
class LogModelView(DashedModelView):
|
||||
datamodel = SQLAInterface(models.Log)
|
||||
list_columns = ('user', 'action', 'dttm')
|
||||
|
|
@ -525,6 +546,22 @@ class Dashed(BaseView):
|
|||
db.session.commit()
|
||||
return Response("OK", mimetype="application/json")
|
||||
|
||||
@has_access
|
||||
@expose("/activity_per_day")
|
||||
def activity_per_day(self):
|
||||
"""endpoint to power the calendar heatmap on the welcome page"""
|
||||
Log = models.Log # noqa
|
||||
qry = (
|
||||
db.session
|
||||
.query(
|
||||
Log.dt,
|
||||
sqla.func.count())
|
||||
.group_by(Log.dt)
|
||||
.all()
|
||||
)
|
||||
payload = {str(time.mktime(dt.timetuple())): ccount for dt, ccount in qry if dt}
|
||||
return Response(json.dumps(payload), mimetype="application/json")
|
||||
|
||||
@has_access
|
||||
@expose("/save_dash/<dashboard_id>/", methods=['GET', 'POST'])
|
||||
def save_dash(self, dashboard_id):
|
||||
|
|
@ -760,25 +797,11 @@ class Dashed(BaseView):
|
|||
art=ascii_art.error), 500
|
||||
|
||||
@has_access
|
||||
@expose("/featured", methods=['GET'])
|
||||
def featured(self):
|
||||
"""views that shows the Featured Datasets"""
|
||||
session = db.session()
|
||||
datasets_sqla = (
|
||||
session.query(models.SqlaTable)
|
||||
.filter_by(is_featured=True)
|
||||
.all()
|
||||
)
|
||||
datasets_druid = (
|
||||
session.query(models.DruidDatasource)
|
||||
.filter_by(is_featured=True)
|
||||
.all()
|
||||
)
|
||||
featured_datasets = datasets_sqla + datasets_druid
|
||||
return self.render_template(
|
||||
'dashed/featured.html',
|
||||
featured_datasets=featured_datasets,
|
||||
utils=utils)
|
||||
@expose("/welcome")
|
||||
def welcome(self):
|
||||
"""Personalized welcome page"""
|
||||
return self.render_template('dashed/welcome.html', utils=utils)
|
||||
|
||||
|
||||
appbuilder.add_view_no_menu(Dashed)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue