Same layout for each dashboard + barchart animations
This commit is contained in:
parent
f04db2401f
commit
790156f28d
|
@ -115,88 +115,49 @@ var Org = React.createClass({
|
|||
}
|
||||
});
|
||||
|
||||
var OrgStats = React.createClass({
|
||||
var Dashboard = React.createClass({
|
||||
mixins: [Router.State],
|
||||
|
||||
render: function(){
|
||||
var org = Storage.get('org', this.getParams().org);
|
||||
return (
|
||||
<section className="content">
|
||||
<InfoBlock key={'info-block-org-'+ this.getParams().org}
|
||||
image={org.avatar_url}
|
||||
title={org.login}
|
||||
text={org.descr} />
|
||||
<BarChart key={'bar-chart-'+ this.getParams().org}
|
||||
api="/api/stat/orgs/top"
|
||||
params={this.getParams()}
|
||||
items={["repo", "team", "user"]} />
|
||||
<StackedAreaChart key={'sa-chart-team-'+ this.getParams().team}
|
||||
api="/api/stat/orgs/activity"
|
||||
params={this.getParams()}
|
||||
items={["repo", "team", "user"]} />
|
||||
</section>
|
||||
);
|
||||
var p = this.getParams(),
|
||||
infoImage, infoTitle, infoText,
|
||||
bcApi, bcItems,
|
||||
sacApi, sacItems;
|
||||
|
||||
if (p.team) {
|
||||
infoTitle = p.team;
|
||||
bcApi = '/api/stat/teams/top';
|
||||
bcItems = ['repo', 'user'],
|
||||
sacApi = '/api/stat/teams/activity';
|
||||
sacItems = ['user', 'repo'];
|
||||
} else if (p.user) {
|
||||
infoTitle = p.user;
|
||||
bcApi = '/api/stat/users/top';
|
||||
bcItems = ['repo'],
|
||||
sacApi = '/api/stat/users/activity';
|
||||
sacItems = ['repo'];
|
||||
} else if (p.repo) {
|
||||
infoTitle = p.repo;
|
||||
bcApi = '/api/stat/repos/top';
|
||||
bcItems = ['user', 'team'],
|
||||
sacApi = '/api/stat/repos/activity';
|
||||
sacItems = ['user', 'team'];
|
||||
} else {
|
||||
var info = Storage.get('org', p.org);
|
||||
infoImage = info.avatar_url;
|
||||
infoTitle = info.login;
|
||||
infoText = info.descr;
|
||||
bcApi = '/api/stat/orgs/top';
|
||||
bcItems = ['repo', 'team', 'user'],
|
||||
sacApi = '/api/stat/orgs/activity';
|
||||
sacItems = ['team', 'user', 'repo'];
|
||||
}
|
||||
});
|
||||
|
||||
var TeamStats = React.createClass({
|
||||
mixins: [Router.State],
|
||||
|
||||
render: function(){
|
||||
return (
|
||||
<section className="content">
|
||||
<InfoBlock key={"info-block-team-"+ this.getParams().team}
|
||||
image="https://media.licdn.com/mpr/mpr/p/8/005/058/14b/0088c48.jpg"
|
||||
title={this.getParams().team}
|
||||
text={"The most awesome team in "+ this.getParams().org} />
|
||||
<BarChart key={'bar-chart-team-'+ this.getParams().team}
|
||||
api="/api/stat/teams/top"
|
||||
params={this.getParams()}
|
||||
items={["repo", "user"]} />
|
||||
<StackedAreaChart key={'sa-chart-team-'+ this.getParams().team}
|
||||
api="/api/stat/teams/activity"
|
||||
params={this.getParams()}
|
||||
items={["repo", "user"]} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var UserStats = React.createClass({
|
||||
mixins: [Router.State],
|
||||
render: function(){
|
||||
return (
|
||||
<section className="content">
|
||||
<InfoBlock key={'info-block-user-'+ this.getParams().user}
|
||||
title={this.getParams().user} />
|
||||
<BarChart key={'bar-chart-user-'+ this.getParams().user}
|
||||
api="/api/stat/users/top"
|
||||
params={this.getParams()}
|
||||
items={["repo"]} />
|
||||
<StackedAreaChart key={'sa-chart-team-'+ this.getParams().team}
|
||||
api="/api/stat/users/activity"
|
||||
params={this.getParams()}
|
||||
items={["repo"]} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var RepoStats = React.createClass({
|
||||
mixins: [Router.State],
|
||||
render: function(){
|
||||
return (
|
||||
<section className="content">
|
||||
<InfoBlock key={'info-block-repo'+ this.getParams().repo}
|
||||
title={this.getParams().repo} />
|
||||
<BarChart key={'bar-chart-repo-'+ this.getParams().team}
|
||||
api="/api/stat/repos/top"
|
||||
params={this.getParams()}
|
||||
items={["user", "team"]} />
|
||||
<StackedAreaChart key={'sa-chart-team-'+ this.getParams().team}
|
||||
api="/api/stat/repos/activity"
|
||||
params={this.getParams()}
|
||||
items={["user", "team"]} />
|
||||
<InfoBlock image={infoImage} title={infoTitle} text={infoText} />
|
||||
<BarChart api={bcApi} params={this.getParams()} items={bcItems} />
|
||||
<StackedAreaChart api={sacApi} params={this.getParams()} items={sacItems} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
@ -236,10 +197,10 @@ var routes = [
|
|||
<Router.DefaultRoute handler={SelectOrg} />
|
||||
<Router.NotFoundRoute handler={NotFound} />
|
||||
<Router.Route name="org" path=":org" handler={Org}>
|
||||
<Router.DefaultRoute handler={OrgStats} />
|
||||
<Router.Route name="team" path="teams/:team" handler={TeamStats} />
|
||||
<Router.Route name="user" path="users/:user" handler={UserStats} />
|
||||
<Router.Route name="repo" path="repos/:repo" handler={RepoStats} />
|
||||
<Router.DefaultRoute handler={Dashboard} />
|
||||
<Router.Route name="team" path="teams/:team" handler={Dashboard} />
|
||||
<Router.Route name="user" path="users/:user" handler={Dashboard} />
|
||||
<Router.Route name="repo" path="repos/:repo" handler={Dashboard} />
|
||||
</Router.Route>
|
||||
</Router.Route>
|
||||
];
|
||||
|
|
|
@ -7,13 +7,16 @@ var BarChart = React.createClass({
|
|||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
currentApi: null,
|
||||
currentParams: null,
|
||||
item: this.props.items[0],
|
||||
sort: 'commits',
|
||||
rawData: [],
|
||||
points: [],
|
||||
oldPoints: [],
|
||||
min: 0,
|
||||
max: 1,
|
||||
canvasWidth: 500
|
||||
canvasWidth: 500,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -24,11 +27,17 @@ var BarChart = React.createClass({
|
|||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.fetchData();
|
||||
this.calculateViewBoxWidth();
|
||||
window.addEventListener('resize', this.calculateViewBoxWidth);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
this.setState({
|
||||
'item': newProps.items[0],
|
||||
'sort': 'commits'
|
||||
}, this.fetchData);
|
||||
},
|
||||
|
||||
handleFilter: function(thing, i) {
|
||||
if (thing === 'item' && this.props.items[i] !== this.state.item) {
|
||||
this.setState({
|
||||
|
@ -48,14 +57,30 @@ var BarChart = React.createClass({
|
|||
},
|
||||
|
||||
fetchData: function() {
|
||||
if (!this.apiParams().item) {
|
||||
return;
|
||||
}
|
||||
if (this.state.currentApi === this.props.api &&
|
||||
this.state.currentParams === JSON.stringify(this.apiParams())) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('-----> fetching', this.state.currentApi, this.props.api);
|
||||
this.setState({
|
||||
currentApi: this.props.api,
|
||||
currentParams: JSON.stringify(this.apiParams())
|
||||
}, function() {
|
||||
$.get(this.props.api, this.apiParams(), function(res){
|
||||
this.setState({
|
||||
rawData: res
|
||||
rawData: res,
|
||||
oldPoints: this.state.points
|
||||
}, this.sort);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
sort: function() {
|
||||
console.log('-----> sorting');
|
||||
var sortFun = function(a, b) {
|
||||
return Math.abs(b[this.state.sort]) - Math.abs(a[this.state.sort]);
|
||||
}.bind(this);
|
||||
|
@ -132,9 +157,15 @@ var BarChart = React.createClass({
|
|||
y = this.y(i);
|
||||
|
||||
return (
|
||||
<Bar key={point.item} item={point.item} value={val}
|
||||
<Bar key={'bar-'+ i}
|
||||
item={point.item}
|
||||
value={val}
|
||||
color={Colors2[i]}
|
||||
x={x} y={y} offset={offset} width={width} height={height}
|
||||
x={x}
|
||||
y={y}
|
||||
offset={offset}
|
||||
width={width}
|
||||
height={height}
|
||||
onClick={this.handleClick.bind(this, point)} />
|
||||
);
|
||||
}
|
||||
|
@ -143,6 +174,36 @@ var BarChart = React.createClass({
|
|||
var Bar = React.createClass({
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
getInitialState: function() {
|
||||
return {lastx: 0, lastw: 0};
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
console.log("New bar props!", newProps.item, newProps.x, newProps.width);
|
||||
this.setState({
|
||||
lastx: this.props.x,
|
||||
lastw: this.props.width
|
||||
}, this.animate);
|
||||
},
|
||||
|
||||
animate: function() {
|
||||
var bar = this.refs.bar.getDOMNode(),
|
||||
anim = anim = document.createElementNS(SVGNS, 'animate');
|
||||
|
||||
if (bar.childNodes.length > 0) {
|
||||
bar.removeChild(bar.childNodes[0]);
|
||||
}
|
||||
|
||||
anim.setAttributeNS(null, 'attributeType', 'XML');
|
||||
anim.setAttributeNS(null, 'attributeName', 'width');
|
||||
anim.setAttributeNS(null, 'from', this.state.lastw);
|
||||
anim.setAttributeNS(null, 'to', this.props.width);
|
||||
anim.setAttributeNS(null, 'dur', '300ms');
|
||||
anim.setAttributeNS(null, 'repeatCount', '1');
|
||||
bar.appendChild(anim);
|
||||
anim.beginElement();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var val = this.props.value,
|
||||
item = this.props.item,
|
||||
|
@ -183,13 +244,23 @@ var Bar = React.createClass({
|
|||
|
||||
return (
|
||||
<g onClick={this.props.onClick}>
|
||||
<rect className="bar" fill={this.props.color}
|
||||
width={width} height={this.props.height}
|
||||
x={this.props.x} y={this.props.y} rx="2" ry="2" />
|
||||
<rect className="label_underlay"
|
||||
x={labelX - labelPaddingH} y={this.props.y + labelMarginV}
|
||||
height={labelOuterHeight} width={labelOuterWidth}
|
||||
rx="3" ry="3" />
|
||||
<rect ref="bar"
|
||||
className="bar"
|
||||
fill={this.props.color}
|
||||
width={width}
|
||||
height={this.props.height}
|
||||
x={this.props.x}
|
||||
y={this.props.y}
|
||||
rx="2"
|
||||
ry="2" />
|
||||
<rect
|
||||
className="label_underlay"
|
||||
width={labelOuterWidth}
|
||||
height={labelOuterHeight}
|
||||
x={labelX - labelPaddingH}
|
||||
y={this.props.y + labelMarginV}
|
||||
rx="3"
|
||||
ry="3" />
|
||||
<text className="label" x={labelX} y={labelY}>{label}</text>
|
||||
</g>
|
||||
);
|
||||
|
|
|
@ -15,6 +15,13 @@ var StackedAreaChart = React.createClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
this.setState({
|
||||
'item': newProps.items[0],
|
||||
'sort': 'commits'
|
||||
}, this.fetchData);
|
||||
},
|
||||
|
||||
calculateViewBoxWidth: function() {
|
||||
this.setState({
|
||||
canvasWidth: this.refs.svg.getDOMNode().offsetWidth
|
||||
|
@ -77,21 +84,21 @@ var StackedAreaChart = React.createClass({
|
|||
if (res[el.week] === undefined) {
|
||||
res[el.week] = {};
|
||||
}
|
||||
if (top.indexOf(el.item) > -1) {
|
||||
res[el.week][el.item] = el.commits;
|
||||
}
|
||||
return res;
|
||||
}, {});
|
||||
|
||||
var max = _.chain(this.state.rawData)
|
||||
.reduce(function(res, el) {
|
||||
if (res[el.week] === undefined) {
|
||||
res[el.week] = 0;
|
||||
}
|
||||
res[el.week] += el.commits;
|
||||
return res;
|
||||
}, {})
|
||||
var max = _.chain(weeks).keys().sort().reverse().take(15).map(function(week) {
|
||||
return _.sum(_.values(weeks[week]));
|
||||
})
|
||||
.max()
|
||||
.value();
|
||||
|
||||
// var max = _.max(_.map(weeks, function(items, week) {
|
||||
// return _.sum(_.values(items));
|
||||
// }));
|
||||
|
||||
this.setState({
|
||||
top: top,
|
||||
max: max,
|
||||
|
@ -102,11 +109,12 @@ var StackedAreaChart = React.createClass({
|
|||
buildPathD: function(points) {
|
||||
var maxWidth = this.state.canvasWidth,
|
||||
maxHeight = this.height,
|
||||
maxValue = this.state.max,
|
||||
len = points.length;
|
||||
var d = _.map(points, function(point, i) {
|
||||
return 'L'+ Math.floor(i/len*maxWidth) +','+ (maxHeight - point);
|
||||
return 'L'+ Math.floor(i/len*maxWidth) +','+ Math.floor(maxHeight - point);
|
||||
});
|
||||
d.unshift('M0,'+maxHeight);
|
||||
d.unshift('M0,'+ maxHeight);
|
||||
d.push('L'+ maxWidth +','+ maxHeight +'Z');
|
||||
|
||||
// for (var i = 0; i < missing; i++) {
|
||||
|
@ -133,8 +141,10 @@ var StackedAreaChart = React.createClass({
|
|||
});
|
||||
|
||||
var sum = 0;
|
||||
// console.log('----------');
|
||||
var points = _.map(values, function(val) {
|
||||
sum += Math.floor(val/max*maxHeight);
|
||||
// console.log(val, max, maxHeight, sum);
|
||||
return sum;
|
||||
});
|
||||
|
||||
|
@ -182,7 +192,10 @@ var StackedAreaChart = React.createClass({
|
|||
<ul className="legend">
|
||||
{_.pairs(colors).map(function(pair){
|
||||
return (
|
||||
<li><div className="color-dot" style={{backgroundColor: pair[1]}}></div>{pair[0]}</li>
|
||||
<li key={'legend-'+ pair[0]}>
|
||||
<div className="color-dot" style={{backgroundColor: pair[1]}}></div>
|
||||
{pair[0]}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
|
@ -194,7 +207,10 @@ var StackedAreaChart = React.createClass({
|
|||
var StackedArea = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<path d={this.props.path} fill={this.props.color} shapeRendering="optimizeQuality" />
|
||||
<path key={'sac-area-'+ this.props.item}
|
||||
d={this.props.path}
|
||||
fill={this.props.color}
|
||||
shapeRendering="optimizeQuality" />
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -148,7 +148,7 @@ order by commits desc`
|
|||
|
||||
const repoActivityQuery = `
|
||||
select
|
||||
c.week as weel,
|
||||
c.week as week,
|
||||
%s as item,
|
||||
sum(c.commits) as commits,
|
||||
sum(c.additions) - sum(c.deletions) as delta
|
||||
|
@ -211,6 +211,6 @@ func StatRepoTop(p map[string]interface{}) (res []StatItem) {
|
|||
|
||||
func StatRepoActivity(p map[string]interface{}) (res []StatPoint) {
|
||||
defer measure("StatRepoActivity", time.Now())
|
||||
mustSelectN(&res, fmt.Sprintf(repoTopQuery, p["item"]), p)
|
||||
mustSelectN(&res, fmt.Sprintf(repoActivityQuery, p["item"]), p)
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue