diff --git a/app/assets/stylesheets/metrics_page.scss b/app/assets/stylesheets/metrics_page.scss index d991d96..c3a1c80 100644 --- a/app/assets/stylesheets/metrics_page.scss +++ b/app/assets/stylesheets/metrics_page.scss @@ -47,6 +47,13 @@ aside.menu { } } } + + #graph-div { + min-height: 340px; + display: flex; + align-items: center; + justify-content: center; + } } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index a085f63..7b06806 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -13,36 +13,15 @@ class ApplicationController < ActionController::Base after_action :track_action - ## The following are used by our Responder service classes so we can access - ## the instance variable for the current resource easily via a standard method - def resource_name - controller_name.demodulize.singularize - end - - def current_resource - instance_variable_get(:"@#{resource_name}") - end - - def current_resource=(val) - instance_variable_set(:"@#{resource_name}", val) - end - # Catch NotFound exceptions and handle them neatly, when URLs are mistyped or mislinked rescue_from ActiveRecord::RecordNotFound do render template: 'errors/error_404', status: 404 end + rescue_from CanCan::AccessDenied do render template: 'errors/error_403', status: 403 end - # IE over HTTPS will not download if browser caching is off, so allow browser caching when sending files - def send_file(file, opts = {}) - response.headers['Cache-Control'] = 'private, proxy-revalidate' # Still prevent proxy caching - response.headers['Pragma'] = 'cache' - response.headers['Expires'] = '0' - super(file, opts) - end - protected # Ahoy Gem function to track actions diff --git a/app/controllers/metrics_controller.rb b/app/controllers/metrics_controller.rb index 0797bdb..8d8d65b 100644 --- a/app/controllers/metrics_controller.rb +++ b/app/controllers/metrics_controller.rb @@ -40,7 +40,7 @@ def update_graph_time config[:data] = data # Check if there is still valid data, else return "No Data" - if helpers.there_data?(data) + if helpers.has_data?(data) return_partial('#graph-div', layout, config) else return_partial(nil, nil, {}) @@ -76,8 +76,8 @@ def generate_json_response(title, template, locals) end # set - def setter(option) - [helpers.data_setter(option), helpers.config_setter(option)] + def setter(graph) + [helpers.data_setter(graph), helpers.config_setter(graph)] end private @@ -98,4 +98,4 @@ def extra_processing(name, data) def graph_params params.require(:metric) end -end \ No newline at end of file +end diff --git a/app/controllers/newsletters_controller.rb b/app/controllers/newsletters_controller.rb index 69174f5..8218de9 100644 --- a/app/controllers/newsletters_controller.rb +++ b/app/controllers/newsletters_controller.rb @@ -67,7 +67,7 @@ def create_unsubscribe end def subscribers - @subscribers = NewsletterSubscription.where(subscribed: true) + @subscribers = NewsletterSubscription.where(subscribed: true).decorate end private diff --git a/app/decorators/newsletter_decorator.rb b/app/decorators/newsletter_subscription_decorator.rb similarity index 64% rename from app/decorators/newsletter_decorator.rb rename to app/decorators/newsletter_subscription_decorator.rb index 544d8f0..5380c3e 100644 --- a/app/decorators/newsletter_decorator.rb +++ b/app/decorators/newsletter_subscription_decorator.rb @@ -1,4 +1,7 @@ -class NewsletterDecorator < Draper::Decorator +# frozen_string_literal: true + +# Decorate for Newsletter +class NewsletterSubscriptionDecorator < Draper::Decorator delegate_all # Define presentation-specific methods here. Helpers are accessed through @@ -10,4 +13,7 @@ class NewsletterDecorator < Draper::Decorator # end # end + def created_at + l object.created_at, format: :date + end end diff --git a/app/decorators/project_decorator.rb b/app/decorators/project_decorator.rb deleted file mode 100644 index 080d907..0000000 --- a/app/decorators/project_decorator.rb +++ /dev/null @@ -1,13 +0,0 @@ -class ProjectDecorator < Draper::Decorator - delegate_all - - # Define presentation-specific methods here. Helpers are accessed through - # `helpers` (aka `h`). You can override attributes, for example: - # - # def created_at - # helpers.content_tag :span, class: 'time' do - # object.created_at.strftime("%a %m/%d/%y") - # end - # end - -end diff --git a/app/decorators/review_decorator.rb b/app/decorators/review_decorator.rb deleted file mode 100644 index cc7b0d9..0000000 --- a/app/decorators/review_decorator.rb +++ /dev/null @@ -1,13 +0,0 @@ -class ReviewDecorator < Draper::Decorator - delegate_all - - # Define presentation-specific methods here. Helpers are accessed through - # `helpers` (aka `h`). You can override attributes, for example: - # - # def created_at - # helpers.content_tag :span, class: 'time' do - # object.created_at.strftime("%a %m/%d/%y") - # end - # end - -end diff --git a/app/decorators/task_decorator.rb b/app/decorators/task_decorator.rb deleted file mode 100644 index e191142..0000000 --- a/app/decorators/task_decorator.rb +++ /dev/null @@ -1,13 +0,0 @@ -class TaskDecorator < Draper::Decorator - delegate_all - - # Define presentation-specific methods here. Helpers are accessed through - # `helpers` (aka `h`). You can override attributes, for example: - # - # def created_at - # helpers.content_tag :span, class: 'time' do - # object.created_at.strftime("%a %m/%d/%y") - # end - # end - -end diff --git a/app/decorators/team_decorator.rb b/app/decorators/team_decorator.rb deleted file mode 100644 index 64143d0..0000000 --- a/app/decorators/team_decorator.rb +++ /dev/null @@ -1,13 +0,0 @@ -class TeamDecorator < Draper::Decorator - delegate_all - - # Define presentation-specific methods here. Helpers are accessed through - # `helpers` (aka `h`). You can override attributes, for example: - # - # def created_at - # helpers.content_tag :span, class: 'time' do - # object.created_at.strftime("%a %m/%d/%y") - # end - # end - -end diff --git a/app/helpers/metrics_helper.rb b/app/helpers/metrics_helper.rb index 29a6b2c..5802acb 100644 --- a/app/helpers/metrics_helper.rb +++ b/app/helpers/metrics_helper.rb @@ -13,15 +13,16 @@ module MetricsHelper Referrers: { group_by: 'referrer', time: 'started_at', average: nil } }.freeze - def config_setter(option) - val = GRAPH_CONFIG.stringify_keys.keys.select { |key| option.include? key } - puts "VAL #{val}" - GRAPH_CONFIG.stringify_keys.fetch(val[0], - { time: 'created_at', average: nil, group_by: nil } ) + def config_setter(graph) + val = GRAPH_CONFIG.stringify_keys.keys.select { |key| graph.include? key } + + GRAPH_CONFIG.stringify_keys.fetch( + val[0], time: 'created_at', average: nil, group_by: nil + ) end - def data_setter(option) - case option + def data_setter(graph) + case graph when /Reason/ [{ title: 'Reason', data: NewsletterFeedback.graph }] when /Landing Page/ @@ -62,11 +63,8 @@ def decide_layout(option) end # loops through arrays and check if there is at least one array has data - def there_data?(array) - array.each do |data| - return true if data[:data].any? - end - false + def has_data?(array) + array.any? { |data| data[:data].any? } end # set time_constraint to data based on the number of time constraint selected @@ -88,11 +86,10 @@ def time_constraint(time, data) def custom_date_scope(data, date1, date2) data.select do |v| - if date2.nil? - v[:created_at].between?(date1, date1 + 1.days) - else - v[:created_at].between?(date1, DateTime.parse(date2) + 1.days) - end + v[:created_at].between?( + date1, + date2.nil? ? date1 + 1.days : DateTime.parse(date2) + 1.days + ) end end end diff --git a/app/inputs/date_string_input.rb b/app/inputs/date_string_input.rb deleted file mode 100644 index 42f54b6..0000000 --- a/app/inputs/date_string_input.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -class DateStringInput < SimpleForm::Inputs::Base - def input(wrapper_options = nil) - date = @builder.object.send attribute_name - input_html = { value: (I18n.l date if date.is_a?(Date)) } - merged_input_options = - merge_wrapper_options( - input_html_options.merge(input_html), - wrapper_options - ) - (@builder.text_field attribute_name, merged_input_options).to_s.html_safe - end - - def input_html_classes - super.push('form-control') - end -end diff --git a/app/inputs/inline_check_boxes_input.rb b/app/inputs/inline_check_boxes_input.rb deleted file mode 100644 index 0ea6864..0000000 --- a/app/inputs/inline_check_boxes_input.rb +++ /dev/null @@ -1,25 +0,0 @@ -class InlineCheckBoxesInput < SimpleForm::Inputs::CollectionCheckBoxesInput - - def input(wrapper_options = nil) - label_method, value_method = detect_collection_methods - - merged_input_options = merge_wrapper_options(input_html_options, wrapper_options) - - @builder.send("collection_check_boxes", - attribute_name, collection, value_method, label_method, - input_options, merged_input_options, - &collection_block_for_nested_boolean_style - ) - end - - - def apply_default_collection_options!(options) - options[:item_wrapper_tag] = nil - options[:item_label_class] = 'checkbox-inline' - options[:collection_wrapper_tag] ||= options.fetch(:collection_wrapper_tag, SimpleForm.collection_wrapper_tag) - options[:collection_wrapper_class] = [ - options[:collection_wrapper_class], SimpleForm.collection_wrapper_class - ].compact.presence - end - -end \ No newline at end of file diff --git a/app/inputs/inline_radio_buttons_input.rb b/app/inputs/inline_radio_buttons_input.rb deleted file mode 100644 index 157cec2..0000000 --- a/app/inputs/inline_radio_buttons_input.rb +++ /dev/null @@ -1,25 +0,0 @@ -class InlineRadioButtonsInput < SimpleForm::Inputs::CollectionRadioButtonsInput - - def input(wrapper_options = nil) - label_method, value_method = detect_collection_methods - - merged_input_options = merge_wrapper_options(input_html_options, wrapper_options) - - @builder.send("collection_radio_buttons", - attribute_name, collection, value_method, label_method, - input_options, merged_input_options, - &collection_block_for_nested_boolean_style - ) - end - - - def apply_default_collection_options!(options) - options[:item_wrapper_tag] = nil - options[:item_label_class] = 'radio-inline' - options[:collection_wrapper_tag] ||= options.fetch(:collection_wrapper_tag, SimpleForm.collection_wrapper_tag) - options[:collection_wrapper_class] = [ - options[:collection_wrapper_class], SimpleForm.collection_wrapper_class - ].compact.presence - end - -end \ No newline at end of file diff --git a/app/models/concerns/date_scope.rb b/app/models/concerns/date_scope.rb index 30f081d..9fede5a 100644 --- a/app/models/concerns/date_scope.rb +++ b/app/models/concerns/date_scope.rb @@ -7,13 +7,11 @@ module DateScope included do scope :on_date, lambda { |query, time| - where(query + ' BETWEEN ? AND ?', - time, time + 1.days) + where(query + ' BETWEEN ? AND ?', time, time + 1.days) } scope :between_date, lambda { |query, time1, time2| - where(query + ' BETWEEN ? AND ?', - time1, time2 + 1.days) + where(query + ' BETWEEN ? AND ?', time1, time2 + 1.days) } end end diff --git a/app/models/newsletter.rb b/app/models/newsletter.rb index 00fae87..98d4446 100644 --- a/app/models/newsletter.rb +++ b/app/models/newsletter.rb @@ -15,14 +15,15 @@ class Newsletter < ApplicationRecord include DateScope - scope :graph, -> { find_by_sql( - "SELECT newsletters.title, - newsletters.created_at, COUNT(newsletter_feedbacks) - as feedback_count FROM newsletters JOIN newsletter_feedbacks - ON newsletter_feedbacks.created_at BETWEEN newsletters.created_at - AND newsletters.created_at+interval\'7 days\' GROUP BY newsletters.id" - ) - } + scope :graph, lambda { + find_by_sql( + "SELECT newsletters.title, + newsletters.created_at, COUNT(newsletter_feedbacks) + as feedback_count FROM newsletters JOIN newsletter_feedbacks + ON newsletter_feedbacks.created_at BETWEEN newsletters.created_at + AND newsletters.created_at+interval\'7 days\' GROUP BY newsletters.id" + ) + } validates_presence_of :title, :content end diff --git a/app/models/newsletter_subscription.rb b/app/models/newsletter_subscription.rb index 87bc254..006beaa 100644 --- a/app/models/newsletter_subscription.rb +++ b/app/models/newsletter_subscription.rb @@ -4,12 +4,11 @@ # # Table name: newsletter_subscriptions # -# id :bigint not null, primary key -# date_subscribed :date default(Mon, 16 Dec 2019), not null -# email :string not null -# subscribed :boolean default(TRUE), not null -# created_at :datetime not null -# updated_at :datetime not null +# id :bigint not null, primary key +# email :string not null +# subscribed :boolean default(TRUE), not null +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # @@ -27,6 +26,8 @@ class NewsletterSubscription < ApplicationRecord scope :all_subscribed_emails, -> { where(subscribed: true).pluck(:email) } scope :previously_subscribed, -> { where(subscribed: false) } + + def self.send_newsletter(newsletter) all_subscribed_emails.each_slice(50) do |emails| NewsletterMailer.send_newsletter(emails, newsletter).deliver_later diff --git a/app/views/newsletters/subscribers.haml b/app/views/newsletters/subscribers.haml index b358559..a481978 100644 --- a/app/views/newsletters/subscribers.haml +++ b/app/views/newsletters/subscribers.haml @@ -17,6 +17,6 @@ - @subscribers.each do |subscriber| %tr{id: "subscriber#{subscriber.id}"} %td= subscriber.email - %td= subscriber.date_subscribed + %td= subscriber.created_at %td= link_to 'Unsubscribe', unsubscribe_newsletters_path(email: subscriber.email, admin: 1), method: 'post', data: { confirm: 'Are you sure?' }, remote: true, class: 'delete_sub' diff --git a/config/locales/en.yml b/config/locales/en.yml index a616258..252dc86 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -9,6 +9,7 @@ en: full: "%a %e %B %Y" time: formats: + date: "%d/%m/%Y" full: "%a %e %B %Y at %H:%M" compact: "%d/%m/%y at %H:%M" short: "%d/%m/%Y %H:%M" diff --git a/db/migrate/20191216174911_remove_date_subscribed_from_newsletter_subscriptions.rb b/db/migrate/20191216174911_remove_date_subscribed_from_newsletter_subscriptions.rb new file mode 100644 index 0000000..51a642d --- /dev/null +++ b/db/migrate/20191216174911_remove_date_subscribed_from_newsletter_subscriptions.rb @@ -0,0 +1,5 @@ +class RemoveDateSubscribedFromNewsletterSubscriptions < ActiveRecord::Migration[6.0] + def change + remove_column :newsletter_subscriptions, :date_subscribed + end +end diff --git a/db/schema.rb b/db/schema.rb index 66c6404..8cada51 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_12_16_022348) do +ActiveRecord::Schema.define(version: 2019_12_16_174911) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -149,7 +149,6 @@ create_table "newsletter_subscriptions", force: :cascade do |t| t.string "email", null: false - t.date "date_subscribed", default: "2019-12-16", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.boolean "subscribed", default: true, null: false diff --git a/spec/decorators/newsletter_decorator_spec.rb b/spec/decorators/newsletter_decorator_spec.rb deleted file mode 100644 index d166ed6..0000000 --- a/spec/decorators/newsletter_decorator_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe NewsletterDecorator do -end diff --git a/spec/decorators/newsletter_subscription_decorator_spec.rb b/spec/decorators/newsletter_subscription_decorator_spec.rb new file mode 100644 index 0000000..a5ddbe5 --- /dev/null +++ b/spec/decorators/newsletter_subscription_decorator_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe NewsletterSubscriptionDecorator do + +end diff --git a/spec/decorators/project_decorator_spec.rb b/spec/decorators/project_decorator_spec.rb deleted file mode 100644 index ad38d2e..0000000 --- a/spec/decorators/project_decorator_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe ProjectDecorator do -end diff --git a/spec/decorators/review_decorator_spec.rb b/spec/decorators/review_decorator_spec.rb deleted file mode 100644 index a0c7ce0..0000000 --- a/spec/decorators/review_decorator_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe ReviewDecorator do -end diff --git a/spec/decorators/task_decorator_spec.rb b/spec/decorators/task_decorator_spec.rb deleted file mode 100644 index ba454ad..0000000 --- a/spec/decorators/task_decorator_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe TaskDecorator do -end diff --git a/spec/decorators/team_decorator_spec.rb b/spec/decorators/team_decorator_spec.rb deleted file mode 100644 index a48d6ee..0000000 --- a/spec/decorators/team_decorator_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe TeamDecorator do -end diff --git a/spec/factories/landing_feedbacks.rb b/spec/factories/landing_feedbacks.rb index bbfeb99..4c385b2 100644 --- a/spec/factories/landing_feedbacks.rb +++ b/spec/factories/landing_feedbacks.rb @@ -13,6 +13,13 @@ # FactoryBot.define do - factory :landing_feedback do + factory :landing_feedback, class: LandingFeedback.name do + smiley { 'neutral' } + interest { true } + channel { 'Social Media' } + + trait :yesterday do + created_at { Time.now - 1.days } + end end end diff --git a/spec/factories/newsletter_subscriptions.rb b/spec/factories/newsletter_subscriptions.rb index 4e1bf32..1256135 100644 --- a/spec/factories/newsletter_subscriptions.rb +++ b/spec/factories/newsletter_subscriptions.rb @@ -2,12 +2,11 @@ # # Table name: newsletter_subscriptions # -# id :bigint not null, primary key -# date_subscribed :date default(Mon, 16 Dec 2019), not null -# email :string not null -# subscribed :boolean default(TRUE), not null -# created_at :datetime not null -# updated_at :datetime not null +# id :bigint not null, primary key +# email :string not null +# subscribed :boolean default(TRUE), not null +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/spec/features/metrics/admin_visit_metrics_index_spec.rb b/spec/features/metrics/admin_visit_metrics_index_spec.rb new file mode 100644 index 0000000..82b42e1 --- /dev/null +++ b/spec/features/metrics/admin_visit_metrics_index_spec.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Metrics > Dashboard', type: :feature do +end diff --git a/spec/helpers/metrics_helper_spec.rb b/spec/helpers/metrics_helper_spec.rb index 90880cc..5a915b7 100644 --- a/spec/helpers/metrics_helper_spec.rb +++ b/spec/helpers/metrics_helper_spec.rb @@ -2,16 +2,103 @@ require 'rails_helper' -# Specs in this file have access to a helper object that includes -# the NewslettersHelper. For example: -# -# describe NewslettersHelper do -# describe "string concat" do -# it "concats two strings with spaces" do -# expect(helper.concat_strings("this","that")).to eq("this that") -# end -# end -# end describe MetricsHelper do + describe '#config_setter' do + let(:graph) { MetricsHelper::GRAPH_CONFIG } + + it { expect(helper.config_setter('Newsletter')).to eq(graph[:Newsletter]) } + + it { expect(helper.config_setter('Null')).to eq(graph[:Newsletter]) } + end + + describe '#data_setter' do + it { expect(helper.data_setter('Reason').size).to eq(1) } + + it { expect(helper.data_setter('Landing Page').size).to eq(3) } + + it { expect(helper.data_setter('by Newsletter').size).to eq(1) } + + it { expect(helper.data_setter('Newsletter').size).to eq(2) } + + it { expect(helper.data_setter('Subscription').size).to eq(1) } + + it { expect(helper.data_setter('Visits').size).to eq(1) } + + it { expect(helper.data_setter('Average Time Spent').size).to eq(1) } + + it { expect(helper.data_setter('Referrers').size).to eq(1) } + + it { expect(helper.data_setter('Null')).to eq([]) } + end + + describe '#decide_layout' do + it { + expect(helper.decide_layout('Landing Page')).to eq('metrics/_feedback.haml') + } + + it { + expect(helper.decide_layout('by Date')).to eq('metrics/_linegraph.haml') + } + + it { + expect(helper.decide_layout('per Page')).to eq('metrics/_barchart.haml') + } + + it { + expect(helper.decide_layout('Null')).to eq('metrics/_piechart.haml') + } + end + + describe '#has_data?' do + it { expect(helper.has_data?([])).to eq false } + + it { + create(:subscriber) + expect(helper.has_data?(helper.data_setter('Newsletter'))).to eq true + } + end + + describe '#time_constraint' do + let(:feedback_yesterday) { create(:landing_feedback, :yesterday) } + let(:feedback_today) { create(:landing_feedback) } + let(:record) { LandingFeedback.all } + let(:data) { [{ data: record }] } + let(:data_array) { [{ data: record.to_a }] } + + let(:two_days_before) { (Time.now - 2.days).to_s } + let(:today) { Time.now.to_s } + + before do + feedback_yesterday + feedback_today + data + two_days_before + today + end + + context 'with datalist as array' do + it { + controller.params[:time] = [two_days_before, today] + expect(helper.time_constraint('created_at', data_array)[0][:data]) + .to eq([feedback_yesterday, feedback_today]) + } + end + + context 'with 1 date' do + it { + controller.params[:time] = [today] + expect(helper.time_constraint('created_at', data)[0][:data]) + .to eq([feedback_today]) + } + end + + context 'with 2 dates' do + it { + controller.params[:time] = [two_days_before, today] + expect(helper.time_constraint('created_at', data)[0][:data]) + .to eq([feedback_yesterday, feedback_today]) + } + end + end end diff --git a/spec/models/newsletter_subscription_spec.rb b/spec/models/newsletter_subscription_spec.rb index 72d3cd6..456ac25 100644 --- a/spec/models/newsletter_subscription_spec.rb +++ b/spec/models/newsletter_subscription_spec.rb @@ -2,12 +2,11 @@ # # Table name: newsletter_subscriptions # -# id :bigint not null, primary key -# date_subscribed :date default(Mon, 16 Dec 2019), not null -# email :string not null -# subscribed :boolean default(TRUE), not null -# created_at :datetime not null -# updated_at :datetime not null +# id :bigint not null, primary key +# email :string not null +# subscribed :boolean default(TRUE), not null +# created_at :datetime not null +# updated_at :datetime not null # # Indexes #