Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

TimeEntries#import and TimeEntries#upload for CSV importing

  • Loading branch information...
commit 0ebb078373a4fb094dfba1a251b9f224722fd262 1 parent 6fad3de
@taavo taavo authored
View
16 app/controllers/time_entries_controller.rb
@@ -47,6 +47,22 @@ def submit_all
end
end
+ def upload
+ @time_entries = TimeEntryImporter.read(params[:file].path)
+ end
+
+ def import
+ TimeEntry.transaction do
+ params[:time_entries].each do |i, t|
+ t[:company_id] = params[:companies][t[:company_id]]
+ TimeEntry.create(t)
+ end
+
+ flash[:notice] = 'Imported!'
+ redirect_to new_time_entry_path
+ end
+ end
+
def destroy
@time_entry.destroy
end
View
2  app/models/ability.rb
@@ -38,7 +38,7 @@ def initialize(user)
can [:create, :update, :destroy, :submit], TimeEntry, :user_id => user.id, :status => 'created'
can [:create, :update, :destroy, :submit], TimeEntry, :user_id => user.id, :status => 'rejected'
can :read, TimeEntry, :user_id => user.id
- can :submit_all, TimeEntry
+ can [:submit_all, :upload, :import], TimeEntry
end
can [:show, :update], User, :id => user.id
View
28 app/models/time_entry_importer.rb
@@ -0,0 +1,28 @@
+require 'csv'
+
+class TimeEntryImporter
+ def self.read(path)
+ data = []
+ company = nil
+
+ CSV.foreach(path) do |row|
+ next if row.empty?
+
+ if !row[0].empty?
+ company = row[0]
+ next
+ end
+
+ hours_parts = row[2].split(':')
+
+ data << {
+ :company_id => company,
+ :hours => hours_parts[0].to_f + hours_parts[1].to_f / 60,
+ :date => Date.parse(row[3]),
+ :description => row[4]
+ }
+ end
+
+ data
+ end
+end
View
9 app/views/time_entries/new.html.haml
@@ -31,6 +31,15 @@
= f.input :description
= f.submit 'Save'
+%h2 Import Time Entries
+= form_tag upload_time_entries_path, :multipart => true, :class => 'formtastic' do
+ %fieldset.inputs
+ %ol
+ %li.file.input.required#file_input
+ %label.label{:for => 'file'} File
+ = file_field_tag 'file'
+ = submit_tag 'Upload'
+
:javascript
$(function(){
$('tbody').on('ajax:success', '.hours .best_in_place', function() {
View
28 app/views/time_entries/upload.html.haml
@@ -0,0 +1,28 @@
+- companies = @time_entries.map { |t| t[:company_id] }.uniq
+- i = -1
+%h1 Import Time Entries
+
+= form_tag import_time_entries_path do
+ - companies.each do |company|
+ = select_tag "companies[#{company}]", options_from_collection_for_select(Company.active, 'id', 'name')
+ = company
+
+ %table
+ %tbody
+ - @time_entries.select { |t| t[:company_id] == company }.each do |t|
+ - i += 1
+ %tr
+ %td
+ = t[:date]
+ = hidden_field_tag "time_entries[#{i}][date]", t[:date]
+ %td
+ = t[:hours]
+ = hidden_field_tag "time_entries[#{i}][hours]", t[:hours]
+ %td
+ = t[:description]
+ = hidden_field_tag "time_entries[#{i}][description]", t[:description]
+ = hidden_field_tag "time_entries[#{i}][company_id]", t[:company_id]
+
+ = submit_tag 'Import'
+
+
View
9 config/routes.rb
@@ -8,7 +8,7 @@
end
resources :hour_logs
-
+
resources :time_entries do
member do
put 'submit'
@@ -16,7 +16,12 @@
put 'approve'
put 'hide'
end
- post 'submit_all', :on => :collection
+
+ collection do
+ post 'submit_all'
+ post 'upload'
+ post 'import'
+ end
end
resources :companies do
View
20 features/step_definitions/time_entry_steps.rb
@@ -117,3 +117,23 @@
page.should have_selector "##{dom_id @time_entry}"
end
end
+
+Then /^I should see company select boxes to assign the time entries from "([^"]*)"$/ do |path|
+ @time_entries_data = TimeEntryImporter.read(path)
+
+ companies = @time_entries_data.map {|t| t[:company_id]}.uniq
+
+ companies.each do |company|
+ page.should have_selector "select[name='companies[#{company}]']"
+ end
+end
+
+Then /^I should see the time entries from "([^"]*)"$/ do |path|
+ @time_entries_data ||= TimeEntryImporter.read(path)
+
+ @time_entries_data.each do |t|
+ page.should have_content t[:date]
+ page.should have_content t[:hours]
+ page.should have_content t[:description]
+ end
+end
View
14 features/time_entries/import_csv.feature
@@ -0,0 +1,14 @@
+Feature: Import CSV
+ As an employee of DD9
+ I want to upload time entries as a CSV
+ So I can record my hours without re-typing them
+
+ Scenario: I import a CSV
+ Given I am a staff member named "Joe Smith" with an email "user@test.com" and password "please"
+ When I sign in as "user@test.com/please"
+ And I attach the file "spec/test_data/time_entries.csv" to "file"
+ And I press "Upload"
+ Then I should see company select boxes to assign the time entries from "spec/test_data/time_entries.csv"
+ When I press "Import"
+ Then I should see "Imported!"
+ And I should see the time entries from "spec/test_data/time_entries.csv"
View
54 spec/controllers/time_entries_controller_spec.rb
@@ -16,7 +16,7 @@
end
describe "POST" do
- ['create', 'submit_all'].each do |action|
+ ['create', 'submit_all', 'upload', 'import'].each do |action|
describe "#{action}" do
it "is unauthorized" do
post action, :format => 'js'
@@ -75,7 +75,7 @@
end
describe "POST" do
- ['create', 'submit_all'].each do |action|
+ ['create', 'submit_all', 'upload', 'import'].each do |action|
describe "#{action}" do
it "redirects to root path" do
post action, :format => 'js'
@@ -177,6 +177,56 @@
end
end
+ describe "POST 'upload'" do
+ let(:file) { Rack::Test::UploadedFile.new('spec/test_data/time_entries.csv') }
+ let(:data) { TimeEntryImporter.read(file.path) }
+
+ context 'response' do
+ before do
+ post 'upload', :file => file
+ end
+
+ it { should be_success }
+ it { should render_template :upload }
+
+ it "assigns the time entries" do
+ assigns(:time_entries).should == data.map { |t| HashWithIndifferentAccess.new(t) }
+ end
+ end
+ end
+
+ describe "POST 'import'" do
+ let(:companies) { {'Company A' => 1007, 'Company B' => 1114 } }
+ let(:time_entries) {
+ { "0" => {:company_id => 'Company A', :date => Date.today, :hours => 1.5, :description => 'first task'},
+ "1" => {:company_id => 'Company B', :date => Date.today, :hours => 0.75, :description => 'second task'},
+ "2" => {:company_id => 'Company B', :date => Date.today, :hours => 3.75, :description => 'third task'}}
+ }
+
+ it "saves each time entry" do
+ time_entries.each do |i, t|
+ expected_param = HashWithIndifferentAccess.new
+
+ t.merge(:company_id => companies[t[:company_id]]).each do |k,v|
+ expected_param[k] = v.to_s
+ end
+
+ TimeEntry.should_receive(:create).with(expected_param).and_return(true)
+ end
+
+ post "import", :companies => companies, :time_entries => time_entries
+ end
+
+ context "response" do
+ before do
+ TimeEntry.stub(:create) { true }
+ post "import", :companies => companies, :time_entries => time_entries
+ end
+
+ it { should redirect_to new_time_entry_path }
+ end
+ end
+
describe "PUT 'submit'" do
it "calls submit method" do
time_entry.should_receive(:submit)
View
9 spec/test_data/time_entries.csv
@@ -0,0 +1,9 @@
+Company A
+"","",00:45,01/02/12,did something
+"","",00:15,01/03/12,did something else
+Company B
+"","",00:30,01/01/12,walked the dog
+"","",00:15,01/03/12,looped the loop
+Company C
+"","",01:45,01/01/12,I am a pony
+"","",02:15,01/02/12,pork and beans
View
4 spec/views/time_entries/new.html.haml_spec.rb
@@ -33,6 +33,10 @@
it { should have_selector "form[method='post'][action='#{time_entries_path}']" }
it { should have_selector "form input[type='submit']"}
it { should have_button "Submit All" }
+
+ it { should have_selector "form[method='post'][action='#{upload_time_entries_path}'][enctype='multipart/form-data']" }
+ it { should have_selector "form input[type='file'][name='file']" }
+ it { should have_button "Upload" }
end
View
49 spec/views/time_entries/upload.html.haml_spec.rb
@@ -0,0 +1,49 @@
+describe 'spec_helper'
+
+describe "time_entries/upload.html.haml" do
+ subject { rendered }
+ let (:user) { Factory.stub(:user, :role => 'staff') }
+
+ before(:all) do
+ @data = TimeEntryImporter.read('spec/test_data/time_entries.csv')
+ @companies = @data.map { |t| t[:company_id] }
+ end
+
+ before(:each) do
+ stub_sign_in user
+ assign(:time_entries, @data)
+ render
+ end
+
+ it { should have_selector "form[method='post'][action='#{import_time_entries_path}']" }
+
+ it "displays all the companies" do
+ @companies.each do |company|
+ rendered.should have_content company
+ end
+ end
+
+ it "has select boxes for each company" do
+ @companies.each do |company|
+ rendered.should have_selector "select[name='companies[#{company}]']"
+ end
+ end
+
+ it "displays all the time entries" do
+ @data.each do |t|
+ rendered.should have_content t[:date]
+ rendered.should have_content t[:hours]
+ rendered.should have_content t[:description]
+ end
+ end
+
+ it "has hidden fields for all the time entries" do
+ @data.each do |t|
+ rendered.should have_selector "input[type='hidden'][value='#{t[:date]}']"
+ rendered.should have_selector "input[type='hidden'][value='#{t[:hours]}']"
+ rendered.should have_selector "input[type='hidden'][value='#{t[:description]}']"
+ end
+ end
+
+ it { should have_button "Import" }
+end
Please sign in to comment.
Something went wrong with that request. Please try again.