Skip to content
Browse files

Fixes storage-related bugs and implements the visitor timeline.

  • Loading branch information...
1 parent f8a5cc0 commit 306e5dbbc9366ba3f387e8bb7e164aa40c1c1ea9 @m2w committed Jan 13, 2012
Showing with 84 additions and 39 deletions.
  1. +1 −0 README.md
  2. +1 −1 src/erli.hrl
  3. +16 −14 src/erli_storage.erl
  4. +20 −7 src/path_resource.erl
  5. +1 −1 templates/base.dtl
  6. +45 −16 templates/stats.dtl
View
1 README.md
@@ -28,6 +28,7 @@ usability:
code quality:
++ enforce the Erlang coding standards: [http://www.erlang.se/doc/programming_rules.shtml](http://www.erlang.se/doc/programming_rules.shtml)
+ eunit tests
## the api
View
2 src/erli.hrl
@@ -9,7 +9,7 @@
-define(SCRIPT_NAME, "parse_eval.py").
-record(target, {target, paths=[], reported=0, rep_num=0}).
--record(timeslots, {timeslots, night=0, morning=0, afternoon=0, evening=0}).
+-record(timeslots, {night=0, morning=0, afternoon=0, evening=0}).
-record(path, {path, total_clicks=0, unique_clicks=0, country_lst=[],
timeslot_visits=#timeslots{}}).
-record(visitor_ip, {visitor_ip, paths=[]}).
View
30 src/erli_storage.erl
@@ -41,7 +41,7 @@ put(TargetUrl) ->
fun() ->
Table = mnesia:table(target),
QueryHandle = qlc:q([T || T <- Table,
- T#target.target =:= binary_to_list(TargetUrl)]),
+ T#target.target =:= TargetUrl]),
qlc:eval(QueryHandle)
end),
make_target(TargetUrl, MatchingTarget).
@@ -55,13 +55,13 @@ put(TargetUrl, PathS) ->
fun() ->
Table = mnesia:table(target),
QueryHandle = qlc:q([T || T <- Table,
- T#target.target =:= binary_to_list(TargetUrl)]),
+ T#target.target =:= TargetUrl]),
qlc:eval(QueryHandle)
end),
case MatchingTarget of
[T] when T#target.rep_num > ?FLAG_LIMIT ->
target_banned;
- [#target{paths=[ExistingPaths], _=_} = T] ->
+ [#target{paths=ExistingPaths, _=_} = T] ->
NewTarget = T#target{paths=[Path|ExistingPaths]},
{atomic, _Ret} = mnesia:transaction(
fun() ->
@@ -159,28 +159,30 @@ update_path_stats(Path, Countries, UniqueIPs, ClickCount, {_Date, {H, _M, _S}})
),
% update the path stats
- NewPath = ThePath#path{country_lst = CountryUnion,
- unique_clicks = UC + UniqueClicks,
- total_clicks = TC + ClickCount,
- timeslot_visits = classify_timeslot(H, ClickCount, TSV)},
+ NewPath = ThePath#path{country_lst=CountryUnion,
+ unique_clicks=UC + UniqueClicks,
+ total_clicks=TC + ClickCount,
+ timeslot_visits=classify_timeslot(H, ClickCount, TSV)},
NewTarget = Target#target{paths = [NewPath | OtherPaths]},
% flush to mnesia
mnesia:dirty_write(NewTarget).
%%%=============================================================================
%%% Internal functions
%%%=============================================================================
-classify_timeslot(Hour, Clicks, TSV) ->
+classify_timeslot(Hour,
+ Clicks,
+ #timeslots{night=N, morning=M, afternoon=A, evening=E}=TS) ->
% classify the data according to a time-slot
case Hour of
H when H =< 6 ->
- TSV#timeslots.night + Clicks;
+ TS#timeslots{night=N + Clicks};
H when H > 6, H =< 12 ->
- TSV#timeslots.morning + Clicks;
+ TS#timeslots{morning=M + Clicks};
H when H > 12, H =< 18 ->
- TSV#timeslots.afternoon + Clicks;
+ TS#timeslots{afternoon=A + Clicks};
H when H > 18 ->
- TSV#timeslots.evening + Clicks
+ TS#timeslots{evening=E + Clicks}
end.
is_unique_for_path(Path, IP) ->
@@ -207,15 +209,15 @@ path_in_target(TargetPaths, SearchPath) ->
case Path of
[] ->
false;
- _ ->
+ _Any ->
true
end.
make_target(TargetUrl, MatchingTarget) ->
case MatchingTarget of
[T] when T#target.rep_num > ?FLAG_LIMIT ->
target_banned;
- [#target{paths=[ExistingPaths], _=_} = T] ->
+ [#target{paths=ExistingPaths, _=_} = T] ->
case make_unique_path(TargetUrl) of
error ->
error;
View
27 src/path_resource.erl
@@ -106,15 +106,28 @@ handle_path_subreq(Type, RD, Ctx) ->
{path, list_to_binary(ThePath#path.path)},
{total_clicks, ThePath#path.total_clicks},
{unique_clicks, ThePath#path.unique_clicks},
- {country_lst, ThePath#path.country_lst}]),
+ {country_lst, ThePath#path.country_lst},
+ {timeslots,
+ mochiweb_util:record_to_proplist(
+ ThePath#path.timeslot_visits,
+ record_info(fields, timeslots)
+ )
+ }]),
{Content, RD, Ctx};
json ->
% TODO: switch from mochijson2 to https://github.com/davisp/eep0018
- Content = mochijson2:encode({struct, [{target, Ctx#target.target},
- {path, list_to_binary(ThePath#path.path)},
- {total_clicks, ThePath#path.total_clicks},
- {unique_clicks, ThePath#path.unique_clicks},
- {country_lst, ThePath#path.country_lst}]}),
+ Content = mochijson2:encode({struct,
+ [{target, Ctx#target.target},
+ {path, list_to_binary(ThePath#path.path)},
+ {total_clicks, ThePath#path.total_clicks},
+ {unique_clicks, ThePath#path.unique_clicks},
+ {country_lst, ThePath#path.country_lst},
+ {timeslots,
+ mochiweb_util:record_to_proplist(
+ ThePath#path.timeslot_visits,
+ record_info(fields, timeslots)
+ )}
+ ]}),
{Content, RD, Ctx}
end;
"report" ->
@@ -127,7 +140,7 @@ handle_path_subreq(Type, RD, Ctx) ->
{{halt, 202}, RD, Ctx} % with a 202
end;
"check" ->
- {{halt, 200}, RD, Ctx}; % fake landing for low overhead checking of path availability
+ {{halt, 200}, RD, Ctx}; % landing for lower overhead checking of path availability
_ ->
{{halt, 404}, RD, Ctx}
end.
View
2 templates/base.dtl
@@ -19,7 +19,7 @@
{% block body %}{% endblock %}
</div>
<footer>
- Created by Moritz Windelen 2012 <span id="about_popover" rel="popover" data-content="This is a weekend project of mine as a way of deepening my understanding of basic Erlang and to experiment the very cool Webmachine REST-kit." data-original-title="About erli">(why?)</span>
+ Created by Moritz Windelen 2012 <span id="about_popover" rel="popover" data-content="This is a weekend project of mine as a way of deepening my understanding of basic Erlang and to experiment with the very cool Webmachine REST-kit." data-original-title="About erli">(why?)</span>
</footer>
{% block modal_content %}{% endblock %}
</body>
View
61 templates/stats.dtl
@@ -3,31 +3,60 @@
{% block title %}statistics{% endblock %}
{% block extra_js %}
-<script type="text/javascript" src="/static/worldmap.js"></script>
-<script type="text/javascript">
-$(document).ready(function(){
- WorldMap({
- {% if surl.countries %}
- detail: {
- {% for country in surl.countries %}
- "{{country}}": "#980000",
- {% endfor %}
- },
- {% endif %}
- id: "themap"
+ <script type="text/javascript" src="/static/worldmap.js"></script>
+ <script type="text/javascript" src="https://www.google.com/jsapi"></script>
+ <script type="text/javascript">
+ google.load("visualization", "1", {packages:["corechart"]});
+ google.setOnLoadCallback(drawChart);
+ function drawChart() {
+ var data = new google.visualization.DataTable();
+ data.addColumn('string', 'Time of day');
+ data.addColumn('number', 'Number of clicks');
+ data.addRows([
+ ['night (0:00-6:00)', {{ timeslots.night }}],
+ ['morning (6:00-12:00)', {{ timeslots.morning }}],
+ ['afternoon (12:00-18:00)', {{ timeslots.afternoon }}],
+ ['evening (18:00-24:00)', {{ timeslots.evening }}],
+ ]);
+
+
+ var options = {
+ width: 800, height: 240,
+ title: 'Click frequency by time of day',
+ vAxis: {minValue: 0, maxValue: 50},
+ chartArea: {width: "75%", height: "75%"}
+ };
+
+ var chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
+ chart.draw(data, options);
+ }
+ $(document).ready(function(){
+ WorldMap({
+ {% if surl.countries %}
+ detail: {
+ {% for country in surl.countries %}
+ "{{country}}": "#980000",
+ {% endfor %}
+ },
+ {% endif %}
+ id: "themap"
+ });
});
-});
-</script>
+ </script>
{% endblock %}
{% block heading %}usage statistics{% endblock %}
{% block body %}
-<p>The shortened URL: <code>/{{ path }}</code> has been viewed a total of {{ total_clicks }} times, taking {{ unique_clicks }} visitors to <code>{{ target }}</code></p>
+<p>The shortened URL: <code>/{{ path }}</code> has been viewed a total of {{ total_clicks }} times, taking {{ unique_clicks }} visitors to <a href="/{{ path }}"><code>{{ target }}</code></a></p>
<p>The visitors came from:</p>
-<canvas id="themap" width="800" height="325">
+<div>
+ <canvas id="themap" width="800" height="325">
+</div>
+
+<div id="chart_div"></div>
<p>PS: these stats are updated hourly</p>
{% endblock %}

0 comments on commit 306e5db

Please sign in to comment.
Something went wrong with that request. Please try again.