diff --git a/app/javascript/views/usage/index.js b/app/javascript/views/usage/index.js index edf7949..c611930 100644 --- a/app/javascript/views/usage/index.js +++ b/app/javascript/views/usage/index.js @@ -161,15 +161,18 @@ }; const yAxisLabel = date => moment(date).format('MMM-YY'); - const drawHorizontalBar = (canvasSelector, data) => { + const drawHorizontalBar = (canvasSelector, data, aspectRatio = 1) => { const chart = new Chart(canvasSelector, { // eslint-disable-line no-new type: 'horizontalBar', data, options: { + responsive: true, + maintainAspectRatio: true, + aspectRatio, scales: { xAxes: [{ - ticks: { beginAtZero: true }, - precision: 1, + position: 'top', + ticks: { beginAtZero: true, stepSize: 10 }, }], }, }, @@ -195,7 +198,7 @@ return { labels, datasets }; }; - const fetch = (lastDayOfMonth) => { + const fetch = (lastDayOfMonth, aspectRatio = 1) => { const baseUrl = $('select[name="monthly_plans_by_template"]').attr('data-url'); $.ajax({ url: `${baseUrl}?start_date=${lastDayOfMonth}`, @@ -205,14 +208,52 @@ if (drawnChart) { drawnChart.destroy(); } - drawnChart = drawHorizontalBar($(canvasSelector), chartData); + drawnChart = drawHorizontalBar($(canvasSelector), chartData, aspectRatio); }); }; + // Set Aspect Rate (width of X-axis/height of Y-axis) based on + // choice of selectedLastDayOfMonth in Time picker string value. Note aspect + const getAspectRatio = (selectedLastDayOfMonth) => { + let aspectRatio; + try { + const now = new Date(); + const dateOfSelectedMonth = new Date(selectedLastDayOfMonth); + const diff = new Date(now.getTime() - dateOfSelectedMonth.getTime()); + const diffInMonths = diff.getUTCMonth(); + + switch (diffInMonths) { + case 0: + case 1: + case 2: + case 3: + aspectRatio = 4; + break; + case 4: + case 5: + aspectRatio = 3; + break; + case 7: + case 8: + case 9: + aspectRatio = 2; + break; + default: + aspectRatio = 1; + } + } catch (e) { + aspectRatio = 1; + } + + return aspectRatio; + }; + const handler = () => { const selectedMonth = jQuerySelectorSelect.val(); + if (selectedMonth) { - fetch(selectedMonth); + const aspectRatio = getAspectRatio(selectedMonth); + fetch(selectedMonth, aspectRatio); } }; diff --git a/app/models/stat_created_plan.rb b/app/models/stat_created_plan.rb index 0f0bd35..4e53896 100644 --- a/app/models/stat_created_plan.rb +++ b/app/models/stat_created_plan.rb @@ -50,7 +50,7 @@ end.call(created_plans) data = created_plans.map do |created_plan| - tuple = { date: created_plan.date } + tuple = { Date: created_plan.date.strftime("%b %Y") } template_names.reduce(tuple) do |acc, name| acc[name] = 0 acc @@ -58,11 +58,10 @@ created_plan.details&.fetch("by_template", [])&.each do |name_count| tuple[name_count.fetch("name")] = name_count.fetch("count") end - tuple[:count] = created_plan.count + tuple[:Count] = created_plan.count tuple end - - Csvable.from_array_of_hashes(data) + Csvable.from_array_of_hashes(data, false) end end diff --git a/app/views/usage/index.html.erb b/app/views/usage/index.html.erb index ef83ac4..a013c34 100644 --- a/app/views/usage/index.html.erb +++ b/app/views/usage/index.html.erb @@ -132,13 +132,13 @@ -
+

<%= _('No. plans by template') %>

-
+
  • @@ -161,8 +161,9 @@
-
- +
+ +
diff --git a/lib/csvable.rb b/lib/csvable.rb index 867a145..d9ca3ac 100644 --- a/lib/csvable.rb +++ b/lib/csvable.rb @@ -5,11 +5,17 @@ require "csv" class << self - def from_array_of_hashes(data = []) + def from_array_of_hashes(data = [], humanize = true) return "" unless data.first&.keys - headers = data.first.keys - .map(&:to_s) - .map(&:humanize) + if humanize + headers = data.first.keys + .map(&:to_s) + .map(&:humanize) + else + headers = data.first.keys + .map(&:to_s) + end + CSV.generate do |csv| csv << headers data.each do |row| diff --git a/spec/models/stat_created_plan_spec.rb b/spec/models/stat_created_plan_spec.rb index d1a0a9c..3327df4 100644 --- a/spec/models/stat_created_plan_spec.rb +++ b/spec/models/stat_created_plan_spec.rb @@ -1,21 +1,27 @@ -require 'rails_helper' +# frozen_string_literal: true + +require "rails_helper" RSpec.describe StatCreatedPlan, type: :model do - describe '.to_csv' do - context 'when no instances' do - it 'returns empty' do + describe ".to_csv" do + context "when no instances" do + it "returns empty" do csv = described_class.to_csv([]) expect(csv).to be_empty end end - context 'when instances' do + context "when instances" do let(:org) { FactoryBot.create(:org) } - context 'when no details' do - it 'returns counts in a comma-separated row' do - may = FactoryBot.create(:stat_created_plan, date: Date.new(2018, 05, 31), org: org, count: 20) - june = FactoryBot.create(:stat_created_plan, date: Date.new(2018, 06, 30), org: org, count: 10) + context "when no details" do + it "returns counts in a comma-separated row" do + may = FactoryBot.create(:stat_created_plan, + date: Date.new(2018, 05, 31), + org: org, count: 20) + june = FactoryBot.create(:stat_created_plan, + date: Date.new(2018, 06, 30), + org: org, count: 10) data = [may, june] csv = described_class.to_csv(data) @@ -29,26 +35,34 @@ end end - context 'when details by template is true' do - it 'returns counts by_template in a comma-separated row' do - may = FactoryBot.create(:stat_created_plan, date: Date.new(2018, 05, 31), org: org, count: 20, details: { by_template: [ - { name: 'Template1', count: 5 }, - { name: 'Template2', count: 15 } - ]}) - june = FactoryBot.create(:stat_created_plan, date: Date.new(2018, 06, 30), org: org, count: 10, details: { by_template: [ - { name: 'Template1', count: 2 }, - { name: 'Template3', count: 8 } - ]}) - july = FactoryBot.create(:stat_created_plan, date: Date.new(2018, 07, 31), org: org, count: 0) + context "when details by template is true" do + it "returns counts by_template in a comma-separated row" do + may = FactoryBot.create(:stat_created_plan, + date: Date.new(2018, 05, 31), + org: org, + count: 20, + details: { by_template: [ + { name: "Template1", count: 5 }, + { name: "Template2", count: 15 }] }) + june = FactoryBot.create(:stat_created_plan, + date: Date.new(2018, 06, 30), + org: org, count: 10, + details: { by_template: [ + { name: "Template1", count: 2 }, + { name: "Template3", count: 8 }] }) + july = FactoryBot.create(:stat_created_plan, + date: Date.new(2018, 07, 31), + org: org, + count: 0) data = [may, june, july] csv = described_class.to_csv(data, details: { by_template: true }) expected_csv = <<~HERE Date,Template1,Template2,Template3,Count - 2018-05-31,5,15,0,20 - 2018-06-30,2,0,8,10 - 2018-07-31,0,0,0,0 + May 2018,5,15,0,20 + Jun 2018,2,0,8,10 + Jul 2018,0,0,0,0 HERE expect(csv).to eq(expected_csv) end @@ -56,19 +70,26 @@ end end - describe '.serialize' do - let(:org) { FactoryBot.create(:org, name: 'An Org', contact_email: 'foo@bar.com', contact_name: 'Foo') } + describe ".serialize" do + let(:org) { FactoryBot.create(:org, + name: "An Org", + contact_email: "foo@bar.com", + contact_name: "Foo") } let(:details) do - { 'by_template' => [ - { 'name' => 'Template 1', 'count' => 10 }, - { 'name' => 'Template 2', 'count' => 10 } - ]} + { "by_template" => [ + { "name" => "Template 1", "count" => 10 }, + { "name" => "Template 2", "count" => 10 }] + } end - it 'retrieves JSON details as a hash object' do - september = FactoryBot.create(:stat_created_plan, date: '2018-09-30', org: org, count: 20, details: details) + it "retrieves JSON details as a hash object" do + september = FactoryBot.create(:stat_created_plan, + date: "2018-09-30", + org: org, + count: 20, + details: details) - json_details = described_class.find_by_date('2018-09-30').details + json_details = described_class.find_by_date("2018-09-30").details expect(json_details).to eq(details) end diff --git a/spec/requests/stat_created_plans_by_template_controller_spec.rb b/spec/requests/stat_created_plans_by_template_controller_spec.rb index e8d9ce4..631d7f2 100644 --- a/spec/requests/stat_created_plans_by_template_controller_spec.rb +++ b/spec/requests/stat_created_plans_by_template_controller_spec.rb @@ -1,101 +1,127 @@ -require 'rails_helper' +# frozen_string_literal: true -RSpec.describe '/stat_created_plan_by_template', type: :request do +require "rails_helper" + +RSpec.describe "/stat_created_plan_by_template", type: :request do def parsed_response JSON.parse(response.body, symbolize_names: true) end - describe '#index' do - let(:path) { '/stat_created_plans_by_template' } + describe "#index" do + let(:path) { "/stat_created_plans_by_template" } - it 'redirects when non-authorized user' do + it "redirects when non-authorized user" do get path expect(response).to have_http_status(:redirect) end - context 'when org_admin user' do + context "when org_admin user" do let(:org) { create(:org) } let(:org_admin) { create(:user, :org_admin, org: org) } before(:each) do sign_in(org_admin) end - it 'returns 200 status' do + it "returns 200 status" do get path - expect(response.content_type).to eq('application/json') + expect(response.content_type).to eq("application/json") expect(response).to have_http_status(:ok) end - context 'when there are no stats' do - it 'returns empty' do + context "when there are no stats" do + it "returns empty" do get path expect(parsed_response).to eq([]) end - it 'returns empty csv file' do + it "returns empty csv file" do get "#{path}.csv" - expect(response.content_type).to eq('text/csv') - expect(response.body).to eq('') + expect(response.content_type).to eq("text/csv") + expect(response.body).to eq("") end end context "when there are stats" do before do - create(:stat_created_plan, date: '2018-07-31', count: 5, org: org, details: { by_template: [{ name: 'Template1', count: 3 }, { name: 'Template2', count: 2 }]}) - create(:stat_created_plan, date: '2018-08-31', count: 10, org: org, details: { by_template: [{ name: 'Template1', count: 6 }, { name: 'Template2', count: 4 }]}) - create(:stat_created_plan, date: '2018-09-30', count: 10, org: org, details: { by_template: [{ name: 'Template1', count: 6 }, { name: 'Template2', count: 4 }]}) + create(:stat_created_plan, date: "2018-07-31", count: 5, org: org, + details: { by_template: [{ name: "Template1", count: 3 }, + { name: "Template2", count: 2 }] }) + create(:stat_created_plan, date: "2018-08-31", count: 10, org: org, + details: { by_template: [{ name: "Template1", count: 6 }, + { name: "Template2", count: 4 }] }) + create(:stat_created_plan, date: "2018-09-30", count: 10, + org: org, details: { by_template: [{ name: "Template1", count: 6 }, + { name: "Template2", count: 4 }] }) end it "returns all stats" do get path expect(parsed_response).to eq([ - { date: '2018-09-30', count: 10, by_template: [{ name: 'Template1', count: 6 }, { name: 'Template2', count: 4 }]}, - { date: '2018-08-31', count: 10, by_template: [{ name: 'Template1', count: 6 }, { name: 'Template2', count: 4 }]}, - { date: '2018-07-31', count: 5, by_template: [{ name: 'Template1', count: 3 }, { name: 'Template2', count: 2 }]} + { date: "2018-09-30", count: 10, by_template: [ + { name: "Template1", count: 6 }, + { name: "Template2", count: 4 }] }, + { date: "2018-08-31", count: 10, by_template: [ + { name: "Template1", count: 6 }, + { name: "Template2", count: 4 }] }, + { date: "2018-07-31", count: 5, by_template: [ + { name: "Template1", count: 3 }, + { name: "Template2", count: 2 }] } ]) end - it 'returns all stats csv formatted' do + it "returns all stats csv formatted" do get "#{path}.csv" expected_csv = <<~HERE Date,Template1,Template2,Count - 2018-09-30,6,4,10 - 2018-08-31,6,4,10 - 2018-07-31,3,2,5 + Sep 2018,6,4,10 + Aug 2018,6,4,10 + Jul 2018,3,2,5 HERE expect(response.body).to eq(expected_csv) end - it 'returns stats for start_date and end_date passed' do - get path, { start_date: '2018-08-31', end_date: '2018-09-30' } + it "returns stats for start_date and end_date passed" do + get path, start_date: "2018-08-31", end_date: "2018-09-30" expect(parsed_response).to eq([ - { date: '2018-09-30', count: 10, by_template: [{ name: 'Template1', count: 6 }, { name: 'Template2', count: 4 }]}, - { date: '2018-08-31', count: 10, by_template: [{ name: 'Template1', count: 6 }, { name: 'Template2', count: 4 }]} + { date: "2018-09-30", count: 10, by_template: [ + { name: "Template1", count: 6 }, + { name: "Template2", count: 4 }] }, + { date: "2018-08-31", count: 10, by_template: [ + { name: "Template1", count: 6 }, + { name: "Template2", count: 4 }] } ]) end - it 'returns stats from start_date passed' do - get path, { start_date: '2018-08-31' } + it "returns stats from start_date passed" do + get path, start_date: "2018-08-31" expect(parsed_response).to eq([ - { date: '2018-09-30', count: 10, by_template: [{ name: 'Template1', count: 6 }, { name: 'Template2', count: 4 }]}, - { date: '2018-08-31', count: 10, by_template: [{ name: 'Template1', count: 6 }, { name: 'Template2', count: 4 }]}, + { date: "2018-09-30", count: 10, by_template: [ + { name: "Template1", count: 6 }, + { name: "Template2", count: 4 }] }, + { date: "2018-08-31", count: 10, by_template: [ + { name: "Template1", count: 6 }, + { name: "Template2", count: 4 }] }, ]) end - it 'returns stats until end_date passed' do - get path, { end_date: '2018-08-31' } + it "returns stats until end_date passed" do + get path, end_date: "2018-08-31" expect(parsed_response).to eq([ - { date: '2018-08-31', count: 10, by_template: [{ name: 'Template1', count: 6 }, { name: 'Template2', count: 4 }]}, - { date: '2018-07-31', count: 5, by_template: [{ name: 'Template1', count: 3 }, { name: 'Template2', count: 2 }]} + { date: "2018-08-31", count: 10, by_template: [ + { name: "Template1", count: 6 }, + { name: "Template2", count: 4 }] }, + { date: "2018-07-31", count: 5, by_template: [ + { name: "Template1", count: 3 }, + { name: "Template2", count: 2 }] } ]) end end