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);
 | 
			
		||||
        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'];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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 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() {
 | 
			
		||||
        $.get(this.props.api, this.apiParams(), function(res){
 | 
			
		||||
            this.setState({
 | 
			
		||||
                rawData: res
 | 
			
		||||
            }, this.sort);
 | 
			
		||||
        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,
 | 
			
		||||
                    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,20 +84,20 @@ var StackedAreaChart = React.createClass({
 | 
			
		||||
            if (res[el.week] === undefined) {
 | 
			
		||||
                res[el.week] = {};
 | 
			
		||||
            }
 | 
			
		||||
            res[el.week][el.item] = el.commits;
 | 
			
		||||
            if (top.indexOf(el.item) > -1) {
 | 
			
		||||
                res[el.week][el.item] = 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 = _.chain(this.state.rawData)
 | 
			
		||||
            .reduce(function(res, el) {
 | 
			
		||||
                if (res[el.week] === undefined) {
 | 
			
		||||
                    res[el.week] = 0;
 | 
			
		||||
                }
 | 
			
		||||
                res[el.week] += el.commits;
 | 
			
		||||
                return res;
 | 
			
		||||
            }, {})
 | 
			
		||||
            .max()
 | 
			
		||||
            .value();
 | 
			
		||||
        // var max = _.max(_.map(weeks, function(items, week) {
 | 
			
		||||
        //         return _.sum(_.values(items));
 | 
			
		||||
        //     }));
 | 
			
		||||
 | 
			
		||||
        this.setState({
 | 
			
		||||
            top: top,
 | 
			
		||||
@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user