Skip to content

Commit

Permalink
Merge pull request #341 from hexlet-codebattle/heapmap
Browse files Browse the repository at this point in the history
Heapmap
  • Loading branch information
vtm9 committed Nov 5, 2018
2 parents 80ea98e + 93a1862 commit f3ee55a
Show file tree
Hide file tree
Showing 18 changed files with 989 additions and 550 deletions.
2 changes: 2 additions & 0 deletions docker-compose.yml
Expand Up @@ -7,6 +7,8 @@ services:
dockerfile: Dockerfile.dev
env_file: .env
command: mix phx.server
environment:
- "USER=${USER}"
ports:
- "${CODEBATTLE_PORT}:${CODEBATTLE_PORT}"
volumes:
Expand Down
35 changes: 35 additions & 0 deletions services/app/assets/css/app.scss
Expand Up @@ -30,3 +30,38 @@ $fa-font-path: "~font-awesome/fonts";
width: 96%;
height: 100%;
}


.react-calendar-heatmap {
.color-huge {
fill: #1e6823;
}
.color-huge:hover {
fill: #0e5813;
}
.color-large {
fill: #44a340;
}
.color-large:hover {
fill: #349330;
}
.color-small {
fill: #d6e685;
}
.color-small:hover {
fill: #c6d675;
}
.color-empty {
fill: #eee;
}
.color-empty:hover {
fill: #ccc;
}
}
.react-calendar-heatmap rect:hover {
stroke: #555;
stroke-width: 1px;
}
.react-calendar-heatmap {
font-size: 8px;
}
7 changes: 6 additions & 1 deletion services/app/assets/js/app.js
Expand Up @@ -18,16 +18,21 @@ import 'phoenix_html';
// Local files can be imported directly using relative
// paths "./socket" or full ones "web/static/js/socket".

import { renderGameWidget, renderLobby } from './widgets';
import { renderGameWidget, renderLobby, renderHeatmapWidget } from './widgets';


const gameWidgetRoot = document.getElementById('game-widget-root');
const heatmapRoot = document.getElementById('heatmap-root');
const gameListRoot = document.getElementById('game-list');

if (gameWidgetRoot) {
renderGameWidget(gameWidgetRoot);
}

if (heatmapRoot) {
renderHeatmapWidget(heatmapRoot);
}

if (gameListRoot) {
renderLobby(gameListRoot);
}
12 changes: 12 additions & 0 deletions services/app/assets/js/widgets/components/Loading.jsx
@@ -0,0 +1,12 @@
import React, { Component } from 'react';
import ReactLoading from 'react-loading';

export default class Messages extends Component {
render() {
return (
<div className="d-flex my-0 py-1 justify-content-center" >
<ReactLoading type="spin" color="#6c757d" height={50} width={50} />
</div>
);
}
}
12 changes: 8 additions & 4 deletions services/app/assets/js/widgets/containers/GameList.jsx
Expand Up @@ -5,6 +5,7 @@ import { connect } from 'react-redux';
// import PropTypes from 'prop-types';
import { fetchState } from '../middlewares/Lobby';
import GameStatusCodes from '../config/gameStatusCodes';
import Loading from '../components/Loading.jsx';
import Gon from 'gon';

class GameList extends React.Component {
Expand Down Expand Up @@ -100,6 +101,9 @@ class GameList extends React.Component {
render() {
const { games } = this.props;
const gameUrl = game => `/games/${game.game_id}`;
if (!games) {
return (<Loading />);
}
return (
<div>
<table className="table table-hover table-sm">
Expand All @@ -119,11 +123,11 @@ class GameList extends React.Component {
key={game.game_id}
>

<td className="align-middle" style={{whiteSpace: "nowrap"}}>{moment.utc(game.game_info.inserted_at).local().format('YYYY-MM-DD HH:mm')}</td>
<td className="align-middle" style={{whiteSpace:"nowrap"}}>{this.renderGameLevelBadge(game.game_info.level)}</td>
<td className="align-middle" style={{ whiteSpace: 'nowrap' }}>{moment.utc(game.game_info.inserted_at).local().format('YYYY-MM-DD HH:mm')}</td>
<td className="align-middle" style={{ whiteSpace: 'nowrap' }}>{this.renderGameLevelBadge(game.game_info.level)}</td>

<td className="align-middle" style={{whiteSpace:"nowrap"}}>{this.renderPlayers(game.users)}</td>
<td className="align-middle" style={{whiteSpace:"nowrap"}}>{game.game_info.state}</td>
<td className="align-middle" style={{ whiteSpace: 'nowrap' }}>{this.renderPlayers(game.users)}</td>
<td className="align-middle" style={{ whiteSpace: 'nowrap' }}>{game.game_info.state}</td>

<td className="align-middle">{this.renderGameActionButton(game)}</td>
</tr>
Expand Down
63 changes: 63 additions & 0 deletions services/app/assets/js/widgets/containers/Heatmap.jsx
@@ -0,0 +1,63 @@
import React from 'react';
import CalendarHeatmap from 'react-calendar-heatmap';
import Loading from '../components/Loading.jsx';
import axios from 'axios';

class Heatmap extends React.Component {
constructor(props) {
super(props);
this.state = {
activities: null,
};
}

colorScale(count) {
if (count >= 5) {
return 'color-huge';
} else if (count >= 3) {
return 'color-large';
} else if (count >= 1) {
return 'color-small';
}
return 'color-empty';
}

componentDidMount() {
axios.get('/api/v1/activity')
.then((response) => { console.log(response.data); this.setState(response.data); });
}

render() {
const { activities } = this.state;
if (!activities) {
return (<Loading />);
}
return (
<div className="card shadow rounded">
<div className="d-flex my-0 py-1 justify-content-center card-header font-weight-bold" >
Activity
</div>
<div className="card-body py-0 my-0" >
<CalendarHeatmap
viewBox="0 0 0 0"
showWeekdayLabels
values={activities}
classForValue={(value) => {
if (!value) {
return 'color-empty';
}
return this.colorScale(value.count);
}}
titleForValue={(value) => {
if (!value) {
return 'No games';
}
return `${value.count} games on ${value.date}`;
}}
/>
</div>
</div>
);
}
}
export default Heatmap;
3 changes: 2 additions & 1 deletion services/app/assets/js/widgets/index.jsx
Expand Up @@ -2,7 +2,8 @@ import React from 'react';
import { render } from 'react-dom';
import './vendors';
import { Game, Lobby } from './App';
import Heatmap from './containers/Heatmap';

export const renderGameWidget = domElement => render(<Game />, domElement);
export const renderLobby = domElement => render(<Lobby />, domElement);

export const renderHeatmapWidget = domElement => render(<Heatmap />, domElement);
2 changes: 1 addition & 1 deletion services/app/assets/js/widgets/reducers/gameList.js
Expand Up @@ -2,7 +2,7 @@ import { handleActions } from 'redux-actions';
import _ from 'lodash';
import * as actions from '../actions';

const initState = { games: [] };
const initState = { games: null };

const gameList = handleActions({
[actions.fetchGameList](state, { payload: { games } }) {
Expand Down
4 changes: 1 addition & 3 deletions services/app/config/dev.exs
Expand Up @@ -12,9 +12,7 @@ config :codebattle, CodebattleWeb.Endpoint,
code_reloader: true,
check_origin: false,
cache_static_lookup: false,
watchers: [
yarn: ["watch", cd: Path.expand("..", __DIR__)]
]
watchers: [ yarn: ["watch", cd: Path.expand("..", __DIR__)] ]

# Watch static and templates for browser reloading.
config :codebattle, CodebattleWeb.Endpoint,
Expand Down
@@ -0,0 +1,26 @@
defmodule CodebattleWeb.Api.V1.ActivityController do
@all [:index, :show]

use CodebattleWeb, :controller

alias Codebattle.{Repo, User, UserGame}
import Ecto.Query, only: [from: 2]

defmacro to_char(field, format) do
quote do
fragment("to_char(?, ?)", unquote(field), unquote(format))
end
end
plug(CodebattleWeb.Plugs.RequireAuth when action in @all)

def show(conn, _params) do
current_user = conn.assigns.current_user
query = from ug in UserGame,
where: ug.user_id == ^current_user.id,
group_by: to_char(ug.inserted_at, "YYYY-mm-dd"),
select: %{date: to_char(ug.inserted_at, "YYYY-mm-dd"), count: count(ug.id)}

user_games = Repo.all(query)
json conn, %{ activities: user_games }
end
end
12 changes: 12 additions & 0 deletions services/app/lib/codebattle_web/router.ex
Expand Up @@ -14,6 +14,10 @@ defmodule CodebattleWeb.Router do

pipeline :api do
plug(:accepts, ["json"])
plug(:fetch_session)
plug(CodebattleWeb.Plugs.AssignCurrentUser)
plug(:protect_from_forgery)
plug(:put_secure_browser_headers)
end

scope "/auth", CodebattleWeb do
Expand All @@ -22,6 +26,14 @@ defmodule CodebattleWeb.Router do
get("/:provider/callback", AuthController, :callback)
end

scope "/api", CodebattleWeb.Api, as: :api do
pipe_through(:api)

scope "/v1", V1, as: :v1 do
get("/activity", ActivityController, :show)
end
end

scope "/", CodebattleWeb do
# Use the default browser stack
pipe_through(:browser)
Expand Down
7 changes: 5 additions & 2 deletions services/app/lib/codebattle_web/templates/user/show.html.slim
Expand Up @@ -2,13 +2,16 @@
.col-12.text-center.mt-4
.row
.col-10.col-sm-4.col-md-2.m-auto
img.attachment.user.avatar.img-fluid.rounded[alt="#{@user.name}" src="https://avatars0.githubusercontent.com/u/#{@user.github_id}"]
img.attachment.user.avatar.img-fluid.shadow.rounded[alt="#{@user.name}" src="https://avatars0.githubusercontent.com/u/#{@user.github_id}"]
h1.mt-1.mb-0
= @user.name
a.text-muted[href="https://github.com/#{@user.name}"]
span.h3
span.fa.fa-github.mt-1.pl-3
span.fa.fa-github.mt-5.pl-3

.row.px-4.mt-5.justify-content-center
.col-6
#heatmap-root
.row.px-4.mt-5.justify-content-center
.col-12.col-md-6.col-lg-3.text-center
.h1
Expand Down
1 change: 1 addition & 0 deletions services/app/mix.exs
Expand Up @@ -65,6 +65,7 @@ defmodule Codebattle.Mixfile do
{:phoenix_html, "~> 2.10"},
{:gettext, "~> 0.11"},
{:cowboy, "~> 1.0"},
{:plug_cowboy, "~> 1.0"},
{:phoenix_slime, "~> 0.8.0"},
{:ueberauth, "~> 0.4"},
{:ueberauth_github, "~> 0.4"},
Expand Down

0 comments on commit f3ee55a

Please sign in to comment.