diff --git a/README.md b/README.md index d8c2b77bd..eabcfbc54 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ You will also need to [configure the app to accept replies from councillors](#ac #### Global feature flag +##### Writing to councillors feature You can toggle the availability of the writing to councillors feature on or off for the entire site with the environment variable `COUNCILLORS_ENABLED`. The feature is globally enabled when the value of `ENV["COUNCILLORS_ENABLED"]` is `"true"`. This flag is useful if you need to turn the feature _off_ globally. @@ -109,6 +110,17 @@ We set this in the [`.env`](https://github.com/openaustralia/planningalerts/blob ``` COUNCILLORS_ENABLED=true ``` +##### Contributing suggested councillors feature +Similarly, you can toggle the availability of the contributing suggested councillors feature on or off for the entire site with the environment variable `CONTRIBUTE_COUNCILLORS_ENABLED`. +The feature is globally enabled when the value of `ENV["CONTRIBUTE_COUNCILLORS_ENABLED"]` is `"true"`. +This flag is useful if you need to turn the feature _off_ globally. + +We set this in the [`.env`](https://github.com/openaustralia/planningalerts/blob/master/.env) file in production. You can control setting in development by creating your own `.env.development` file which includes: + +``` +CONTRIBUTE_COUNCILLORS_ENABLED=true +``` + #### Set the reply address for accepting responses diff --git a/app/admin/suggested_councillors.rb b/app/admin/suggested_councillors.rb new file mode 100644 index 000000000..95ab8005d --- /dev/null +++ b/app/admin/suggested_councillors.rb @@ -0,0 +1,8 @@ +ActiveAdmin.register SuggestedCouncillor do + actions :index + index do + column :authority + column :name + column :email + end +end diff --git a/app/assets/stylesheets/partials/_form.scss b/app/assets/stylesheets/partials/_form.scss index 983463004..83463f284 100644 --- a/app/assets/stylesheets/partials/_form.scss +++ b/app/assets/stylesheets/partials/_form.scss @@ -221,3 +221,4 @@ form.donate { @import "special_forms/autocomplete"; @import "special_forms/comment_form"; @import "special_forms/payment_form"; +@import "special_forms/councillor_contribution_form"; diff --git a/app/assets/stylesheets/partials/_grid.scss b/app/assets/stylesheets/partials/_grid.scss index 32f2ab268..85c2e61d8 100644 --- a/app/assets/stylesheets/partials/_grid.scss +++ b/app/assets/stylesheets/partials/_grid.scss @@ -504,3 +504,48 @@ } } } + +.councillor-contributions { + @include at-breakpoint(60em) { + @include span-columns(8, 12); + float: none; + margin-left: auto; + margin-right: auto; + } +} + +.councillor-contribution-form { + .suggested-councillor-input-wrapper { + @include at-breakpoint(30em) { + display: flex; + justify-content: space-between; + } + } + + .suggested-councillor-input-group { + @include at-breakpoint(30em) { + width: 50%; + box-sizing: border-box; + } + } + + input { + box-sizing: border-box; + } +} + +.suggested-councillor-input-group { + @include at-breakpoint(30em) { + padding-right: 1em; + } + + input { + width: 100%; + } + + & + & { + @include at-breakpoint(30em) { + padding-right: 0; + } + } +} diff --git a/app/assets/stylesheets/partials/special_forms/_councillor_contribution_form.scss b/app/assets/stylesheets/partials/special_forms/_councillor_contribution_form.scss new file mode 100644 index 000000000..2a9c8c95d --- /dev/null +++ b/app/assets/stylesheets/partials/special_forms/_councillor_contribution_form.scss @@ -0,0 +1,47 @@ +// TODO: These forms styles are very repetitive with #new_add_comment and .formtastic child styles +// They just don't have the .formatastic overrides. +// There is probably a refactor to make these the base/raw form styles +// and to move them into _form.scss +.councillor-contribution-form { + .councillor-contribution-label { + display: block; + margin: .75em 0 .25em; + padding: 0; + font-size: 1em; + font-weight: 500; + } + + input { + display: block; + padding: .25em; + font-family: $blueprint-font-family; + font-size: 100%; + } + + fieldset { + margin-bottom: 1em; + } + + ul { + list-style: none; + padding: 0; + margin: 0; + } +} + +.councillor-contribution-councillors { + padding-bottom: 2em; + + input, + dd { + font-size: 1.2em; + } + + dt { + color: $cool-highlight; + } +} + +.councillor-contribution-actions { + text-align: right; +} diff --git a/app/controllers/contributors_controller.rb b/app/controllers/contributors_controller.rb new file mode 100644 index 000000000..75b9e0717 --- /dev/null +++ b/app/controllers/contributors_controller.rb @@ -0,0 +1,34 @@ +class ContributorsController < ApplicationController + before_action :check_if_feature_flag_is_on + + def new + @contributor = Contributor.new + end + + def create + @contributor = Contributor.new(contributor_params) + @councillor_contribution = CouncillorContribution.find(params[:contributor][:councillor_contribution_id]) + if @contributor.save + @councillor_contribution.update(contributor: @contributor) + flash[:notice] = "Thank you" + redirect_to root_url + end + end + + def no_contributor_info + flash[:notice] = "Thank you" + redirect_to root_url + end + + private + + def contributor_params + params.require(:contributor).permit(:name, :email) + end + + def check_if_feature_flag_is_on + unless ENV["CONTRIBUTE_COUNCILLORS_ENABLED"] == "true" + render "static/error_404", status: 404 + end + end +end diff --git a/app/controllers/councillor_contributions_controller.rb b/app/controllers/councillor_contributions_controller.rb new file mode 100644 index 000000000..29201be82 --- /dev/null +++ b/app/controllers/councillor_contributions_controller.rb @@ -0,0 +1,38 @@ +class CouncillorContributionsController < ApplicationController + before_action :check_if_feature_flag_is_on + + def new + @authority = Authority.find_by_short_name_encoded!(params[:authority_id]) + + if params["councillor_contribution"] + @councillor_contribution = @authority.councillor_contributions.build(councillor_contribution_params) + else + @councillor_contribution = CouncillorContribution.new + end + + @councillor_contribution.suggested_councillors.build({email: nil, name: nil}) + end + + def create + @authority = Authority.find_by_short_name_encoded!(params[:authority_id]) + @councillor_contribution = @authority.councillor_contributions.build(councillor_contribution_params) + + if @councillor_contribution.save + redirect_to new_contributor_url(councillor_contribution_id: @councillor_contribution.id) + else + render :new + end + end + + private + + def councillor_contribution_params + params.require(:councillor_contribution).permit(suggested_councillors_attributes: [:name, :email]) + end + + def check_if_feature_flag_is_on + unless ENV["CONTRIBUTE_COUNCILLORS_ENABLED"].present? + render "static/error_404", status: 404 + end + end +end diff --git a/app/models/authority.rb b/app/models/authority.rb index 6bdea8290..357398130 100644 --- a/app/models/authority.rb +++ b/app/models/authority.rb @@ -21,6 +21,7 @@ class Authority < ActiveRecord::Base has_many :applications has_many :councillors has_many :comments, through: :applications + has_many :councillor_contributions validates :short_name, presence: true, uniqueness: { case_sensitive: false } diff --git a/app/models/contributor.rb b/app/models/contributor.rb new file mode 100644 index 000000000..1deccff38 --- /dev/null +++ b/app/models/contributor.rb @@ -0,0 +1,3 @@ +class Contributor < ActiveRecord::Base + has_many :councillor_contributions +end diff --git a/app/models/councillor_contribution.rb b/app/models/councillor_contribution.rb new file mode 100644 index 000000000..209fb740a --- /dev/null +++ b/app/models/councillor_contribution.rb @@ -0,0 +1,6 @@ +class CouncillorContribution < ActiveRecord::Base + belongs_to :contributor + belongs_to :authority + has_many :suggested_councillors, inverse_of: :councillor_contribution + accepts_nested_attributes_for :suggested_councillors, reject_if: :all_blank +end diff --git a/app/models/suggested_councillor.rb b/app/models/suggested_councillor.rb new file mode 100644 index 000000000..8f254cd99 --- /dev/null +++ b/app/models/suggested_councillor.rb @@ -0,0 +1,5 @@ +class SuggestedCouncillor < ActiveRecord::Base + has_one :authority, through: :councillor_contribution + belongs_to :councillor_contribution + validates :councillor_contribution, :name, :email, presence: true +end diff --git a/app/views/contributors/new.html.haml b/app/views/contributors/new.html.haml new file mode 100644 index 000000000..eb495bd9f --- /dev/null +++ b/app/views/contributors/new.html.haml @@ -0,0 +1,20 @@ +%h1 Thank you! +%p + This data you contributed will be reviewed + by an administrator, and go live shortly. += form_for @contributor do |f| + = f.hidden_field :councillor_contribution_id, value: params[:councillor_contribution_id] + %fieldset + %legend + Please tell us about yourself, so we can send you a little note of appreciation + and updates about your contribution when it goes live. + %p This field is optional. + %p + = f.label :name + = f.text_field :name + %p + = f.label :email + = f.text_field :email + %p.button + = f.submit "Submit" + = link_to "I prefer not to", contributors_no_info_path diff --git a/app/views/councillor_contributions/_contribution_form_input.html.haml b/app/views/councillor_contributions/_contribution_form_input.html.haml new file mode 100644 index 000000000..82028697d --- /dev/null +++ b/app/views/councillor_contributions/_contribution_form_input.html.haml @@ -0,0 +1,9 @@ +%fieldset + .suggested-councillor-input-wrapper + = f.fields_for :suggested_councillors, @councillor_contribution.suggested_councillors.last do |suggested_councillor_field| + .suggested-councillor-input-group + = suggested_councillor_field.label :name, "Full name", class: "councillor-contribution-label" + = suggested_councillor_field.text_field :name + .suggested-councillor-input-group + = suggested_councillor_field.label :email, class: "councillor-contribution-label" + = suggested_councillor_field.text_field :email diff --git a/app/views/councillor_contributions/new.html.haml b/app/views/councillor_contributions/new.html.haml new file mode 100644 index 000000000..e901698fc --- /dev/null +++ b/app/views/councillor_contributions/new.html.haml @@ -0,0 +1,23 @@ +.councillor-contributions + %h1.page-title Add a new councillor for #{@authority.full_name} + = form_for [@authority, @councillor_contribution], url: authority_councillor_contributions_path, html: { class: "councillor-contribution-form" } do |f| + - @councillor_contribution.suggested_councillors[0...-1].each do |s| + = f.fields_for :suggested_councillors, @councillor_contribution.suggested_councillors.last do |suggested_councillor_field| + = suggested_councillor_field.hidden_field :name, value: s.name + = suggested_councillor_field.hidden_field :email, value: s.email + .councillor-contribution-councillors + - if @councillor_contribution.suggested_councillors.many? + %ul + - @councillor_contribution.suggested_councillors[0...-1].each do |s| + %li + %dl.suggested-councillor-input-wrapper + .suggested-councillor-input-group + %dt.councillor-contribution-label Full name + %dd= s.name + .suggested-councillor-input-group + %dt.councillor-contribution-label Email + %dd= s.email + = render 'contribution_form_input', f: f + %button{formaction: new_authority_councillor_contribution_path, class: "button"} Add another councillor + .councillor-contribution-actions + = f.submit "Submit #{pluralize(@councillor_contribution.suggested_councillors.length, "new councillor")}", class: "button-action" diff --git a/config/routes.rb b/config/routes.rb index 3c80567fa..146ce7130 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -102,11 +102,17 @@ def matches?(request) get :per_week end end + resources :councillor_contributions, only:[:new, :create] collection do get :test_feed end end + post "/authorities/:authority_id/councillor_contributions/new", to: "councillor_contributions#new" + + resources :contributors, only:[:new, :create] + get "/contributors/no_info", to: "contributors#no_contributor_info" + namespace :atdis do get :test post :test, action: 'test_redirect' diff --git a/db/migrate/20170616171451_create_suggested_councillors.rb b/db/migrate/20170616171451_create_suggested_councillors.rb new file mode 100644 index 000000000..e30418931 --- /dev/null +++ b/db/migrate/20170616171451_create_suggested_councillors.rb @@ -0,0 +1,11 @@ +class CreateSuggestedCouncillors < ActiveRecord::Migration + def change + create_table :suggested_councillors do |t| + t.string :name + t.string :email + t.integer :authority_id + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20170623020945_create_contributors.rb b/db/migrate/20170623020945_create_contributors.rb new file mode 100644 index 000000000..cff863706 --- /dev/null +++ b/db/migrate/20170623020945_create_contributors.rb @@ -0,0 +1,10 @@ +class CreateContributors < ActiveRecord::Migration + def change + create_table :contributors do |t| + t.string :name + t.string :email + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20170623024916_add_contributor_id_to_suggested_councillors.rb b/db/migrate/20170623024916_add_contributor_id_to_suggested_councillors.rb new file mode 100644 index 000000000..fac90ea09 --- /dev/null +++ b/db/migrate/20170623024916_add_contributor_id_to_suggested_councillors.rb @@ -0,0 +1,5 @@ +class AddContributorIdToSuggestedCouncillors < ActiveRecord::Migration + def change + add_column :suggested_councillors, :contributor_id, :integer + end +end diff --git a/db/migrate/20170704043739_create_councillor_contributions.rb b/db/migrate/20170704043739_create_councillor_contributions.rb new file mode 100644 index 000000000..6ece290cf --- /dev/null +++ b/db/migrate/20170704043739_create_councillor_contributions.rb @@ -0,0 +1,10 @@ +class CreateCouncillorContributions < ActiveRecord::Migration + def change + create_table :councillor_contributions do |t| + t.integer :contributor_id + t.integer :authority_id + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20170704164758_add_councillor_contribution_id_in_suggested_councillors.rb b/db/migrate/20170704164758_add_councillor_contribution_id_in_suggested_councillors.rb new file mode 100644 index 000000000..f0ea9ee41 --- /dev/null +++ b/db/migrate/20170704164758_add_councillor_contribution_id_in_suggested_councillors.rb @@ -0,0 +1,5 @@ +class AddCouncillorContributionIdInSuggestedCouncillors < ActiveRecord::Migration + def change + add_column :suggested_councillors, :councillor_contribution_id, :integer + end +end diff --git a/db/migrate/20170704165154_remove_authority_id_from_suggested_councillors.rb b/db/migrate/20170704165154_remove_authority_id_from_suggested_councillors.rb new file mode 100644 index 000000000..d7bf85578 --- /dev/null +++ b/db/migrate/20170704165154_remove_authority_id_from_suggested_councillors.rb @@ -0,0 +1,5 @@ +class RemoveAuthorityIdFromSuggestedCouncillors < ActiveRecord::Migration + def change + remove_column :suggested_councillors, :authority_id + end +end diff --git a/db/migrate/20170704165351_remove_contributor_id_from_suggested_councillors.rb b/db/migrate/20170704165351_remove_contributor_id_from_suggested_councillors.rb new file mode 100644 index 000000000..80660a564 --- /dev/null +++ b/db/migrate/20170704165351_remove_contributor_id_from_suggested_councillors.rb @@ -0,0 +1,5 @@ +class RemoveContributorIdFromSuggestedCouncillors < ActiveRecord::Migration + def change + remove_column :suggested_councillors, :contributor_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 49a304cf8..b62c73561 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170109005050) do +ActiveRecord::Schema.define(version: 20170704165351) do create_table "active_admin_comments", force: :cascade do |t| t.string "resource_id", limit: 255, null: false @@ -131,6 +131,20 @@ add_index "comments", ["confirmed"], name: "index_comments_on_confirmed", using: :btree add_index "comments", ["hidden"], name: "index_comments_on_hidden", using: :btree + create_table "contributors", force: :cascade do |t| + t.string "name", limit: 255 + t.string "email", limit: 255 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "councillor_contributions", force: :cascade do |t| + t.integer "contributor_id", limit: 4 + t.integer "authority_id", limit: 4 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "councillors", force: :cascade do |t| t.string "name", limit: 255 t.string "image_url", limit: 255 @@ -203,6 +217,14 @@ t.integer "value", limit: 4, null: false end + create_table "suggested_councillors", force: :cascade do |t| + t.string "name", limit: 255 + t.string "email", limit: 255 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "councillor_contribution_id", limit: 4 + end + create_table "users", force: :cascade do |t| t.string "email", limit: 255, default: "", null: false t.string "encrypted_password", limit: 128, default: "", null: false diff --git a/spec/factories.rb b/spec/factories.rb index 2ea9b8070..4d854da2e 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -116,4 +116,10 @@ factory :subscription do sequence(:email) { |s| "mary#{s}@enterpriserealty.com.au" } end + + factory :suggested_councillor do + name "Mila Gilic" + email "mgilic@casey.vic.gov.au" + association :authority + end end diff --git a/spec/features/admin_views_councillor_contribution_spec.rb b/spec/features/admin_views_councillor_contribution_spec.rb new file mode 100644 index 000000000..10de55405 --- /dev/null +++ b/spec/features/admin_views_councillor_contribution_spec.rb @@ -0,0 +1,20 @@ +require "spec_helper" + +feature "Admin views a suggested councillor" do + before do + create( + :suggested_councillor, + name: "Mila Gilic", + email: "mgilic@casey.vic.gov.au", + authority: create(:authority, full_name: "Casey City Council") + ) + end + + it "successfully" do + sign_in_as_admin + + click_link "Suggested Councillors" + + expect(page).to have_content "Casey City Council Mila Gilic mgilic@casey.vic.gov.au" + end +end diff --git a/spec/features/contributor_can_provide_their_contact_details_spec.rb b/spec/features/contributor_can_provide_their_contact_details_spec.rb new file mode 100644 index 000000000..bd9a84b4b --- /dev/null +++ b/spec/features/contributor_can_provide_their_contact_details_spec.rb @@ -0,0 +1,44 @@ +require "spec_helper" + +feature "Contributor can contribute their contact information" do + context "when the feature flag is off" do + it "isn't available" do + visit new_contributor_path + + expect(page.status_code).to eq 404 + end + end + + context "when the feature flag is on" do + around do |test| + with_modified_env CONTRIBUTE_COUNCILLORS_ENABLED: "true" do + test.run + end + end + + before :each do + CouncillorContribution.new(id: 1).save + end + + it "successfully" do + visit new_contributor_path(councillor_contribution_id: 1) + + within_fieldset "Please tell us about yourself, so we can send you a little note of appreciation and updates about your contribution when it goes live." do + fill_in "Name", with: "Jane Contributes" + fill_in "Email", with: "jane@contributor.com" + end + + click_button "Submit" + + expect(page).to have_content "Thank you" + end + + it "or not if they choose" do + visit new_contributor_path(councillor_contribution_id: 1) + + click_link "I prefer not to" + + expect(page).to have_content "Thank you" + end + end +end diff --git a/spec/features/user_contributes_new_councillor_spec.rb b/spec/features/user_contributes_new_councillor_spec.rb new file mode 100644 index 000000000..f92182324 --- /dev/null +++ b/spec/features/user_contributes_new_councillor_spec.rb @@ -0,0 +1,77 @@ +require "spec_helper" + +feature "Contributing new councillors for an authority" do + let(:authority) { create(:authority, full_name: "Casey City Council") } + + context "when the feature flag is off" do + it "isn't available" do + visit new_authority_councillor_contribution_path(authority.short_name_encoded) + + expect(page.status_code).to eq 404 + end + end + + context "when the feature flag is on" do + around do |test| + with_modified_env CONTRIBUTE_COUNCILLORS_ENABLED: "true" do + test.run + end + end + + it "successfully with three councillors and one blank councillor" do + visit new_authority_councillor_contribution_path(authority.short_name_encoded) + + within ".councillor-contribution-councillors fieldset" do + fill_in "Full name", with: "Mila Gilic" + fill_in "Email", with: "mgilic@casey.vic.gov.au" + end + + click_button "Add another councillor" + + within ".councillor-contribution-councillors fieldset" do + fill_in "Full name", with: "Rosalie Crestani" + fill_in "Email", with: "rcrestani@casey.vic.gov.au" + end + + click_button "Add another councillor" + + within ".councillor-contribution-councillors fieldset" do + fill_in "Full name", with: "Rosalie Crestani" + fill_in "Email", with: "rcrestani@casey.vic.gov.au" + end + + click_button "Add another councillor" + + click_button "Submit 4 new councillors" + + expect(page).to have_content "Thank you" + end + + it "successfully with three councillors" do + visit new_authority_councillor_contribution_path(authority.short_name_encoded) + + within ".councillor-contribution-councillors fieldset" do + fill_in "Full name", with: "Mila Gilic" + fill_in "Email", with: "mgilic@casey.vic.gov.au" + end + + click_button "Add another councillor" + + within ".councillor-contribution-councillors fieldset" do + fill_in "Full name", with: "Rosalie Crestani" + fill_in "Email", with: "rcrestani@casey.vic.gov.au" + end + + click_button "Add another councillor" + + within ".councillor-contribution-councillors fieldset" do + fill_in "Full name", with: "Rosalie Crestani" + fill_in "Email", with: "rcrestani@casey.vic.gov.au" + end + + click_button "Submit 3 new councillors" + + expect(page).to have_content "Thank you" + end + end +end diff --git a/spec/models/suggested_councillor_spec.rb b/spec/models/suggested_councillor_spec.rb new file mode 100644 index 000000000..1af940ce7 --- /dev/null +++ b/spec/models/suggested_councillor_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +RSpec.describe SuggestedCouncillor, type: :model do + describe "is invalid without an email" do + subject { SuggestedCouncillor.new(name: "Milla", email: nil, councillor_contribution_id: 1) } + it { is_expected.to_not be_valid } + end + + describe "is invalid with a blank email" do + subject { SuggestedCouncillor.new(name: "Milla", email: "", councillor_contribution_id: 1) } + it { is_expected.to_not be_valid } + end + + describe "is invalid without an name" do + subject { SuggestedCouncillor.new(name: nil, email: "test@test.com", councillor_contribution_id: 1) } + it { is_expected.to_not be_valid } + end + + describe "is invalid with a blank name" do + subject { SuggestedCouncillor.new(name: "", email: "test@test.com", councillor_contribution_id: 1) } + it { is_expected.to_not be_valid } + end + + describe "is invalid without an councillor_contribution_id" do + subject { SuggestedCouncillor.new(name: "Milla", email: "test@test.com", councillor_contribution_id: nil) } + it { is_expected.to_not be_valid } + end +end