Commit aa080838 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge pull request #4212 from karlo57/feature/graphs

Contributors Graph
parents b9d989dc 71d67e65
......@@ -107,6 +107,12 @@ gem 'tinder', '~> 1.9.2'
# HipChat integration
gem "hipchat", "~> 0.9.0"
# d3
gem "d3_rails", "~> 3.1.4"
# underscore-rails
gem "underscore-rails", "~> 1.4.4"
group :assets do
gem "sass-rails"
gem "coffee-rails"
......@@ -177,6 +183,7 @@ group :development, :test do
gem 'poltergeist', '~> 1.3.0'
gem 'spork', '~> 1.0rc'
gem 'jasmine'
end
group :test do
......
......@@ -69,6 +69,8 @@ GEM
celluloid (0.14.0)
timers (>= 1.0.0)
charlock_holmes (0.6.9.4)
childprocess (0.3.9)
ffi (~> 1.0, >= 1.0.11)
chosen-rails (0.9.8)
railties (~> 3.0)
thor (~> 0.14)
......@@ -92,6 +94,8 @@ GEM
simplecov (>= 0.7)
thor
crack (0.3.2)
d3_rails (3.1.4)
railties (>= 3.1.0)
daemons (1.1.9)
database_cleaner (1.0.1)
debug_inspector (0.0.2)
......@@ -216,6 +220,12 @@ GEM
multi_xml (>= 0.5.2)
httpauth (0.2.0)
i18n (0.6.1)
jasmine (1.3.2)
jasmine-core (~> 1.3.1)
rack (~> 1.0)
rspec (>= 1.3.1)
selenium-webdriver (>= 0.1.3)
jasmine-core (1.3.1)
journey (1.0.4)
jquery-atwho-rails (0.3.0)
jquery-rails (2.1.3)
......@@ -392,6 +402,7 @@ GEM
rspec-mocks (~> 2.13.0)
ruby-progressbar (1.0.2)
rubyntlm (0.1.1)
rubyzip (0.9.9)
sanitize (2.0.3)
nokogiri (>= 1.4.4, < 1.6)
sass (3.2.9)
......@@ -408,6 +419,11 @@ GEM
select2-rails (3.3.1)
sass-rails (>= 3.2)
thor (~> 0.14)
selenium-webdriver (2.32.1)
childprocess (>= 0.2.5)
multi_json (~> 1.0)
rubyzip
websocket (~> 1.0.4)
settingslogic (2.0.9)
sexp_processor (4.2.1)
shoulda-matchers (2.1.0)
......@@ -482,6 +498,7 @@ GEM
uglifier (2.0.1)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
underscore-rails (1.4.4)
virtus (0.5.4)
backports (~> 2.6.1)
descendants_tracker (~> 0.0.1)
......@@ -490,6 +507,7 @@ GEM
webmock (1.11.0)
addressable (>= 2.2.7)
crack (>= 0.3.2)
websocket (1.0.7)
xpath (2.0.0)
nokogiri (~> 1.3)
yajl-ruby (1.1.0)
......@@ -510,6 +528,7 @@ DEPENDENCIES
coffee-rails
colored
coveralls
d3_rails (~> 3.1.4)
database_cleaner
devise
email_spec
......@@ -536,6 +555,7 @@ DEPENDENCIES
haml-rails
hipchat (~> 0.9.0)
httparty
jasmine
jquery-atwho-rails (= 0.3.0)
jquery-rails (= 2.1.3)
jquery-turbolinks
......@@ -586,4 +606,5 @@ DEPENDENCIES
tinder (~> 1.9.2)
turbolinks
uglifier
underscore-rails (~> 1.4.4)
webmock
......@@ -27,3 +27,5 @@
//= require branch-graph
//= require ace-src-noconflict/ace
//= require_tree .
//= require d3
//= require underscore
class window.StatGraph
@log: {}
@get_log: ->
@log
@set_log: (data) ->
@log = data
class window.ContributorsStatGraph
init: (log) ->
@parsed_log = ContributorsStatGraphUtil.parse_log(log)
@set_current_field("commits")
total_commits = ContributorsStatGraphUtil.get_total_data(@parsed_log, @field)
author_commits = ContributorsStatGraphUtil.get_author_data(@parsed_log, @field)
@add_master_graph(total_commits)
@add_authors_graph(author_commits)
@change_date_header()
add_master_graph: (total_data) ->
@master_graph = new ContributorsMasterGraph(total_data)
@master_graph.draw()
add_authors_graph: (author_data) ->
@authors = []
_.each(author_data, (d) =>
author_header = @create_author_header(d)
$(".contributors-list").append(author_header)
@authors[d.author] = author_graph = new ContributorsAuthorGraph(d.dates)
author_graph.draw()
)
format_author_commit_info: (author) ->
author.commits + " commits " + author.additions + " ++ / " + author.deletions + " --"
create_author_header: (author) ->
list_item = $('<li/>', {
class: 'person'
style: 'display: block;'
})
author_name = $('<h4>' + author.author + '</h4>')
author_commit_info_span = $('<span/>', {
class: 'commits'
})
author_commit_info = @format_author_commit_info(author)
author_commit_info_span.text(author_commit_info)
list_item.append(author_name)
list_item.append(author_commit_info_span)
list_item
redraw_master: ->
total_data = ContributorsStatGraphUtil.get_total_data(@parsed_log, @field)
@master_graph.set_data(total_data)
@master_graph.redraw()
redraw_authors: ->
$("ol").html("")
x_domain = ContributorsGraph.prototype.x_domain
author_commits = ContributorsStatGraphUtil.get_author_data(@parsed_log, @field, x_domain)
_.each(author_commits, (d) =>
@redraw_author_commit_info(d)
$(@authors[d.author].list_item).appendTo("ol")
@authors[d.author].set_data(d.dates)
@authors[d.author].redraw()
)
set_current_field: (field) ->
@field = field
change_date_header: ->
x_domain = ContributorsGraph.prototype.x_domain
print_date_format = d3.time.format("%B %e %Y");
print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1]);
$("#date_header").text(print);
redraw_author_commit_info: (author) ->
author_list_item = $(@authors[author.author].list_item)
author_commit_info = @format_author_commit_info(author)
author_list_item.find("span").text(author_commit_info)
\ No newline at end of file
class window.ContributorsGraph
MARGIN:
top: 20
right: 20
bottom: 30
left: 50
x_domain: null
y_domain: null
dates: []
@set_x_domain: (data) =>
@prototype.x_domain = data
@set_y_domain: (data) =>
@prototype.y_domain = [0, d3.max(data, (d) ->
d.commits = d.commits ? d.additions ? d.deletions
)]
@init_x_domain: (data) =>
@prototype.x_domain = d3.extent(data, (d) ->
d.date
)
@init_y_domain: (data) =>
@prototype.y_domain = [0, d3.max(data, (d) ->
d.commits = d.commits ? d.additions ? d.deletions
)]
@init_domain: (data) =>
@init_x_domain(data)
@init_y_domain(data)
@set_dates: (data) =>
@prototype.dates = data
set_x_domain: ->
@x.domain(@x_domain)
set_y_domain: ->
@y.domain(@y_domain)
set_domain: ->
@set_x_domain()
@set_y_domain()
create_scale: (width, height) ->
@x = d3.time.scale().range([0, width]).clamp(true)
@y = d3.scale.linear().range([height, 0]).nice()
draw_x_axis: ->
@svg.append("g").attr("class", "x axis").attr("transform", "translate(0, #{@height})")
.call(@x_axis);
draw_y_axis: ->
@svg.append("g").attr("class", "y axis").call(@y_axis)
set_data: (data) ->
@data = data
class window.ContributorsMasterGraph extends ContributorsGraph
constructor: (@data) ->
@width = 1100
@height = 125
@x = null
@y = null
@x_axis = null
@y_axis = null
@area = null
@svg = null
@brush = null
@x_max_domain = null
process_dates: (data) ->
dates = @get_dates(data)
@parse_dates(data)
ContributorsGraph.set_dates(dates)
get_dates: (data) ->
_.pluck(data, 'date')
parse_dates: (data) ->
parseDate = d3.time.format("%Y-%m-%d").parse
data.forEach((d) ->
d.date = parseDate(d.date)
)
create_scale: ->
super @width, @height
create_axes: ->
@x_axis = d3.svg.axis().scale(@x).orient("bottom")
@y_axis = d3.svg.axis().scale(@y).orient("left")
create_svg: ->
@svg = d3.select("#contributors-master").append("svg")
.attr("width", @width + @MARGIN.left + @MARGIN.right)
.attr("height", @height + @MARGIN.top + @MARGIN.bottom)
.attr("class", "tint-box")
.append("g")
.attr("transform", "translate(" + @MARGIN.left + "," + @MARGIN.top + ")")
create_area: (x, y) ->
@area = d3.svg.area().x((d) ->
x(d.date)
).y0(@height).y1((d) ->
y(d.commits = d.commits ? d.additions ? d.deletions)
).interpolate("basis")
create_brush: ->
@brush = d3.svg.brush().x(@x).on("brushend", @update_content);
draw_path: (data) ->
@svg.append("path").datum(data).attr("class", "area").attr("d", @area);
add_brush: ->
@svg.append("g").attr("class", "selection").call(@brush).selectAll("rect").attr("height", @height);
update_content: =>
ContributorsGraph.set_x_domain(if @brush.empty() then @x_max_domain else @brush.extent())
$("#brush_change").trigger('change')
draw: ->
@process_dates(@data)
@create_scale()
@create_axes()
ContributorsGraph.init_domain(@data)
@x_max_domain = @x_domain
@set_domain()
@create_area(@x, @y)
@create_svg()
@create_brush()
@draw_path(@data)
@draw_x_axis()
@draw_y_axis()
@add_brush()
redraw: ->
@process_dates(@data)
ContributorsGraph.set_y_domain(@data)
@set_y_domain()
@svg.select("path").datum(@data)
@svg.select("path").attr("d", @area)
@svg.select(".y.axis").call(@y_axis)
class window.ContributorsAuthorGraph extends ContributorsGraph
constructor: (@data) ->
@width = 490
@height = 130
@x = null
@y = null
@x_axis = null
@y_axis = null
@area = null
@svg = null
@list_item = null
create_scale: ->
super @width, @height
create_axes: ->
@x_axis = d3.svg.axis().scale(@x).orient("bottom").tickFormat(d3.time.format("%m/%d"));
@y_axis = d3.svg.axis().scale(@y).orient("left")
create_area: (x, y) ->
@area = d3.svg.area().x((d) ->
parseDate = d3.time.format("%Y-%m-%d").parse
x(parseDate(d))
).y0(@height).y1((d) =>
if @data[d]? then y(@data[d]) else y(0)
).interpolate("basis")
create_svg: ->
@list_item = d3.selectAll(".person")[0].pop()
@svg = d3.select(@list_item).append("svg")
.attr("width", @width + @MARGIN.left + @MARGIN.right)
.attr("height", @height + @MARGIN.top + @MARGIN.bottom)
.attr("class", "spark")
.append("g")
.attr("transform", "translate(" + @MARGIN.left + "," + @MARGIN.top + ")")
draw_path: (data) ->
@svg.append("path").datum(data).attr("class", "area-contributor").attr("d", @area);
draw: ->
@create_scale()
@create_axes()
@set_domain()
@create_area(@x, @y)
@create_svg()
@draw_path(@dates)
@draw_x_axis()
@draw_y_axis()
redraw: ->
@set_domain()
@svg.select("path").datum(@dates)
@svg.select("path").attr("d", @area)
@svg.select(".x.axis").call(@x_axis)
@svg.select(".y.axis").call(@y_axis)
window.ContributorsStatGraphUtil =
parse_log: (log) ->
total = {}
by_author = {}
for entry in log
@add_date(entry.date, total) unless total[entry.date]?
@add_author(entry.author, by_author) unless by_author[entry.author]?
@add_date(entry.date, by_author[entry.author]) unless by_author[entry.author][entry.date]
@store_data(entry, total[entry.date], by_author[entry.author][entry.date])
total = _.toArray(total)
by_author = _.toArray(by_author)
total: total, by_author: by_author
add_date: (date, collection) ->
collection[date] = {}
collection[date].date = date
add_author: (author, by_author) ->
by_author[author] = {}
by_author[author].author = author
store_data: (entry, total, by_author) ->
@store_commits(total, by_author)
@store_additions(entry, total, by_author)
@store_deletions(entry, total, by_author)
store_commits: (total, by_author) ->
@add(total, "commits", 1)
@add(by_author, "commits", 1)
add: (collection, field, value) ->
collection[field] ?= 0
collection[field] += value
store_additions: (entry, total, by_author) ->
entry.additions ?= 0
@add(total, "additions", entry.additions)
@add(by_author, "additions", entry.additions)
store_deletions: (entry, total, by_author) ->
entry.deletions ?= 0
@add(total, "deletions", entry.deletions)
@add(by_author, "deletions", entry.deletions)
get_total_data: (parsed_log, field) ->
log = parsed_log.total
total_data = @pick_field(log, field)
_.sortBy(total_data, (d) ->
d.date
)
pick_field: (log, field) ->
total_data = []
_.each(log, (d) ->
total_data.push(_.pick(d, [field, 'date']))
)
total_data
get_author_data: (parsed_log, field, date_range = null) ->
log = parsed_log.by_author
author_data = []
_.each(log, (log_entry) =>
parsed_log_entry = @parse_log_entry(log_entry, field, date_range)
if not _.isEmpty(parsed_log_entry.dates)
author_data.push(parsed_log_entry)
)
_.sortBy(author_data, (d) ->
d[field]
).reverse()
parse_log_entry: (log_entry, field, date_range) ->
parsed_entry = {}
parsed_entry.author = log_entry.author
parsed_entry.dates = {}
parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0
_.each(_.omit(log_entry, 'author'), (value, key) =>
if @in_range(value.date, date_range)
parsed_entry.dates[value.date] = value[field]
parsed_entry.commits += value.commits
parsed_entry.additions += value.additions
parsed_entry.deletions += value.deletions
)
return parsed_entry
in_range: (date, date_range) ->
if date_range is null || date_range[0] <= new Date(date) <= date_range[1]
true
else
false
\ No newline at end of file
......@@ -37,6 +37,7 @@
@import "sections/wiki.scss";
@import "sections/wall.scss";
@import "sections/dashboard.scss";
@import "sections/stat_graph.scss";
@import "highlight/white.scss";
@import "highlight/dark.scss";
......
.tint-box {
border-radius: 6px;
background: #f3f3f3;
position: relative;
margin-bottom: 10px;
}
.area {
fill: #1db34f;
fill-opacity: 0.5;
}
.axis {
fill: #aaa;
font-size: 10px;
}
#contributors .person {
-moz-box-sizing: border-box;
box-sizing: border-box;
float: left;
border-radius: 2px;
margin: 10px;
border: 1px solid #ddd;
}
.contributors-list {
margin: 0 0 10px 0;
list-style: none;
padding: 0;
}
#contributors .person .spark {
display: block;
background: #f7f7f7;
}
#contributors .person .area-contributor {
fill: #f17f49;
}
.selection rect {
fill: #333;
fill-opacity: 0.1;
stroke: #333;
stroke-width: 1px;
stroke-opacity: 0.4;
shape-rendering: crispedges;
stroke-dasharray: 3 3;
}
.right{
float: right;
display: inline-block;
margin-top: 5px;
}
class StatGraphController < ProjectResourceController
# Authorize
before_filter :authorize_read_project!
before_filter :authorize_code_access!
before_filter :require_non_empty_project
def show
@repo = @project.repository
@stats = Gitlab::GitStats.new(@repo.raw, @repo.root_ref)
@log = @stats.parsed_log.to_json
end
end
\ No newline at end of file
......@@ -11,6 +11,8 @@
= link_to "Commits", project_commits_path(@project, @ref || @repository.root_ref)
= nav_link(controller: %w(graph)) do
= link_to "Network", project_graph_path(@project, @ref || @repository.root_ref)
= nav_link(controller: %w(stat_graph)) do
= link_to "Graphs", project_stat_graph_path(@project, @ref || @repository.root_ref)
- if @project.issues_enabled
= nav_link(controller: %w(issues milestones labels)) do
......
.header.clearfix
.right
%select
%option{:value => "commits"} Commits
%option{:value => "additions"} Additions
%option{:value => "deletions"} Deletions
%h3#date_header
%input#brush_change{:type => "hidden"}
.graphs
#contributors-master
#contributors.clearfix
%ol.contributors-list.clearfix
:javascript
controller = new ContributorsStatGraph
controller.init(#{@log})
$("select").change( function () {
var field = $(this).val()
controller.set_current_field(field)
controller.redraw_master()
controller.redraw_authors()
})
$("#brush_change").change( function () {
controller.change_date_header()
controller.redraw_authors()
})
\ No newline at end of file
......@@ -190,6 +190,7 @@ Gitlab::Application.routes.draw do
resources :compare, only: [:index, :create]
resources :blame, only: [:show], constraints: {id: /.+/}
resources :graph, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
resources :stat_graph, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/}
scope module: :projects do
......
require 'gitlab/git_stats_log_parser'
module Gitlab
class GitStats
attr_accessor :repo, :ref
def initialize repo, ref
@repo, @ref = repo, ref
end
def log
args = ['--format=%aN%x0a%ad', '--date=short', '--shortstat', '--no-merges']
repo.git.run(nil, 'log', nil, {}, args)
end
def parsed_log
LogParser.parse_log(log)
end
end
end
class LogParser
#Parses the log file into a collection of commits
#Data model: {author, date, additions, deletions}
def self.parse_log log_from_git
log = log_from_git.split("\n")
i = 0
collection = []
entry = {}
while i <= log.size do
pos = i % 4
case pos
when 0
unless i == 0
collection.push(entry)
entry = {}
end
entry[:author] = log[i].to_s
when 1
entry[:date] = log[i].to_s
when 3
changes = log[i].split(",")
entry[:additions] = changes[1].to_i unless changes[1].nil?
entry[:deletions] = changes[2].to_i unless changes[2].nil?
end
i += 1
end
collection
end
end
\ No newline at end of file
describe("ContributorsGraph", function () {
describe("#set_x_domain", function () {
it("set the x_domain", function () {
ContributorsGraph.set_x_domain(20)
expect(ContributorsGraph.prototype.x_domain).toEqual(20)
})
})
describe("#set_y_domain", function () {
it("sets the y_domain", function () {
ContributorsGraph.set_y_domain([{commits: 30}])
expect(ContributorsGraph.prototype.y_domain).toEqual([0, 30])
})
})
describe("#init_x_domain", function () {
it("sets the initial x_domain", function () {
ContributorsGraph.init_x_domain([{date: "2013-01-31"}, {date: "2012-01-31"}])
expect(ContributorsGraph.prototype.x_domain).toEqual(["2012-01-31", "2013-01-31"])
})
})
describe("#init_y_domain", function () {
it("sets the initial y_domain", function () {
ContributorsGraph.init_y_domain([{commits: 30}])
expect(ContributorsGraph.prototype.y_domain).toEqual([0, 30])
})
})
describe("#init_domain", function () {
it("calls init_x_domain and init_y_domain", function () {
spyOn(ContributorsGraph, "init_x_domain")
spyOn(ContributorsGraph, "init_y_domain")
ContributorsGraph.init_domain()
expect(ContributorsGraph.init_x_domain).toHaveBeenCalled()
expect(ContributorsGraph.init_y_domain).toHaveBeenCalled()
})
})
describe("#set_dates", function () {
it("sets the dates", function () {
ContributorsGraph.set_dates("2013-12-01")
expect(ContributorsGraph.prototype.dates).toEqual("2013-12-01")
})
})
describe("#set_x_domain", function () {
it("sets the instance's x domain using the prototype's x_domain", function () {
ContributorsGraph.prototype.x_domain = 20
var instance = new ContributorsGraph()
instance.x = d3.time.scale().range([0, 100]).clamp(true)
spyOn(instance.x, 'domain')
instance.set_x_domain()
expect(instance.x.domain).toHaveBeenCalledWith(20)
})
})
describe("#set_y_domain", function () {
it("sets the instance's y domain using the prototype's y_domain", function () {
ContributorsGraph.prototype.y_domain = 30
var instance = new ContributorsGraph()
instance.y = d3.scale.linear().range([100, 0]).nice()
spyOn(instance.y, 'domain')
instance.set_y_domain()
expect(instance.y.domain).toHaveBeenCalledWith(30)
})
})
describe("#set_domain", function () {
it("calls set_x_domain and set_y_domain", function () {
var instance = new ContributorsGraph()
spyOn(instance, 'set_x_domain')
spyOn(instance, 'set_y_domain')
instance.set_domain()
expect(instance.set_x_domain).toHaveBeenCalled()
expect(instance.set_y_domain).toHaveBeenCalled()
})
})
describe("#set_data", function () {
it("sets the data", function () {
var instance = new ContributorsGraph()
instance.set_data("20")
expect(instance.data).toEqual("20")
})
})
})
describe("ContributorsMasterGraph", function () {
describe("#process_dates", function () {
it("gets and parses dates", function () {
var graph = new ContributorsMasterGraph()
var data = 'random data here'
spyOn(graph, 'parse_dates')
spyOn(graph, 'get_dates').andReturn("get")
spyOn(ContributorsGraph,'set_dates').andCallThrough()
graph.process_dates(data)
expect(graph.parse_dates).toHaveBeenCalledWith(data)
expect(graph.get_dates).toHaveBeenCalledWith(data)
expect(ContributorsGraph.set_dates).toHaveBeenCalledWith("get")
})
})
describe("#get_dates", function () {
it("plucks the date field from data collection", function () {
var graph = new ContributorsMasterGraph()
var data = [{date: "2013-01-01"}, {date: "2012-12-15"}]
expect(graph.get_dates(data)).toEqual(["2013-01-01", "2012-12-15"])
})
})
describe("#parse_dates", function () {
it("parses the dates", function () {
var graph = new ContributorsMasterGraph()
var parseDate = d3.time.format("%Y-%m-%d").parse
var data = [{date: "2013-01-01"}, {date: "2012-12-15"}]
var correct = [{date: parseDate(data[0].date)}, {date: parseDate(data[1].date)}]
graph.parse_dates(data)
expect(data).toEqual(correct)
})
})
})
describe("ContributorsStatGraphUtil", function () {
describe("#parse_log", function () {
it("returns a correctly parsed log", function () {
var fake_log = [
{author: "Karlo Soriano", date: "2013-05-09", additions: 471},
{author: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 6, deletions: 1},
{author: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 19, deletions: 3},
{author: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 29, deletions: 3}]
var correct_parsed_log = {
total: [
{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
{date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
by_author:
[
{
author: "Karlo Soriano",
"2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
},
{
author: "Dmitriy Zaporozhets",
"2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}
}
]
}
expect(ContributorsStatGraphUtil.parse_log(fake_log)).toEqual(correct_parsed_log)
})
})
describe("#store_data", function () {
var fake_entry = {author: "Karlo Soriano", date: "2013-05-09", additions: 471}
var fake_total = {}
var fake_by_author = {}
it("calls #store_commits", function () {
spyOn(ContributorsStatGraphUtil, 'store_commits')
ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author)
expect(ContributorsStatGraphUtil.store_commits).toHaveBeenCalled()
})
it("calls #store_additions", function () {
spyOn(ContributorsStatGraphUtil, 'store_additions')
ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author)
expect(ContributorsStatGraphUtil.store_additions).toHaveBeenCalled()
})
it("calls #store_deletions", function () {
spyOn(ContributorsStatGraphUtil, 'store_deletions')
ContributorsStatGraphUtil.store_data(fake_entry, fake_total, fake_by_author)
expect(ContributorsStatGraphUtil.store_deletions).toHaveBeenCalled()
})
})
describe("#store_commits", function () {
var fake_total = "fake_total"
var fake_by_author = "fake_by_author"
it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
spyOn(ContributorsStatGraphUtil, 'add')
ContributorsStatGraphUtil.store_commits(fake_total, fake_by_author)
expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "commits", 1], ["fake_by_author", "commits", 1]])
})
})
describe("#add", function () {
it("adds 1 to current test_field in collection", function () {
var fake_collection = {test_field: 10}
ContributorsStatGraphUtil.add(fake_collection, "test_field", 1)
expect(fake_collection.test_field).toEqual(11)
})
it("inits and adds 1 if test_field in collection is not defined", function () {
var fake_collection = {}
ContributorsStatGraphUtil.add(fake_collection, "test_field", 1)
expect(fake_collection.test_field).toEqual(1)
})
})
describe("#store_additions", function () {
var fake_entry = {additions: 10}
var fake_total= "fake_total"
var fake_by_author = "fake_by_author"
it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
spyOn(ContributorsStatGraphUtil, 'add')
ContributorsStatGraphUtil.store_additions(fake_entry, fake_total, fake_by_author)
expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "additions", 10], ["fake_by_author", "additions", 10]])
})
})
describe("#store_deletions", function () {
var fake_entry = {deletions: 10}
var fake_total= "fake_total"
var fake_by_author = "fake_by_author"
it("calls #add twice with arguments fake_total and fake_by_author respectively", function () {
spyOn(ContributorsStatGraphUtil, 'add')
ContributorsStatGraphUtil.store_deletions(fake_entry, fake_total, fake_by_author)
expect(ContributorsStatGraphUtil.add.argsForCall).toEqual([["fake_total", "deletions", 10], ["fake_by_author", "deletions", 10]])
})
})
describe("#add_date", function () {
it("adds a date field to the collection", function () {
var fake_date = "2013-10-02"
var fake_collection = {}
ContributorsStatGraphUtil.add_date(fake_date, fake_collection)
expect(fake_collection[fake_date].date).toEqual("2013-10-02")
})
})
describe("#add_author", function () {
it("adds an author field to the collection", function () {
var fake_author = "Author"
var fake_collection = {}
ContributorsStatGraphUtil.add_author(fake_author, fake_collection)
expect(fake_collection[fake_author].author).toEqual("Author")
})
})
describe("#get_total_data", function () {
it("returns the collection sorted via specified field", function () {
var fake_parsed_log = {
total: [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
{date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
by_author:[
{
author: "Karlo Soriano",
"2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
},
{
author: "Dmitriy Zaporozhets",
"2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}
}
]};
var correct_total_data = [{date: "2013-05-08", commits: 3},
{date: "2013-05-09", commits: 1}];
expect(ContributorsStatGraphUtil.get_total_data(fake_parsed_log, "commits")).toEqual(correct_total_data)
})
})
describe("#pick_field", function () {
it("returns the collection with only the specified field and date", function () {
var fake_parsed_log_total = [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
{date: "2013-05-08", additions: 54, deletions: 7, commits: 3}];
ContributorsStatGraphUtil.pick_field(fake_parsed_log_total, "commits")
var correct_pick_field_data = [{date: "2013-05-09", commits: 1},{date: "2013-05-08", commits: 3}];
expect(ContributorsStatGraphUtil.pick_field(fake_parsed_log_total, "commits")).toEqual(correct_pick_field_data)
})
})
describe("#get_author_data", function () {
it("returns the log by author sorted by specified field", function () {
var fake_parsed_log = {
total: [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
{date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
by_author:[
{
author: "Karlo Soriano",
"2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
},
{
author: "Dmitriy Zaporozhets",
"2013-05-08": {date: "2013-05-08", additions: 54, deletions: 7, commits: 3}
}
]}
var correct_author_data = [{author:"Dmitriy Zaporozhets",dates:{"2013-05-08":3},deletions:7,additions:54,"commits":3},
{author:"Karlo Soriano",dates:{"2013-05-09":1},deletions:0,additions:471,commits:1}]
expect(ContributorsStatGraphUtil.get_author_data(fake_parsed_log, "commits")).toEqual(correct_author_data)
})
})
describe("#parse_log_entry", function () {
it("adds the corresponding info from the log entry to the author", function () {
var fake_log_entry = { author: "Karlo Soriano",
"2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
}
var correct_parsed_log = {author:"Karlo Soriano",dates:{"2013-05-09":1},deletions:0,additions:471,commits:1}
expect(ContributorsStatGraphUtil.parse_log_entry(fake_log_entry, 'commits', null)).toEqual(correct_parsed_log)
})
})
describe("#in_range", function () {
var date = "2013-05-09"
it("returns true if date_range is null", function () {
expect(ContributorsStatGraphUtil.in_range(date, null)).toEqual(true)
})
it("returns true if date is in range", function () {
var date_range = [new Date("2013-01-01"), new Date("2013-12-12")]
expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(true)
})
it("returns false if date is not in range", function () {
var date_range = [new Date("1999-12-01"), new Date("2000-12-01")]
expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(false)
})
})
})
\ No newline at end of file
describe("StatGraph", function () {
describe("#get_log", function () {
it("returns log", function () {
StatGraph.log = "test";
expect(StatGraph.get_log()).toBe("test");
});
});
describe("#set_log", function () {
it("sets the log", function () {
StatGraph.set_log("test");
expect(StatGraph.log).toBe("test");
})
})
});
\ No newline at end of file
# src_files
#
# Return an array of filepaths relative to src_dir to include before jasmine specs.
# Default: []
#
# EXAMPLE:
#
# src_files:
# - lib/source1.js
# - lib/source2.js
# - dist/**/*.js
#
src_files:
- assets/application.js
# stylesheets
#
# Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs.
# Default: []
#
# EXAMPLE:
#
# stylesheets:
# - css/style.css
# - stylesheets/*.css
#
stylesheets:
- stylesheets/**/*.css
# helpers
#
# Return an array of filepaths relative to spec_dir to include before jasmine specs.
# Default: ["helpers/**/*.js"]
#
# EXAMPLE:
#
# helpers:
# - helpers/**/*.js
#
helpers:
- helpers/**/*.js
# spec_files
#
# Return an array of filepaths relative to spec_dir to include.
# Default: ["**/*[sS]pec.js"]
#
# EXAMPLE:
#
# spec_files:
# - **/*[sS]pec.js
#
spec_files:
- '**/*[sS]pec.js'
# src_dir
#
# Source directory path. Your src_files must be returned relative to this path. Will use root if left blank.
# Default: project root
#
# EXAMPLE:
#
# src_dir: public
#
src_dir:
# spec_dir
#
# Spec directory path. Your spec_files must be returned relative to this path.
# Default: spec/javascripts
#
# EXAMPLE:
#
# spec_dir: spec/javascripts
#
spec_dir: spec/javascripts
#Use this file to set/override Jasmine configuration options
#You can remove it if you don't need it.
#This file is loaded *after* jasmine.yml is interpreted.
#
#Example: using a different boot file.
#Jasmine.configure do |config|
# @config.boot_dir = '/absolute/path/to/boot_dir'
# @config.boot_files = lambda { ['/absolute/path/to/boot_dir/file.js'] }
#end
#
require 'spec_helper'
require 'gitlab/git_stats_log_parser'
describe LogParser do
describe "#self.parse_log" do
context "log_from_git is a valid log" do
it "returns the correct log" do
fake_log = "Karlo Soriano
2013-05-09
14 files changed, 471 insertions(+)
Dmitriy Zaporozhets
2013-05-08
1 file changed, 6 insertions(+), 1 deletion(-)
Dmitriy Zaporozhets
2013-05-08
6 files changed, 19 insertions(+), 3 deletions(-)
Dmitriy Zaporozhets
2013-05-08
3 files changed, 29 insertions(+), 3 deletions(-)";
lp = LogParser.parse_log(fake_log)
lp.should eq([
{author: "Karlo Soriano", date: "2013-05-09", additions: 471},
{author: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 6, deletions: 1},
{author: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 19, deletions: 3},
{author: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 29, deletions: 3}])
end
end
end
end
\ No newline at end of file
require 'spec_helper'
describe Gitlab::GitStats do
describe "#parsed_log" do
let(:stats) { Gitlab::GitStats.new(nil, nil) }
before(:each) do
stats.stub(:log).and_return("anything")
end
context "LogParser#parse_log returns 'test'" do
it "returns 'test'" do
LogParser.stub(:parse_log).and_return("test")
stats.parsed_log.should eq("test")
end
end
end
describe "#log" do
let(:repo) { Repository.new(nil, nil) }
let(:gs) { Gitlab::GitStats.new(repo.raw, repo.root_ref) }
before(:each) do
repo.stub(:raw).and_return(nil)
repo.stub(:root_ref).and_return(nil)
repo.raw.stub(:git)
end
context "repo.git.run returns 'test'" do
it "returns 'test'" do
repo.raw.git.stub(:run).and_return("test")
gs.log.should eq("test")
end
end
end
end
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment