Skip to content

Commit

Permalink
Merge pull request #796 from pulibrary/airtable-job
Browse files Browse the repository at this point in the history
Add an airtable staff list job and URL
  • Loading branch information
christinach authored Jun 24, 2024
2 parents 1568f67 + b039e1b commit 45ba1d4
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 0 deletions.
12 changes: 12 additions & 0 deletions app/controllers/staff_directory_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,16 @@ def removed
format.text { send_data differ.ids.join(","), filename: "removed-staff.txt" }
end
end

def pul_staff_report
if Flipflop.air_table_staff_list?
job = AirTableStaff::StaffListJob.new
job.run
respond_to do |format|
format.csv { send_data job.read_most_recent_report, filename: "pul-staff-report.csv" }
end
else
render plain: 'Airtable-based staff list is turned off. Go to /features to turn it back on.'
end
end
end
41 changes: 41 additions & 0 deletions app/models/air_table_staff/staff_list_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true
module AirTableStaff
# This class is responsible for running and
# recording the results of Airtable-based
# CSV file generation
class StaffListJob < LibJob
def initialize(filename: nil)
super(category: 'AirTableStaffDirectory')
@report_filename = filename if filename
end

private

def handle(data_set:)
if Flipflop.air_table_staff_list?
return most_recent_dataset if most_recent_dataset && recent_enough?(most_recent_dataset&.data_file)
write_csv_to_disk
data_set.data_file = report_filename
else
data_set.data = 'Airtable-based staff list is typically scheduled for this time, but it is turned off. Go to /features to turn it back on.'
end
data_set.report_time = Time.zone.now
data_set
end

def write_csv_to_disk
File.open(report_filename, 'w') { |file| file.write(CSVBuilder.new.to_csv) }
end

def report_filename
@report_filename ||= begin
date_str = Time.zone.now.strftime('%Y%m%d%H%M')
File.join(Rails.configuration.staff_directory['report_directory'], "library_staff_#{date_str}.csv")
end
end

def recent_enough?(filename)
File.mtime(filename) > 2.hours.ago
end
end
end
4 changes: 4 additions & 0 deletions config/features.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@
feature :meter_files_sent_to_recap,
default: false,
description: "Send only the configured number of files to recap"

feature :air_table_staff_list,
default: false,
description: "Generate a staff list CSV based on the data in Airtable"
end
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
get '/data_sets/latest/:category', to: 'data_sets#latest', defaults: { format: 'text' }

get '/staff-directory', to: 'staff_directory#index', defaults: { format: 'csv' }
get '/pul-staff-report', to: 'staff_directory#pul_staff_report', defaults: { format: 'csv' }
get '/removed-staff', to: 'staff_directory#removed', defaults: { format: 'text' }

get '/library-events', to: 'library_events#index', defaults: { format: 'csv' }
Expand Down
28 changes: 28 additions & 0 deletions docs/air_table_staff/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Web Staff
This generates the staff report from data in an Airtable. It
creates a CSV that can then be used by the OIT WDS-hosted
library website.

## Sequence of events

The steps to maintain this list, as illustrated in the sequence diagram below.
1. Library Administration staff make updates in the Airtable UI.
1. Lib jobs requests a staff list from the Airtable API
1. The Airtable API returns a paginated JSON response
1. Lib-jobs publishes a CSV of the Airtable data
1. Drupal downloads the CSV
1. Drupal displays the staff list

```mermaid
---
title: Airtable-based Staff Report Generation by lib-jobs
---
sequenceDiagram
actor Library Administration
Library Administration->>Airtable UI: make updates
Lib jobs->>Airtable API: request staff list
Airtable API-->+Lib jobs: return paginated json
Lib jobs->>Lib jobs: publish a CSV of the Airtable data
Drupal->>Lib jobs: download the CSV
Drupal->>Drupal: display the staff list
```
101 changes: 101 additions & 0 deletions spec/models/air_table_staff/staff_list_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe AirTableStaff::StaffListJob, type: :model do
before do
stub_request(:get, 'https://api.airtable.com/v0/appv7XA5FWS7DG9oe/Synchronized%20Staff%20Directory%20View?view=Grid%20view')
.with(
headers: {
'Authorization' => 'Bearer FAKE_AIRTABLE_TOKEN'
}
)
.to_return(status: 200, body: File.read(file_fixture('air_table/records_no_offset.json')), headers: {})
end

context 'job is turned off' do
before do
allow(Flipflop).to receive(:air_table_staff_list?).and_return(true)
end
describe('CSV file generation') do
let(:file_path) { Pathname.new(Rails.root.join('tmp', "airtable_staff.csv")) }
let(:first_row) do
[
'123', 'ab123', '(123) 123-1234', 'Phillip Librarian', 'Librarian', 'Phillip', '[email protected]', '123 Stokes', 'Stokes', 'Stokes', nil, nil, nil, 'Virtual Reality'
]
end

around do |example|
File.delete(file_path) if File.exist?(file_path)
example.run
File.delete(file_path) if File.exist?(file_path)
end

it 'creates a CSV file' do
job = described_class.new(filename: file_path)
job.run
expect(File.exist?(file_path)).to be true
end

it 'the CSV file has a header row and a data row' do
job = described_class.new(filename: file_path)
job.run
expect(CSV.read(file_path).length).to eq(2)
end

it 'the first row of data is correct' do
job = described_class.new(filename: file_path)
job.run
expect(CSV.read(file_path).second).to eq(first_row)
end

context 'when run at a particular time' do
let(:run_time) { Time.zone.local(2022, 3, 14, 15, 9, 26) }
before do
allow(Time).to receive(:now).and_return(run_time)
end
it 'records that time in the database' do
job = described_class.new(filename: file_path)
job.run
expect(DataSet.order('created_at').last.report_time).to eq(run_time)
end
end

context 'when the process has already been run in the past hour' do
let(:original_data_set) do
FactoryBot.create(:data_set,
category: 'AirTableStaffDirectory',
data_file: 'library_staff_20221109.csv')
end
before do
original_data_set.save
allow(File).to receive(:exist?).and_return true
allow(File).to receive(:mtime).and_return 1.minute.ago
end

it 'returns the existing dataset' do
job = described_class.new(filename: file_path)
expect { job.run }.not_to change { DataSet.count }
end
end
end
end

context 'job is turned off' do
before do
allow(Flipflop).to receive(:air_table_staff_list?).and_return(false)
end
it 'logs that it is turned off' do
stafflist_job = described_class.new(filename: 'should_not_exist.csv')
stafflist_job.run
data_set = DataSet.last
expect(data_set.data).to eq('Airtable-based staff list is typically scheduled for this time, but it is turned off. Go to /features to turn it back on.')
end
it 'does not create a CSV file' do
stafflist_job = described_class.new(filename: 'should_not_exist.csv')
stafflist_job.run

expect(File.exist?('should_not_exist.csv')).to be false
end
end
end

0 comments on commit 45ba1d4

Please sign in to comment.