Adds ideal progress to progress graph
This commit is contained in:
Juan Salvador Pérez García
2018-07-10 10:16:54 +02:00
parent 6e75f5b494
commit 762c4faef6
11 changed files with 660 additions and 6 deletions

View File

@@ -18,6 +18,7 @@ gem 'delayed_job_active_record', '~> 4.1.0'
gem 'devise', '~> 3.5.7'
gem 'devise-async', '~> 0.10.2'
gem 'devise_security_extension', '~> 0.10.0'
gem 'faker', '~> 1.8.7'
gem 'foundation-rails', '~> 6.4.3.0'
gem 'foundation_rails_helper', '~> 2.0.0'
gem 'graphiql-rails', '~> 1.4.1'
@@ -64,7 +65,6 @@ group :development, :test do
gem 'bullet', '~> 5.7.0'
gem 'byebug', '~> 10.0.0'
gem 'factory_bot_rails', '~> 4.8.2'
gem 'faker', '~> 1.8.7'
gem 'i18n-tasks', '~> 0.9.20'
gem 'knapsack_pro', '~> 0.53.0'
gem 'launchy', '~> 2.4.3'

View File

@@ -9,6 +9,8 @@ CKEDITOR.editorConfig = function( config )
// config.language = 'fr';
// config.uiColor = '#AADC6E';
config.forcePasteAsPlainText = true;
/* Filebrowser routes */
// The location of an external file browser, that should be launched when "Browse Server" button is pressed.
config.filebrowserBrowseUrl = "/ckeditor/attachment_files";

View File

@@ -7,20 +7,27 @@
var ProposalGraph = function(url) {
this.url = url;
this.successfulProposalDataUrl = null;
this.proposalAchievementsUrl = null;
this.targetId = null;
this.groupBy = null;
this.proposalSuccess = null;
this.progressLabel = 'Progress';
this.supportsLabel = 'Supports';
this.successLabel = 'Success';
this.chart = null;
this.goals = null;
this.achievements = null;
this.xColumnValues = null;
this.succesfulColumnValues = null;
this.progressColumnValues = null;
};
ProposalGraph.prototype.refresh = function() {
this.refreshGoals()
.then(this.refreshData.bind(this))
.then(this.refreshSuccessfulData.bind(this))
.then(this.refreshAchievements.bind(this))
.done(this.draw.bind(this));
};
@@ -62,7 +69,7 @@
ProposalGraph.prototype.parseData = function(data) {
var key;
this.xColumnValues = [ 'x' ];
this.xColumnValues = [ ];
this.progressColumnValues = [ this.progressLabel ];
for (key in data) {
@@ -73,14 +80,78 @@
}
};
ProposalGraph.prototype.refreshSuccessfulData = function() {
return $.ajax({
url: this.successfulProposalDataUrl,
cache: false,
success: function (data) {
this.parseSuccessfulProposalData(data);
}.bind(this),
data: {
group_by: this.groupBy
}
});
};
ProposalGraph.prototype.parseSuccessfulProposalData = function(data) {
var key;
this.successfulColumnValues = [ this.successLabel ];
for (key in data) {
if (data.hasOwnProperty(key)) {
this.addXColumnValue(key);
this.successfulColumnValues.push(data[key]);
}
}
};
ProposalGraph.prototype.refreshAchievements = function() {
return $.ajax({
url: this.proposalAchievementsUrl,
cache: false,
success: function (data) {
this.parseAchievements(data);
}.bind(this),
data: {
group_by: this.groupBy
}
});
};
ProposalGraph.prototype.parseAchievements = function(data) {
var group;
this.achievements = [];
for (group in data) {
if (data.hasOwnProperty(group)) {
this.addXColumnValue(group);
this.achievements.push({
value: group,
text: data[group][data[group].length - 1].title
});
}
}
};
ProposalGraph.prototype.addXColumnValue = function (value) {
if (this.xColumnValues.indexOf(value) === -1) {
this.xColumnValues.push(value);
}
}
ProposalGraph.prototype.draw = function(data) {
this.xColumnValues = this.xColumnValues.sort();
this.xColumnValues.unshift('x');
this.chart = c3.generate({
bindto: '#' + this.targetId,
data: {
x: 'x',
columns: [
this.xColumnValues,
this.progressColumnValues
this.progressColumnValues,
this.successfulColumnValues
]
},
axis: {
@@ -93,13 +164,25 @@
}
},
x: {
type: 'category'
type: 'category',
tick: {
fit: true,
culling: {
max: 15
}
}
}
},
grid: {
y: {
lines: this.goals
},
x: {
lines: this.achievements
}
},
legend: {
position: 'right'
}
});
};
@@ -107,10 +190,13 @@
$(document).ready(function () {
$('[data-proposal-graph-url]').each(function () {
var graph = new ProposalGraph($(this).data('proposal-graph-url'));
graph.successfulProposalDataUrl = $(this).data('successful-proposal-graph-url');
graph.proposalAchievementsUrl = $(this).data('proposal-achievements-url');
graph.targetId = $(this).attr('id');
graph.groupBy = $(this).data('proposal-graph-group-by');
graph.progressLabel = $(this).data('proposal-graph-progress-label');
graph.supportsLabel = $(this).data('proposal-graph-supports-label');
graph.successLabel = $(this).data('proposal-graph-success-label');
graph.proposalSuccess = parseInt($(this).data('proposal-success'), 10);
graph.refresh();

View File

@@ -52,6 +52,16 @@ class ProposalsDashboardController < Dashboard::BaseController
render json: ProposalSupportsQuery.for(params)
end
def successful_supports
authorize! :dashboard, proposal
render json: SuccessfulProposalSupportsQuery.for(params)
end
def achievements
authorize! :dashboard, proposal
render json: ProposalAchievementsQuery.for(params)
end
private
def proposal_dashboard_action

View File

@@ -0,0 +1,59 @@
class ProposalAchievementsQuery
attr_reader :params
def self.for(params)
query = ProposalAchievementsQuery.new params
query.results
end
def initialize(params)
@params = params
end
def results
grouped_results = groups
grouped_results.each do |key, achievements|
grouped_results[key] = []
achievements.each do |achievement|
grouped_results[key] << {
executed_at: achievements.last.executed_at,
title: achievements.last.proposal_dashboard_action.title
}
end
end
grouped_results
end
private
def groups
return achievements.group_by { |v| v.executed_at.to_date.year } if params[:group_by] == 'year'
return achievements.group_by { |v| "#{v.executed_at.to_date.cweek}/#{v.executed_at.to_date.year}" } if params[:group_by] == 'week'
return achievements.group_by { |v| "#{v.executed_at.to_date.year}-#{v.executed_at.to_date.month}" } if params[:group_by] == 'month'
achievements.group_by { |a| a.executed_at.to_date }
end
def achievements
ProposalExecutedDashboardAction
.includes(:proposal_dashboard_action)
.where(proposal: proposal, executed_at: start_date.beginning_of_day..end_date.end_of_day)
.order(executed_at: :asc)
end
def proposal
@proposal ||= Proposal.find(params[:proposal_id])
end
def start_date
return Date.parse(params[:start_date]) unless params[:start_date].blank?
proposal.created_at.to_date
end
def end_date
return Date.parse(params[:end_date]) unless params[:end_date].blank?
Date.today
end
end

View File

@@ -0,0 +1,72 @@
class SuccessfulProposalSupportsQuery
attr_reader :params
def self.for(params)
query = SuccessfulProposalSupportsQuery.new params
query.results
end
def initialize(params)
@params = params
end
def results
grouped_votes = groups
grouped_votes.each do |group, votes|
grouped_votes[group] = votes.inject(0) { |sum, vote| sum + vote.vote_weight }
end
accumulated = 0
grouped_votes.each do |k, v|
accumulated += v
grouped_votes[k] = accumulated
end
grouped_votes
end
private
def groups
return votes.group_by { |v| v.voted_at.to_date.year } if params[:group_by] == 'year'
return votes.group_by { |v| "#{v.voted_at.to_date.cweek}/#{v.voted_at.to_date.year}" } if params[:group_by] == 'week'
return votes.group_by { |v| "#{v.voted_at.to_date.year}-#{v.voted_at.to_date.month}" } if params[:group_by] == 'month'
votes.group_by { |v| v.voted_at.to_date }
end
def votes
return [] if successful_proposal.nil?
Vote
.select("created_at + interval '#{days_diff} day' voted_at, *")
.where(votable: successful_proposal)
.where("created_at + interval '#{days_diff} day' between ? and ?", start_date.beginning_of_day, end_date.end_of_day)
.order(created_at: :asc)
end
def proposal
@proposal ||= Proposal.find(params[:proposal_id])
end
def successful_proposal
@successful_proposal ||= Proposal.find_by(id: Setting['proposals.successful_proposal_id'])
end
def days_diff
return 0 if successful_proposal.nil?
return 0 if proposal.published_at.nil?
(proposal.published_at.to_date - successful_proposal.published_at.to_date).to_i
end
def start_date
return Date.parse(params[:start_date]) unless params[:start_date].blank?
proposal.created_at.to_date
end
def end_date
return Date.parse(params[:end_date]) unless params[:end_date].blank?
Date.today
end
end

View File

@@ -13,9 +13,12 @@
<div class="small-12 column">
<div id="proposal-graph"
data-proposal-graph-url="<%= supports_proposal_dashboard_index_path(proposal, format: :json) %>"
data-successful-proposal-graph-url="<%= successful_supports_proposal_dashboard_index_path(proposal, format: :json) %>"
data-proposal-achievements-url="<%= achievements_proposal_dashboard_index_path(proposal, format: :json) %>"
data-proposal-graph-group-by="<%= params[:group_by] %>"
data-proposal-graph-progress-label="<%= t '.progress' %>"
data-proposal-graph-supports-label="<%= t '.supports' %>"
data-proposal-graph-progress-label="<%= t('.progress') %>"
data-proposal-graph-supports-label="<%= t('.supports') %>"
data-proposal-graph-success-label="<%= t('.success') %>"
data-proposal-success="<%= Setting["votes_for_proposal_success"] %>"
class="c3"
style="max-height: 320px; position: relative;"></div>

View File

@@ -520,6 +520,7 @@ en:
group_by_date: Daily
progress: Acumulated progress
supports: Supports
success: Ideal progress
index:
title: Edition
edit_proposal_link: Edit

View File

@@ -520,6 +520,7 @@ es:
group_by_date: Diario
progress: Progreso acumulado
supports: Apoyos
success: Progreso ideal
index:
title: Edición
edit_proposal_link: Editar propuesta

View File

@@ -7,7 +7,9 @@ resources :proposals do
collection do
patch :publish
get :supports
get :successful_supports
get :progress
get :achievements
end
member do

View File

@@ -17,4 +17,422 @@ namespace :proposal_actions do
task initialize_successful_proposal_id: :environment do
Setting['proposals.successful_proposal_id'] = nil
end
desc 'Simulate successful proposal'
task create_successful_proposal: :environment do
expected_supports = [
1049,
596,
273,
208,
97,
74,
148,
116,
83,
62,
42,
20,
36,
40,
44,
38,
45,
17,
15,
15,
10,
28,
22,
32,
26,
15,
16,
21,
26,
25,
14,
12,
11,
18,
27,
27,
22,
119,
103,
65,
79,
140,
96,
102,
96,
65,
42,
39,
108,
380,
424,
302,
233,
98,
88,
78,
149,
202,
137,
135,
48,
52,
90,
47,
120,
83,
55,
29,
38,
51,
64,
105,
27,
17,
8,
13,
16,
118,
105,
69,
136,
85,
50,
32,
32,
34,
38,
24,
23,
34,
16,
41,
22,
13,
17,
44,
98,
52,
42,
38,
12,
7,
14,
14,
25,
20,
21,
10,
10,
11,
22,
44,
28,
9,
35,
30,
24,
22,
91,
41,
34,
42,
23,
21,
18,
18,
19,
21,
58,
31,
30,
24,
38,
32,
20,
372,
520,
178,
85,
150,
562,
212,
110,
50,
49,
53,
69,
134,
78,
42,
62,
76,
141,
101,
196,
209,
196,
211,
165,
181,
361,
736,
325,
194,
194,
126,
122,
143,
186,
339,
169,
97,
125,
120,
152,
88,
27,
45,
23,
35,
39,
53,
40,
23,
26,
22,
20,
30,
18,
22,
15,
50,
42,
23,
11,
94,
113,
115,
122,
159,
184,
173,
211,
161,
144,
115,
99,
80,
77,
123,
355,
338,
226,
201,
70,
47,
117,
116,
61,
79,
284,
607,
565,
541,
347,
265,
204,
158,
127,
110,
173,
137,
92,
135,
95,
104,
131,
106,
103,
85,
81,
46,
58,
88,
108,
85,
78,
52,
39,
21,
33,
50,
57,
53,
32,
263,
162,
89,
142,
70,
48,
39,
26,
19,
25,
24,
36,
48,
48,
26,
19,
40,
1916,
535,
214,
106,
73,
50,
42,
62,
54,
54,
82,
124,
112,
104,
328,
256,
309,
547,
68,
27,
41,
55,
55,
37,
32,
29,
14,
18,
23,
21,
18,
11,
10,
16,
12,
49,
74,
230,
110,
63,
17,
14,
26,
300,
137,
45,
25,
7,
6,
19,
12,
7,
53,
53,
14,
14,
17,
10,
8,
6,
5,
7,
5,
3,
5,
5,
4,
4,
3,
1,
4,
7,
7,
5,
6,
3,
3,
8,
6,
6,
4,
7,
4,
5,
9,
5,
1,
3,
4,
1,
2,
5,
4,
3,
5
]
tags = Faker::Lorem.words(25)
author = User.all.sample
description = "<p>#{Faker::Lorem.paragraphs.join('</p><p>')}</p>"
proposal = Proposal.create!(author: author,
title: Faker::Lorem.sentence(3).truncate(60),
question: Faker::Lorem.sentence(3) + "?",
summary: Faker::Lorem.sentence(3),
responsible_name: Faker::Name.name,
external_url: Faker::Internet.url,
description: description,
created_at: Time.now - expected_supports.length.days,
tag_list: tags.sample(3).join(','),
geozone: Geozone.all.sample,
skip_map: "1",
terms_of_service: "1",
published_at: Time.now - expected_supports.length.days)
expected_supports.each_with_index do |supports, day_offset|
supports.times do |i|
user = User.create!(
username: "user_#{proposal.id}_#{day_offset}_#{i}",
email: "user_#{proposal.id}_#{day_offset}_#{i}@consul.dev",
password: '12345678',
password_confirmation: '12345678',
confirmed_at: Time.current - expected_supports.length.days,
terms_of_service: '1',
gender: ['Male', 'Female'].sample,
date_of_birth: rand((Time.current - 80.years)..(Time.current - 16.years)),
public_activity: (rand(1..100) > 30)
)
Vote.create!(
votable: proposal,
voter: user,
vote_flag: false,
vote_weight: 1,
created_at: proposal.published_at + day_offset.days,
updated_at: proposal.published_at + day_offset.days
)
end
end
Setting['proposals.successful_proposal_id'] = proposal.id
end
end