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