diff --git a/README.md b/README.md index 606d8c34..c22a0322 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,12 @@ npm install docker-compose run --rm web npm install ``` -You may need to log into github to pull the circulation-history image. To do that, get a [Personal Access Token](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token) with permission to "read:packages". On the command line log in with your github username and then Personal Access Token as the password. +You may need to log into github to pull the circulation-history image. To do that, get a [Personal Access Token](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token) with permission to "read:packages". On the command line: +``` +docker login docker.pkg.github.com +``` + +Log in with your github username and then Personal Access Token as the password. start containers diff --git a/css/_banner.scss b/css/_banner.scss new file mode 100644 index 00000000..c625396d --- /dev/null +++ b/css/_banner.scss @@ -0,0 +1,12 @@ +.banner { + background: var(--color-teal-200); + padding: var(--space-x-small) 0; + * { + color: var(--color-neutral-400); + } + m-icon { + position: relative; + margin-right: var(--space-xx-small); + top: var(--space-xxx-small); + } +} diff --git a/css/_patron.scss b/css/_patron.scss new file mode 100644 index 00000000..27f1676e --- /dev/null +++ b/css/_patron.scss @@ -0,0 +1,34 @@ +form[method="post"] { + m-icon { + position: relative; + margin-right: var(--space-xx-small); + top: var(--space-xxx-small); + } + + #text-notifications:not(:checked) ~ .sms-number-container { + display: none; + } + + #sms-number:invalid + #sms-number-description { + color: var(--color-pink-500); + } + + .retain-history-option { + input[type="radio"] { + &:checked + label m-icon[name="radio-button-unchecked"] { + display: none; + } + &:not(:checked) + label m-icon[name="radio-button-checked"] { + display: none; + } + &:focus + label m-icon { + outline: 0; + box-shadow: 0 0 0 2px var(--color-maize-400),0 0 0 3px var(--color-neutral-400); + border-radius: 2px; + } + } + m-icon { + color: var(--color-teal-500); + } + } +} diff --git a/css/index.scss b/css/index.scss index 3bf380af..0dde13fe 100644 --- a/css/index.scss +++ b/css/index.scss @@ -1,5 +1,5 @@ @import "defaults", "base", "utilities", "focus", "site-navigation", "horizontal-navigation", "site-footer", "overview-cards", "loans", "empty-state-container", "buttons", "pagination", "messages", - "text-notifications", "tags", "renew", "dropdown", - "maybe-design-system/controls"; + "text-notifications", "tags", "banner", "renew", "dropdown", + "patron", "maybe-design-system/controls"; diff --git a/css/maybe-design-system/_controls.scss b/css/maybe-design-system/_controls.scss index a4e1c5f8..1c45e8a9 100644 --- a/css/maybe-design-system/_controls.scss +++ b/css/maybe-design-system/_controls.scss @@ -1,12 +1,18 @@ -input[type="text"] { +input[type="text"], +input[type="tel"] { font-size: 1rem; font-family: var(--font-base-family); border: solid 1px rgba(0, 0, 0, 0.3); - box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.1); border-radius: 4px; padding: var(--space-small); margin: 0; width: 100%; + &:invalid { + border-color: var(--color-pink-500); + } + &:not(:focus) { + box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.1); + } } select { @@ -49,3 +55,51 @@ option { select::-ms-expand { display: none; } + +.toggle-switch { + & + .toggle-switch-label { + .toggle { + background-color: var(--color-neutral-200); + border: 1px solid var(--color-neutral-200); + border-radius: var(--space-medium); + display: inline-block; + height: 1.5em; + margin-right: var(--space-x-small); + position: relative; + top: var(--space-x-small); + transition: none 0.2s ease-in-out; + transition-property: background-color, border-color; + width: 3em; + &:before { + content: ''; + background-color: white; + border-radius: var(--space-medium); + display: block; + height: 100%; + left: 0; + position: absolute; + transition: left 0.2s ease-in-out; + width: 50%; + } + } + } + &:checked + .toggle-switch-label { + .toggle { + background-color: var(--color-teal-400); + border-color: var(--color-teal-400); + &:before { + left: 50%; + } + } + } + &:focus + .toggle-switch-label { + .toggle { + outline: 0; + box-shadow: 0 0 0 2px var(--color-maize-400),0 0 0 3px var(--color-neutral-400); + } + } +} + +.input-description { + font-size: var(--space-medium); +} diff --git a/docker-compose.yml b/docker-compose.yml index a81d655c..cb30314c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,7 +43,6 @@ services: circulation-history-db: image: niquerio/circulation_history_dev_db:latest - sass: build: . volumes: @@ -64,3 +63,4 @@ services: volumes: gem_cache: + circ_history_db: diff --git a/lib/circ_history_client.rb b/lib/circ_history_client.rb index d89da68e..405a6b2d 100644 --- a/lib/circ_history_client.rb +++ b/lib/circ_history_client.rb @@ -23,7 +23,7 @@ def download_csv end def set_retain_history(retain_history) - self.class.put("users/#{@uniqname}", query: {retain_history: retain_history}) + self.class.put("/users/#{@uniqname}", query: {retain_history: retain_history}) end end diff --git a/models/patron.rb b/models/patron.rb index cf1a7055..031c87a5 100644 --- a/models/patron.rb +++ b/models/patron.rb @@ -1,20 +1,33 @@ require 'telephone_number' class Patron - def initialize(uniqname:, parsed_response:) - @uniqname = uniqname - @parsed_response = parsed_response + def initialize(alma_data:, circ_history_data: nil) + @alma_data = alma_data + @circ_history_data = circ_history_data end - def self.for(uniqname:, client: AlmaRestClient.client) + def self.for(uniqname:, alma_client: AlmaRestClient.client, circ_history_client: CircHistoryClient.new(uniqname)) url = "/users/#{uniqname}?user_id_type=all_unique&view=full&expand=none" - response = client.get(url) - if response.code == 200 - Patron.new(uniqname: uniqname, parsed_response: response.parsed_response) + alma_response = alma_client.get(url) + circ_history_response = circ_history_client.user_info + if alma_response.code == 200 && circ_history_response.code == 200 + Patron.new(alma_data: alma_response.parsed_response, circ_history_data: circ_history_response.parsed_response) else #should be something else - AlmaError.new(response) + AlmaError.new(alma_response) end end + def self.set_retain_history(uniqname:, retain_history:, circ_history_client: CircHistoryClient.new(uniqname)) + circ_history_client.set_retain_history(retain_history) + end + def confirmed_history_setting? + @circ_history_data["confirmed"] + end + def retain_history? + @circ_history_data["retain_history"] + end + def get_record_count(history=CircHistoryClient.new(uniqname)) + history.loans["total_record_count"] + end def update_sms(sms, client=AlmaRestClient.client, phone=TelephoneNumber.parse(sms, :US)) return Error.new(message: "Phone number #{sms} is invalid") unless phone.valid? || sms.empty? @@ -24,30 +37,30 @@ def update_sms(sms, client=AlmaRestClient.client, phone=TelephoneNumber.parse(sm end def uniqname - @parsed_response["primary_id"]&.downcase + @alma_data["primary_id"]&.downcase end def sms_number - @parsed_response.dig("contact_info","phone")&.find(-> {{}}){|x| x["preferred_sms"]}&.dig("phone_number") + @alma_data.dig("contact_info","phone")&.find(-> {{}}){|x| x["preferred_sms"]}&.dig("phone_number") end def sms_number? !!sms_number end def full_name - @parsed_response["full_name"] + @alma_data["full_name"] end def user_group - @parsed_response.dig("user_group","desc") + @alma_data.dig("user_group","desc") end def can_book? ['Faculty','Graduate','Staff'].any?{|x| user_group.match(x)} end def addresses - @parsed_response.dig("contact_info","address")&.map{|x| Address.new(x)} + @alma_data.dig("contact_info","address")&.map{|x| Address.new(x)} end private def patron_with_internal_sms(sms_number) - updated_patron = JSON.parse(@parsed_response.to_json) + updated_patron = JSON.parse(@alma_data.to_json) phones = updated_patron["contact_info"].delete("phone") my_phones = phones.map{ |x| Phone.new(x)} diff --git a/models/session_patron.rb b/models/session_patron.rb index ea3196fb..0f9511e0 100644 --- a/models/session_patron.rb +++ b/models/session_patron.rb @@ -1,15 +1,14 @@ class SessionPatron - extend Forwardable - def_delegators :@alma_patron, :uniqname, :full_name, :can_book? def initialize(uniqname) - @alma_patron = Patron.for(uniqname: uniqname) + @patron = Patron.for(uniqname: uniqname) end def to_h { - uniqname: @alma_patron.uniqname, - full_name: @alma_patron.full_name, - can_book: @alma_patron.can_book? + uniqname: @patron.uniqname, + full_name: @patron.full_name, + can_book: @patron.can_book?, + confirmed_history_setting: @patron.confirmed_history_setting? } end end diff --git a/my_account.rb b/my_account.rb index 0cb07daf..6082e63c 100644 --- a/my_account.rb +++ b/my_account.rb @@ -95,10 +95,10 @@ # :nocov: get '/' do - session[:uniqname] = 'mlibrary.acct.testing1@gmail.com' if !session[:uniqname] - session[:full_name] = 'Julian Tutor' if session[:uniqname] == 'mlibrary.acct.testing1@gmail.com' - session[:can_book] = true if session[:uniqname] == 'mlibrary.acct.testing1@gmail.com' - + if !session[:uniqname] + patron = SessionPatron.new('mlibrary.acct.testing1@gmail.com') + patron.to_h.each{|k,v| session[k] = v} + end test_users = [ { label: 'Graduate student (few)', @@ -241,10 +241,20 @@ redirect 'https://apps.lib.umich.edu/my-account/favorites' end -get '/settings' do - #session[:uniqname] = 'tutor' #need to get this from cosign? - patron = Patron.for(uniqname: session[:uniqname]) - erb :patron, :locals => {patron: patron} +namespace '/settings' do + get '' do + patron = Patron.for(uniqname: session[:uniqname]) + erb :patron, :locals => {patron: patron} + end + post '/history' do + response = Patron.set_retain_history(uniqname: session[:uniqname], retain_history: params[:retain_history]) + if response.code == 200 + flash[:success] = "Success: History Setting Successfully Changed" + else + flash[:error] = "Error: #{response.message}" + end + redirect "/settings" + end end #TODO set up renew loan to handle renew in place with top part message??? post '/renew-loan' do @@ -262,12 +272,12 @@ post '/sms' do patron = Patron.for(uniqname: session[:uniqname]) - response = patron.update_sms(params["phone-number"]) + response = patron.update_sms(params["text-notifications"] == "on" ? params["sms-number"] : "") if response.code == 200 - if params["phone-number"] == '' - flash[:success] = "Success: SMS Successfully Removed" - else + if params["text-notifications"] == "on" flash[:success] = "Success: SMS Successfully Updated" + else + flash[:success] = "Success: SMS Successfully Removed" end else flash[:error] = "Error: #{response.message}" diff --git a/spec/fixtures/circ_history_user.json b/spec/fixtures/circ_history_user.json new file mode 100644 index 00000000..6c3b5771 --- /dev/null +++ b/spec/fixtures/circ_history_user.json @@ -0,0 +1,6 @@ +{ + "uniqname": "tutor", + "retain_history": true, + "confirmed": true, + "updated_at": "2021-05-20T11:47:48.199-04:00" +} diff --git a/spec/models/patron_spec.rb b/spec/models/patron_spec.rb index 4b523cdf..683fb13b 100644 --- a/spec/models/patron_spec.rb +++ b/spec/models/patron_spec.rb @@ -5,6 +5,7 @@ context "found uniqname" do before(:each) do @alma_response = JSON.parse(File.read('./spec/fixtures/mrio_user_alma.json')) + @circ_history_response = JSON.parse(File.read('./spec/fixtures/circ_history_user.json')) @patron_url = "users/mrio?user_id_type=all_unique&view=full&expand=none" end subject do @@ -12,6 +13,10 @@ url: @patron_url, body: @alma_response.to_json ) + stub_circ_history_get_request( + url: 'users/mrio', + output: @circ_history_response.to_json + ) Patron.for(uniqname: 'mrio') end context "#update_sms(sms_number)" do @@ -96,6 +101,24 @@ expect(subject.can_book?).to eq(false) end end + context "#retain_history?" do + it "returns correct true boolean" do + expect(subject.retain_history?).to eq(true) + end + it "returns correct false boolean" do + @circ_history_response["retain_history"] = false + expect(subject.retain_history?).to eq(false) + end + end + context "#confiremd_history_setting?" do + it "returns correct true boolean" do + expect(subject.confirmed_history_setting?).to eq(true) + end + it "returns correct false boolean" do + @circ_history_response["confirmed"] = false + expect(subject.confirmed_history_setting?).to eq(false) + end + end context "#sms_number" do it "returns sms number if preferred_sms is set" do expect(subject.sms_number).to eq('734-123-4567') @@ -146,6 +169,9 @@ url: @patron_url, body: @alma_response ) + stub_circ_history_get_request( + url: 'users/mrioaaa', + ) end subject do Patron.for(uniqname: 'mrioaaa') @@ -157,5 +183,11 @@ end end end - +end +describe Patron, ".set_retain_history" do + it "updates the circ history setting" do + stub = stub_circ_history_put_request(url: 'users/tutor', query: {retain_history: false}) + Patron.set_retain_history(uniqname: 'tutor', retain_history: 'false') + expect(stub).to have_been_requested + end end diff --git a/spec/requests_spec.rb b/spec/requests_spec.rb index 84a0c9fa..11813005 100644 --- a/spec/requests_spec.rb +++ b/spec/requests_spec.rb @@ -214,10 +214,25 @@ context "get /settings" do it "contains 'Settings'" do stub_alma_get_request(url: "users/tutor?expand=none&user_id_type=all_unique&view=full") + stub_circ_history_get_request(url: "users/tutor") get "/settings" expect(last_response.body).to include("Settings") end end + context "post /settings/history" do + before(:each) do + @patron_json = File.read("./spec/fixtures/mrio_user_alma.json") + stub_alma_get_request(url: "users/tutor?expand=none&user_id_type=all_unique&view=full", body: @patron_json) + stub_circ_history_get_request(url: "users/tutor") + stub_circ_history_put_request(url: "users/tutor", query: {retain_history: true}) + end + it "handles retain history" do + + post "/settings/history", {'retain_history' => 'true'} + follow_redirect! + expect(last_response.body).to include("History Setting Successfully Changed") + end + end #ToDO #context "post /renew-loan" do #before(:each) do @@ -252,21 +267,22 @@ before(:each) do @patron_json = File.read("./spec/fixtures/mrio_user_alma.json") stub_alma_get_request(url: "users/tutor?expand=none&user_id_type=all_unique&view=full", body: @patron_json) + stub_circ_history_get_request(url: 'users/tutor') end it "handles good phone number update" do - phone_number = '(734) 555-5555' + sms_number = '(734) 555-5555' new_phone_patron = JSON.parse(@patron_json) - new_phone_patron["contact_info"]["phone"][1]["phone_number"] = phone_number + new_phone_patron["contact_info"]["phone"][1]["phone_number"] = sms_number stub_alma_put_request(url: "users/mrio", input: new_phone_patron.to_json, output: new_phone_patron.to_json) - post "/sms", {'phone-number' => phone_number} + post "/sms", {'text-notifications' => 'on', 'sms-number' => sms_number} follow_redirect! expect(last_response.body).to include("SMS Successfully Updated") end it "handles bad phone number update" do - post "/sms", {'phone-number' => 'aaa'} + post "/sms", {'text-notifications' => 'on', 'sms-number' => 'aaa'} follow_redirect! expect(last_response.body).to include("is invalid") end @@ -274,7 +290,7 @@ new_phone_patron = JSON.parse(@patron_json) new_phone_patron["contact_info"]["phone"].delete_at(1) stub_alma_put_request(url: "users/mrio", input: new_phone_patron.to_json, output: new_phone_patron.to_json) - post "/sms", {'phone-number' => ''} + post "/sms", {'text-notifications' => 'off', 'sms-number' => ''} follow_redirect! expect(last_response.body).to include("SMS Successfully Removed") end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 53b7f5ca..d3d70387 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -116,16 +116,18 @@ def app() Sinatra::Application end Kernel.srand config.seed =end end -def stub_circ_history_get_request(url:, output: "{}",status: 200, query: {}) - stub_request(:get, "#{ENV["CIRCULATION_HISTORY_URL"]}/v1/#{url}").with( - headers: { - accept: 'application/json', - #ApiKey: ENV['ILLIAD_API_KEY'], - 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', - 'User-Agent'=>'Ruby' - }, - query: query, - ).to_return(body: output, status: status, headers: {content_type: 'application/json'}) +[:get, :put].each do |name| + define_method("stub_circ_history_#{name}_request") do |url:, output: "{}",status: 200, query: {}| + stub_request(name, "#{ENV["CIRCULATION_HISTORY_URL"]}/v1/#{url}").with( + headers: { + accept: 'application/json', + #ApiKey: ENV['ILLIAD_API_KEY'], + 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', + 'User-Agent'=>'Ruby' + }, + query: query, + ).to_return(body: output, status: status, headers: {content_type: 'application/json'}) + end end def stub_updater(params) query = Authenticator.params_with_signature(params: params) diff --git a/spec/views/layout.erb_spec.rb b/spec/views/layout.erb_spec.rb index 9f8cda40..de8626bd 100644 --- a/spec/views/layout.erb_spec.rb +++ b/spec/views/layout.erb_spec.rb @@ -2,7 +2,8 @@ describe "flash messages" do include Rack::Test::Methods before(:each) do - stub_alma_get_request(url: "users/tutor?expand=none&user_id_type=all_unique&view=full", body: '{}') + stub_alma_get_request(url: "users/mlibrary.acct.testing1@gmail.com?expand=none&user_id_type=all_unique&view=full", body: File.read('./spec/fixtures/mrio_user_alma.json')) + stub_circ_history_get_request(url: "users/mlibrary.acct.testing1@gmail.com", output: File.read('./spec/fixtures/circ_history_user.json')) end it "displays appropriate flash message" do #post "/session_switcher", {flash: {success: "it was successful"}} diff --git a/views/banner.erb b/views/banner.erb new file mode 100644 index 00000000..e7534da4 --- /dev/null +++ b/views/banner.erb @@ -0,0 +1,7 @@ +<% if session[:confirmed_history_setting] == false %> + +<% end %> diff --git a/views/layout.erb b/views/layout.erb index 30e9e10d..f405645e 100644 --- a/views/layout.erb +++ b/views/layout.erb @@ -1,3 +1,4 @@ + @@ -23,7 +24,7 @@ <%= erb :user_drop_down %> - + <%= erb :banner %>
<%= erb :navigation %>
diff --git a/views/patron.erb b/views/patron.erb index 48bcf1df..f683ca37 100644 --- a/views/patron.erb +++ b/views/patron.erb @@ -2,40 +2,122 @@
-

To update you name and address, visit Wolverine Access.

+

You can update your name and address at Wolverine Access.

+ +

To change document delivery preferences, visit your interlibrary loan account.

- - - -
Full name <%= patron.full_name %>
Address - <% patron.addresses do |address| %> -

<%= address %>

- <% end %> -
Email <%= patron.uniqname %>@umich.edu
-
-

Text notifications

+ +

Text Notifications

-