├── log └── .keep ├── storage └── .keep ├── tmp └── .keep ├── vendor └── .keep ├── lib ├── assets │ └── .keep ├── tasks │ ├── .keep │ └── scheduler.rake ├── templates │ └── erb │ │ └── scaffold │ │ └── _form.html.erb └── capistrano │ └── tasks │ └── envvars.rake ├── .ruby-gemset ├── .ruby-version ├── app ├── assets │ ├── images │ │ ├── .keep │ │ ├── fogg.png │ │ ├── fogg1.png │ │ ├── gary_web.png │ │ ├── abalone_logo.png │ │ ├── icons │ │ │ ├── plus.png │ │ │ ├── plus.svg │ │ │ └── pencil.svg │ │ ├── Burgess white ab 1.png │ │ └── abalone_upload_job.jpeg │ ├── config │ │ └── manifest.js │ ├── stylesheets │ │ ├── _utils.scss │ │ ├── animals.scss │ │ ├── pages │ │ │ └── operations.scss │ │ ├── file_uploads.scss │ │ ├── _mixins.scss │ │ ├── reports_kit.scss │ │ └── _variables.scss │ └── javascripts │ │ └── application.js ├── models │ ├── concerns │ │ ├── .keep │ │ ├── raw.rb │ │ ├── organization_scope.rb │ │ └── csv_exportable.rb │ ├── current.rb │ ├── operation_batch.rb │ ├── application_record.rb │ ├── animals_shl_number.rb │ ├── shl_number.rb │ ├── measurement_event.rb │ ├── temporary_file.rb │ ├── file_upload.rb │ ├── exit_type.rb │ ├── measurement_type.rb │ ├── organization.rb │ ├── location.rb │ ├── user.rb │ ├── ability.rb │ ├── operation.rb │ ├── enclosure.rb │ ├── cohort.rb │ ├── facility.rb │ └── processed_file.rb ├── controllers │ ├── concerns │ │ └── .keep │ ├── reports_controller.rb │ ├── operations_controller.rb │ ├── home_controller.rb │ ├── cohort_imports_controller.rb │ ├── passwords_controller.rb │ ├── application_controller.rb │ └── users_controller.rb ├── views │ ├── layouts │ │ ├── mailer.text.erb │ │ └── mailer.html.erb │ ├── facilities │ │ ├── show.json.jbuilder │ │ ├── index.json.jbuilder │ │ ├── _facility.json.jbuilder │ │ ├── new.html.erb │ │ ├── edit.html.erb │ │ └── _form.html.erb │ ├── devise │ │ ├── mailer │ │ │ ├── password_change.html.erb │ │ │ ├── confirmation_instructions.html.erb │ │ │ ├── unlock_instructions.html.erb │ │ │ ├── email_changed.html.erb │ │ │ └── reset_password_instructions.html.erb │ │ ├── unlocks │ │ │ └── new.html.erb │ │ ├── confirmations │ │ │ └── new.html.erb │ │ ├── passwords │ │ │ ├── edit.html.erb │ │ │ └── new.html.erb │ │ └── sessions │ │ │ └── new.html.erb │ ├── home │ │ └── _footer.html.erb │ ├── users │ │ ├── shared │ │ │ ├── _error_messages.html.erb │ │ │ └── _links.html.erb │ │ ├── new.html.erb │ │ ├── edit.html.erb │ │ └── show.html.erb │ ├── enclosures │ │ ├── new.html.erb │ │ ├── edit.html.erb │ │ ├── csv_upload.html.erb │ │ └── _form.html.erb │ ├── animals │ │ ├── new.html.erb │ │ ├── edit.html.erb │ │ └── csv_upload.html.erb │ ├── cohorts │ │ ├── new.html.erb │ │ └── edit.html.erb │ ├── measurement_types │ │ ├── new.html.erb │ │ ├── edit.html.erb │ │ ├── _form.html.erb │ │ └── show.html.erb │ ├── exit_types │ │ ├── new.html.erb │ │ ├── edit.html.erb │ │ └── _form.html.erb │ ├── locations │ │ ├── edit.html.erb │ │ ├── new.html.erb │ │ └── _form.html.erb │ ├── measurements │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── cohort_imports │ │ └── new.html.erb │ ├── file_uploads │ │ ├── upload.html.erb │ │ ├── show_processing_csv_errors.html.erb │ │ └── show.html.erb │ ├── reports │ │ └── index.html.erb │ └── passwords │ │ └── edit.html.erb ├── javascript │ ├── packs │ │ ├── tailwind.js │ │ ├── styles.scss │ │ ├── measurements.js │ │ └── application.js │ └── controllers │ │ ├── navbar_controller.js │ │ ├── index.js │ │ ├── hello_controller.js │ │ └── animals_controller.js ├── helpers │ ├── facilities_helper.rb │ ├── application_helper.rb │ └── file_uploads_helper.rb ├── jobs │ ├── application_job.rb │ ├── measurement_job.rb │ └── mortality_event_job.rb ├── mailers │ └── application_mailer.rb ├── lib │ ├── aggregates.rb │ └── date_parser.rb └── forms │ └── csv_import_form.rb ├── .browserslistrc ├── public ├── apple-touch-icon.png ├── apple-touch-icon-precomposed.png ├── favicon.ico ├── images │ └── abalone-hero.png ├── robots.txt └── samples │ ├── cohort.csv │ ├── animal.csv │ ├── enclosure.csv │ ├── length_gonad_score_template.csv │ └── animal_count_measurements_template.csv ├── .gitattributes ├── .rspec ├── spec ├── fixtures │ ├── files │ │ ├── tiny_custom_measurement.json │ │ ├── enclosures.csv │ │ ├── custom_measurements_multiple_models.csv │ │ ├── animals.csv │ │ ├── basic_custom_measurement_invalid.csv │ │ ├── basic_custom_mortality_events.csv │ │ ├── basic_custom_measurement.csv │ │ └── basic_custom_measurement_with_spaces.csv │ └── animals.csv ├── support │ ├── csv │ │ ├── invalid_headers.csv │ │ └── basic_custom_measurement_invalid.csv │ ├── factory_bot.rb │ ├── file_upload_helpers.rb │ └── shared_contexts │ │ └── rake.rb ├── factories │ ├── shl_numbers.rb │ ├── temporary_files.rb │ ├── operations.rb │ ├── organizations.rb │ ├── animals_shl_numbers.rb │ ├── exit_types.rb │ ├── operation_batches.rb │ ├── measurement_events.rb │ ├── measurement_types.rb │ ├── enclosures.rb │ ├── cohorts.rb │ ├── mortality_events.rb │ ├── blazer │ │ └── queries.rb │ ├── facility.rb │ ├── file_upload.rb │ ├── users.rb │ ├── animals.rb │ ├── locations.rb │ ├── measurements.rb │ └── processed_files.rb ├── controllers │ ├── operations_controller_spec.rb │ ├── reports_controller_spec.rb │ └── blazer │ │ └── queries_controller_spec.rb ├── models │ ├── temporary_file_spec.rb │ ├── operation_batch_spec.rb │ ├── animals_shl_number_spec.rb │ ├── shl_number_spec.rb │ ├── organization_spec.rb │ ├── measurement_event_spec.rb │ ├── file_upload_spec.rb │ ├── user_spec.rb │ ├── enclosure_spec.rb │ ├── location_spec.rb │ ├── exit_type_spec.rb │ └── processed_file_spec.rb ├── system │ ├── reporting_spec.rb │ ├── csv_upload_confirmation_spec.rb │ ├── new_measurements_spec.rb │ ├── download_csv_file_spec.rb │ ├── locations │ │ ├── index_spec.rb │ │ ├── new_spec.rb │ │ ├── update_spec.rb │ │ ├── show_spec.rb │ │ └── destroy_spec.rb │ ├── facilities │ │ ├── create_spec.rb │ │ ├── destroy_spec.rb │ │ ├── show_spec.rb │ │ └── update_spec.rb │ ├── cohorts │ │ ├── delete_spec.rb │ │ ├── create_spec.rb │ │ ├── update_spec.rb │ │ └── show_spec.rb │ ├── animals │ │ ├── create_spec.rb │ │ ├── destroy_spec.rb │ │ ├── upload_spec.rb │ │ └── show_spec.rb │ ├── exit_types │ │ ├── show_spec.rb │ │ ├── destroy_spec.rb │ │ ├── create_spec.rb │ │ └── index_spec.rb │ ├── enclosures │ │ ├── destroy_spec.rb │ │ ├── show_spec.rb │ │ ├── new_spec.rb │ │ └── update_spec.rb │ ├── csv_upload_pagination_spec.rb │ ├── passwords │ │ └── update_password_spec.rb │ ├── measurement_types │ │ ├── show_spec.rb │ │ ├── new_spec.rb │ │ └── destroy_spec.rb │ ├── delete_failed_file_upload_spec.rb │ └── users │ │ ├── update_spec.rb │ │ ├── delete_spec.rb │ │ ├── new_spec.rb │ │ └── show_spec.rb ├── jobs │ └── measurement_job_spec.rb └── requests │ └── measurements_index_spec.rb ├── Procfile ├── db ├── migrate │ ├── .DS_Store │ ├── 20200922142446_add_role_to_users.rb │ ├── 20200906162226_add_name_to_family.rb │ ├── 20200922202022_drop_pedigrees.rb │ ├── 20200924153501_add_cohort_id_to_animals.rb │ ├── 20200923153522_drop_wild_collections.rb │ ├── 20200924153007_add_location_id_to_enclosures.rb │ ├── 20200922172429_drop_spawning_successes.rb │ ├── 20200922185040_drop_mortality_tracking.rb │ ├── 20200922161505_add_organization_to_tanks.rb │ ├── 20200923124428_drop_population_estimates.rb │ ├── 20191031181501_fix_column_typo.rb │ ├── 20200405184451_add_measurement_date_to_measurements.rb │ ├── 20200421024915_remove_measurements_from_tanks.rb │ ├── 20200921205459_add_organization_to_families.rb │ ├── 20200923152816_remove_tank_from_measurement_events.rb │ ├── 20200924150252_remove_facility_id_from_enclosures.rb │ ├── 20200922194114_add_unique_constraint_animals.rb │ ├── 20200922195722_add_organization_to_operations.rb │ ├── 20200922203142_drop_tagged_animal_assessments.rb │ ├── 20200923130852_drop_consolidation_reports_table.rb │ ├── 20200923154527_add_measurement_type_to_measurements.rb │ ├── 20200921193217_add_organization_to_animals.rb │ ├── 20200922182535_drop_untagged_animal_assessments.rb │ ├── 20200922195812_add_organization_to_measurements.rb │ ├── 20200923131001_add_unique_constraint_tanks.rb │ ├── 20200923134705_drop_post_settlement_inventories.rb │ ├── 20210930125236_add_exit_type_to_mortality_event.rb │ ├── 20200618201524_add_processed_file_to_measurements.rb │ ├── 20211010204649_add_organization_to_processed_file.rb │ ├── 20200109184825_add_temporary_file_id_to_processed_files.rb │ ├── 20200418143117_create_organizations.rb │ ├── 20211008231933_add_organization_to_mortality_event.rb │ ├── 20200204044451_remove_original_filename_from_processed_files.rb │ ├── 20200418143934_add_org_indexes.rb │ ├── 20211011181422_fix_versions_table.rb │ ├── 20211014153539_add_processed_file_to_mortality_event.rb │ ├── 20190726185815_change_spawning_successes_shl_number_to_string.rb │ ├── 20200922195820_add_organization_to_measurement_events.rb │ ├── 20200922213826_create_sex_enum.rb │ ├── 20200924145521_create_shl_numbers.rb │ ├── 20200405190459_add_dates_to_operations.rb │ ├── 20200109183112_create_temporary_files.rb │ ├── 20200506173804_add_tank_relationship_to_family.rb │ ├── 20190726135958_create_facilities.rb │ ├── 20190726191807_rename_spawing_successes_shl_number_to_shl_case_number.rb │ ├── 20200506173350_add_family_relationship_to_operations.rb │ ├── 20190726192306_rename_population_estimates_shl_number_to_shl_case_number.rb │ ├── 20200203060633_change_population_estimate_abundace_field_type.rb │ ├── 20200404224702_create_families.rb │ ├── 20200404212335_create_tanks.rb │ ├── 20211002202547_add_collected_to_animals.rb │ ├── 20200923202015_change_tanks_to_enclosures.rb │ ├── 20200924145950_create_animals_shl_numbers.rb │ ├── 20210930124942_create_exit_types.rb │ ├── 20211002202438_add_default_entry_point_to_animals.rb │ ├── 20200923190307_change_families_to_cohort.rb │ ├── 20200924210137_rework_animal_tags.rb │ ├── 20200405182059_create_operations.rb │ ├── 20200405181908_create_measurements.rb │ ├── 20200905154358_add_animal_family_tank_to_measurement.rb │ ├── 20200922201229_create_file_uploads.rb │ ├── 20200923150158_create_measurement_types.rb │ ├── 20200924151343_create_locations.rb │ ├── 20201020103647_create_mortality_events_table.rb │ ├── 20200513172140_create_operation_batches.rb │ ├── 20200404224902_create_consolidation_reports.rb │ ├── 20200404222231_create_post_settlement_inventories.rb │ ├── 20200404225209_create_animals.rb │ ├── 20200420193620_create_measurement_events.rb │ ├── 20190727173430_create_processed_files.rb │ ├── 20190726184116_create_pedigrees.rb │ ├── 20190726144739_create_spawning_success.rb │ ├── 20190726183847_create_population_estimates.rb │ ├── 20200922214542_migrate_animal_sex_to_postgres_enum.rb │ ├── 20200923223021_add_object_changes_to_versions.rb │ ├── 20200923151254_add_references_and_attributes_to_measurements.rb │ ├── 20211002202239_update_animals_column_names.rb │ ├── 20210213212906_create_active_storage_variant_records.active_storage.rb │ ├── 20190727201619_add_processed_file_id_to_data.rb │ ├── 20200506175333_rename_operations_operation_type_to_prevent_rails_naming_collisions.rb │ ├── 20190726191528_create_untagged_animal_assessments.rb │ ├── 20190726211013_change_wild_collection_field_types.rb │ ├── 20190726182945_create_mortality_tracking.rb │ ├── 20210213212905_add_service_name_to_active_storage_blobs.active_storage.rb │ ├── 20190726191836_create_tagged_animal_assessments.rb │ ├── 20190726182100_create_wild_collections.rb │ ├── 20200922203301_create_active_storage_tables.active_storage.rb │ └── 20200927001420_install_blazer.rb └── sample_data_files │ └── measurement │ └── basic_measurement.csv ├── bin ├── dbinit │ └── 001-create-databases.sql ├── rake ├── bundle ├── delayed_job ├── rails ├── webpack ├── webpack-dev-server ├── yarn ├── update └── setup ├── config ├── initializers │ ├── delayed_job.rb │ ├── mime_types.rb │ ├── filter_parameter_logging.rb │ ├── application_controller_renderer.rb │ ├── cookies_serializer.rb │ ├── wrap_parameters.rb │ ├── backtrace_silencers.rb │ ├── assets.rb │ └── inflections.rb ├── webpack │ ├── environment.js │ ├── test.js │ ├── production.js │ └── development.js ├── environment.rb ├── boot.rb ├── reports_kit │ └── reports │ │ └── total_animals_by_cohort.yml ├── credentials.yml.enc ├── locales │ ├── simple_form.en.yml │ └── en.yml └── storage.yml ├── docker-entrypoint.sh ├── config.ru ├── Rakefile ├── .github ├── dependabot.yml ├── workflows │ ├── rubocop.yml │ └── brakeman.yml └── FUNDING.yml ├── package.json ├── postcss.config.js ├── .gitignore ├── LICENSE.md └── Dockerfile /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | -global -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.0.3 2 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | text=auto eol=lf -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/fixtures/files/tiny_custom_measurement.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/javascript/packs/tailwind.js: -------------------------------------------------------------------------------- 1 | import "./styles.scss" 2 | -------------------------------------------------------------------------------- /app/helpers/facilities_helper.rb: -------------------------------------------------------------------------------- 1 | module FacilitiesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /spec/support/csv/invalid_headers.csv: -------------------------------------------------------------------------------- 1 | some,invalid,headers 2 | value,value1,value2 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyforgood/abalone/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bin/rails server -p $PORT -e $RAILS_ENV 2 | worker: bundle exec rake jobs:work 3 | -------------------------------------------------------------------------------- /db/migrate/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyforgood/abalone/HEAD/db/migrate/.DS_Store -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | include Pagy::Frontend 3 | end 4 | -------------------------------------------------------------------------------- /app/views/facilities/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.partial! "facilities/facility", facility: @facility 2 | -------------------------------------------------------------------------------- /app/assets/images/fogg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyforgood/abalone/HEAD/app/assets/images/fogg.png -------------------------------------------------------------------------------- /app/models/current.rb: -------------------------------------------------------------------------------- 1 | class Current < ActiveSupport::CurrentAttributes 2 | attribute :user 3 | end 4 | -------------------------------------------------------------------------------- /bin/dbinit/001-create-databases.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE abalone_development; 2 | CREATE DATABASE abalone_test; -------------------------------------------------------------------------------- /app/assets/images/fogg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyforgood/abalone/HEAD/app/assets/images/fogg1.png -------------------------------------------------------------------------------- /app/models/operation_batch.rb: -------------------------------------------------------------------------------- 1 | class OperationBatch < ApplicationRecord 2 | has_many :operations 3 | end 4 | -------------------------------------------------------------------------------- /app/assets/images/gary_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyforgood/abalone/HEAD/app/assets/images/gary_web.png -------------------------------------------------------------------------------- /app/views/facilities/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array! @facilities, partial: "facilities/facility", as: :facility 2 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /public/images/abalone-hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyforgood/abalone/HEAD/public/images/abalone-hero.png -------------------------------------------------------------------------------- /spec/support/factory_bot.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.include FactoryBot::Syntax::Methods 3 | end 4 | -------------------------------------------------------------------------------- /app/assets/images/abalone_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyforgood/abalone/HEAD/app/assets/images/abalone_logo.png -------------------------------------------------------------------------------- /app/assets/images/icons/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyforgood/abalone/HEAD/app/assets/images/icons/plus.png -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /config/initializers/delayed_job.rb: -------------------------------------------------------------------------------- 1 | Delayed::Worker.logger = Logger.new(File.join(Rails.root, 'log', 'delayed_job.log')) 2 | -------------------------------------------------------------------------------- /config/webpack/environment.js: -------------------------------------------------------------------------------- 1 | const { environment } = require('@rails/webpacker') 2 | 3 | module.exports = environment 4 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /spec/factories/shl_numbers.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :shl_number do 3 | animals_shl_number 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/factories/temporary_files.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :temporary_file do 3 | contents { "" } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/assets/images/Burgess white ab 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyforgood/abalone/HEAD/app/assets/images/Burgess white ab 1.png -------------------------------------------------------------------------------- /app/assets/images/abalone_upload_job.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyforgood/abalone/HEAD/app/assets/images/abalone_upload_job.jpeg -------------------------------------------------------------------------------- /app/javascript/packs/styles.scss: -------------------------------------------------------------------------------- 1 | @import "tailwindcss/base"; 2 | @import "tailwindcss/components"; 3 | @import "tailwindcss/utilities"; -------------------------------------------------------------------------------- /spec/factories/operations.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :operation do 3 | enclosure 4 | organization 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /app/models/animals_shl_number.rb: -------------------------------------------------------------------------------- 1 | class AnimalsShlNumber < ApplicationRecord 2 | belongs_to :animal 3 | belongs_to :shl_number 4 | end 5 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /spec/controllers/operations_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe OperationsController, type: :controller do 4 | end 5 | -------------------------------------------------------------------------------- /spec/factories/organizations.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :organization do 3 | name { Faker::Name.unique.name } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'from@example.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /spec/factories/animals_shl_numbers.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :animals_shl_number do 3 | animal 4 | shl_number 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/factories/exit_types.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :exit_type do 3 | name { 'Incidental' } 4 | organization 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/models/shl_number.rb: -------------------------------------------------------------------------------- 1 | class ShlNumber < ApplicationRecord 2 | has_many :animals_shl_numbers 3 | has_many :animals, through: :animals_shl_numbers 4 | end 5 | -------------------------------------------------------------------------------- /spec/factories/operation_batches.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :operation_batch do 3 | sequence(:name) { |n| "Operation #{n}" } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/facilities/_facility.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! facility, :id, :name, :code, :created_at, :updated_at 2 | json.url facility_url(facility, format: :json) 3 | -------------------------------------------------------------------------------- /spec/factories/measurement_events.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :measurement_event do 3 | name { "MyString" } 4 | organization 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/assets/stylesheets/_utils.scss: -------------------------------------------------------------------------------- 1 | // Breakpoints for responsiveness 2 | $mobile: 480px; 3 | $tablet: 768px; 4 | $desktop-min: $tablet + 1px; 5 | $desktop-max: 1024px; 6 | -------------------------------------------------------------------------------- /app/controllers/reports_controller.rb: -------------------------------------------------------------------------------- 1 | class ReportsController < ApplicationController 2 | # def index; end 3 | end 4 | # 5 | # Replaced by blazer reporting - 1/24/21 6 | -------------------------------------------------------------------------------- /app/models/measurement_event.rb: -------------------------------------------------------------------------------- 1 | class MeasurementEvent < ApplicationRecord 2 | include OrganizationScope 3 | 4 | has_many :measurements, dependent: :destroy 5 | end 6 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | if [ "$1" = 'abalone' ]; then 5 | exec su-exec app bundle exec rails s -b 0.0.0.0 6 | else 7 | exec "$@" 8 | fi 9 | -------------------------------------------------------------------------------- /spec/models/temporary_file_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe TemporaryFile, type: :model do 4 | it { should have_one(:processed_file) } 5 | end 6 | -------------------------------------------------------------------------------- /app/models/temporary_file.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TemporaryFile < ApplicationRecord 4 | has_one :processed_file, inverse_of: :temporary_file 5 | end 6 | -------------------------------------------------------------------------------- /app/views/devise/mailer/password_change.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

We're contacting you to notify you that your password has been changed.

4 | -------------------------------------------------------------------------------- /spec/models/operation_batch_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe OperationBatch, type: :model do 4 | it { is_expected.to have_many(:operations) } 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200922142446_add_role_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddRoleToUsers < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :users, :role, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/factories/measurement_types.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :measurement_type do 3 | name { "length" } 4 | unit { "cm" } 5 | organization 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20200906162226_add_name_to_family.rb: -------------------------------------------------------------------------------- 1 | class AddNameToFamily < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :families, :name, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200922202022_drop_pedigrees.rb: -------------------------------------------------------------------------------- 1 | class DropPedigrees < ActiveRecord::Migration[6.0] 2 | def up 3 | drop_table :pedigrees 4 | end 5 | 6 | def down; end 7 | end 8 | -------------------------------------------------------------------------------- /spec/factories/enclosures.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :enclosure do 3 | sequence(:name) { |n| "Enclosure #{n}" } 4 | location 5 | organization 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /config/webpack/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /config/webpack/production.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'production' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /db/migrate/20200924153501_add_cohort_id_to_animals.rb: -------------------------------------------------------------------------------- 1 | class AddCohortIdToAnimals < ActiveRecord::Migration[6.0] 2 | def change 3 | add_reference :animals, :cohort, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/webpack/development.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /db/migrate/20200923153522_drop_wild_collections.rb: -------------------------------------------------------------------------------- 1 | class DropWildCollections < ActiveRecord::Migration[6.0] 2 | def up 3 | drop_table :wild_collections 4 | end 5 | 6 | def down; end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20200924153007_add_location_id_to_enclosures.rb: -------------------------------------------------------------------------------- 1 | class AddLocationIdToEnclosures < ActiveRecord::Migration[6.0] 2 | def change 3 | add_reference :enclosures, :location 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/concerns/raw.rb: -------------------------------------------------------------------------------- 1 | module Raw 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | scope :raw, -> { where(raw: true) } 6 | scope :not_raw, -> { where(raw: false) } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/models/file_upload.rb: -------------------------------------------------------------------------------- 1 | class FileUpload < ApplicationRecord 2 | include OrganizationScope 3 | 4 | belongs_to :user 5 | has_one_attached :file 6 | 7 | validates_presence_of :status, :user 8 | end 9 | -------------------------------------------------------------------------------- /bin/delayed_job: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment')) 4 | require 'delayed/command' 5 | Delayed::Command.new(ARGV).daemonize 6 | -------------------------------------------------------------------------------- /db/migrate/20200922172429_drop_spawning_successes.rb: -------------------------------------------------------------------------------- 1 | class DropSpawningSuccesses < ActiveRecord::Migration[6.0] 2 | def up 3 | drop_table :spawning_successes 4 | end 5 | 6 | def down; end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20200922185040_drop_mortality_tracking.rb: -------------------------------------------------------------------------------- 1 | class DropMortalityTracking < ActiveRecord::Migration[6.0] 2 | def up 3 | drop_table :mortality_trackings 4 | end 5 | 6 | def down; end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/exit_type.rb: -------------------------------------------------------------------------------- 1 | class ExitType < ApplicationRecord 2 | include OrganizationScope 3 | 4 | has_many :mortality_events, dependent: :restrict_with_error 5 | 6 | validates :name, presence: true 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20200922161505_add_organization_to_tanks.rb: -------------------------------------------------------------------------------- 1 | class AddOrganizationToTanks < ActiveRecord::Migration[6.0] 2 | def change 3 | add_reference :tanks, :organization, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200923124428_drop_population_estimates.rb: -------------------------------------------------------------------------------- 1 | class DropPopulationEstimates < ActiveRecord::Migration[6.0] 2 | def up 3 | drop_table :population_estimates 4 | end 5 | 6 | def down; end 7 | end 8 | -------------------------------------------------------------------------------- /spec/factories/cohorts.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :cohort do 3 | female factory: :female 4 | male factory: :male 5 | organization 6 | name { Faker::Name.unique.name } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20191031181501_fix_column_typo.rb: -------------------------------------------------------------------------------- 1 | class FixColumnTypo < ActiveRecord::Migration[5.2] 2 | def change 3 | rename_column :wild_collections, :collection_coodinates, :collection_coordinates 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/home/_footer.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /db/migrate/20200405184451_add_measurement_date_to_measurements.rb: -------------------------------------------------------------------------------- 1 | class AddMeasurementDateToMeasurements < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :measurements, :date, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200421024915_remove_measurements_from_tanks.rb: -------------------------------------------------------------------------------- 1 | class RemoveMeasurementsFromTanks < ActiveRecord::Migration[5.2] 2 | def change 3 | remove_reference :measurements, :tank, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200921205459_add_organization_to_families.rb: -------------------------------------------------------------------------------- 1 | class AddOrganizationToFamilies < ActiveRecord::Migration[6.0] 2 | def change 3 | add_reference :families, :organization, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200923152816_remove_tank_from_measurement_events.rb: -------------------------------------------------------------------------------- 1 | class RemoveTankFromMeasurementEvents < ActiveRecord::Migration[6.0] 2 | def change 3 | remove_column :measurement_events, :tank_id 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200924150252_remove_facility_id_from_enclosures.rb: -------------------------------------------------------------------------------- 1 | class RemoveFacilityIdFromEnclosures < ActiveRecord::Migration[6.0] 2 | def change 3 | remove_column :enclosures, :facility_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/files/enclosures.csv: -------------------------------------------------------------------------------- 1 | facility_code,location_name,name 2 | AOP,SeaLab2020,Enclosure Number 1 3 | AOP,Atlantis,Enclosure Number 2 4 | AOP,SeaWorld,Enclosure Number 2 5 | QQQ,OceanPlace,Enclosure Number 1 6 | ,, 7 | -------------------------------------------------------------------------------- /app/models/measurement_type.rb: -------------------------------------------------------------------------------- 1 | class MeasurementType < ApplicationRecord 2 | include OrganizationScope 3 | 4 | has_many :measurements, dependent: :restrict_with_error 5 | 6 | validates :name, :unit, presence: true 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20200922194114_add_unique_constraint_animals.rb: -------------------------------------------------------------------------------- 1 | class AddUniqueConstraintAnimals < ActiveRecord::Migration[6.0] 2 | def change 3 | add_index :animals, [:pii_tag, :organization_id], unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200922195722_add_organization_to_operations.rb: -------------------------------------------------------------------------------- 1 | class AddOrganizationToOperations < ActiveRecord::Migration[6.0] 2 | def change 3 | add_reference :operations, :organization, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200922203142_drop_tagged_animal_assessments.rb: -------------------------------------------------------------------------------- 1 | class DropTaggedAnimalAssessments < ActiveRecord::Migration[6.0] 2 | def up 3 | drop_table :tagged_animal_assessments 4 | end 5 | 6 | def down; end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20200923130852_drop_consolidation_reports_table.rb: -------------------------------------------------------------------------------- 1 | class DropConsolidationReportsTable < ActiveRecord::Migration[6.0] 2 | def up 3 | drop_table :consolidation_reports 4 | end 5 | 6 | def down; end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20200923154527_add_measurement_type_to_measurements.rb: -------------------------------------------------------------------------------- 1 | class AddMeasurementTypeToMeasurements < ActiveRecord::Migration[6.0] 2 | def change 3 | add_reference :measurements, :measurement_type 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/files/custom_measurements_multiple_models.csv: -------------------------------------------------------------------------------- 1 | date,subject_type,measurement_type,value,measurement_event,enclosure_name,cohort_name,tag,reason 2 | 9/01/21,Cohort,count,12,Monthly Count,Test Enclosure,New Cohort, 3 | -------------------------------------------------------------------------------- /app/assets/stylesheets/animals.scss: -------------------------------------------------------------------------------- 1 | #animal_collected_true, 2 | #animal_collected_false { 3 | margin-right: 0.25rem; 4 | } 5 | 6 | .animals__collected-fieldset { 7 | label { 8 | margin-right: 2rem; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /db/migrate/20200921193217_add_organization_to_animals.rb: -------------------------------------------------------------------------------- 1 | class AddOrganizationToAnimals < ActiveRecord::Migration[6.0] 2 | def change 3 | add_reference :animals, :organization, foreign_key: true, after: :sex 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200922182535_drop_untagged_animal_assessments.rb: -------------------------------------------------------------------------------- 1 | class DropUntaggedAnimalAssessments < ActiveRecord::Migration[6.0] 2 | def up 3 | drop_table :untagged_animal_assessments 4 | end 5 | 6 | def down; end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20200922195812_add_organization_to_measurements.rb: -------------------------------------------------------------------------------- 1 | class AddOrganizationToMeasurements < ActiveRecord::Migration[6.0] 2 | def change 3 | add_reference :measurements, :organization, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200923131001_add_unique_constraint_tanks.rb: -------------------------------------------------------------------------------- 1 | class AddUniqueConstraintTanks < ActiveRecord::Migration[6.0] 2 | def change 3 | add_index :tanks, [:name, :facility_id, :organization_id], unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200923134705_drop_post_settlement_inventories.rb: -------------------------------------------------------------------------------- 1 | class DropPostSettlementInventories < ActiveRecord::Migration[6.0] 2 | def up 3 | drop_table :post_settlement_inventories 4 | end 5 | 6 | def down; end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20210930125236_add_exit_type_to_mortality_event.rb: -------------------------------------------------------------------------------- 1 | class AddExitTypeToMortalityEvent < ActiveRecord::Migration[6.1] 2 | def change 3 | add_reference :mortality_events, :exit_type, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200618201524_add_processed_file_to_measurements.rb: -------------------------------------------------------------------------------- 1 | class AddProcessedFileToMeasurements < ActiveRecord::Migration[5.2] 2 | def change 3 | add_reference :measurements, :processed_file, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20211010204649_add_organization_to_processed_file.rb: -------------------------------------------------------------------------------- 1 | class AddOrganizationToProcessedFile < ActiveRecord::Migration[6.1] 2 | def change 3 | add_reference :processed_files, :organization, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200109184825_add_temporary_file_id_to_processed_files.rb: -------------------------------------------------------------------------------- 1 | class AddTemporaryFileIdToProcessedFiles < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :processed_files, :temporary_file_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200418143117_create_organizations.rb: -------------------------------------------------------------------------------- 1 | class CreateOrganizations < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :organizations do |t| 4 | t.string :name 5 | t.timestamps 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20211008231933_add_organization_to_mortality_event.rb: -------------------------------------------------------------------------------- 1 | class AddOrganizationToMortalityEvent < ActiveRecord::Migration[6.1] 2 | def change 3 | add_reference :mortality_events, :organization, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/factories/mortality_events.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :mortality_event do 3 | cohort 4 | animal 5 | organization 6 | 7 | trait :for_cohort do 8 | animal { nil } 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | 6 | Mime::Type.register "text/csv", :csv 7 | -------------------------------------------------------------------------------- /db/migrate/20200204044451_remove_original_filename_from_processed_files.rb: -------------------------------------------------------------------------------- 1 | class RemoveOriginalFilenameFromProcessedFiles < ActiveRecord::Migration[5.2] 2 | def change 3 | remove_column :processed_files, :original_filename 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200418143934_add_org_indexes.rb: -------------------------------------------------------------------------------- 1 | class AddOrgIndexes < ActiveRecord::Migration[5.2] 2 | def change 3 | add_reference :users, :organization, index: true 4 | add_reference :facilities, :organization, index: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20211011181422_fix_versions_table.rb: -------------------------------------------------------------------------------- 1 | class FixVersionsTable < ActiveRecord::Migration[6.1] 2 | def change 3 | remove_column :versions, "{:null=>false}" 4 | 5 | change_column_null :versions, :item_type, false 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20211014153539_add_processed_file_to_mortality_event.rb: -------------------------------------------------------------------------------- 1 | class AddProcessedFileToMortalityEvent < ActiveRecord::Migration[6.1] 2 | def change 3 | add_reference :mortality_events, :processed_file, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /db/migrate/20190726185815_change_spawning_successes_shl_number_to_string.rb: -------------------------------------------------------------------------------- 1 | class ChangeSpawningSuccessesShlNumberToString < ActiveRecord::Migration[5.2] 2 | def change 3 | change_column :spawning_successes, :shl_number, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200922195820_add_organization_to_measurement_events.rb: -------------------------------------------------------------------------------- 1 | class AddOrganizationToMeasurementEvents < ActiveRecord::Migration[6.0] 2 | def change 3 | add_reference :measurement_events, :organization, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200922213826_create_sex_enum.rb: -------------------------------------------------------------------------------- 1 | class CreateSexEnum < ActiveRecord::Migration[6.0] 2 | def up 3 | create_enum :animal_sex, %w[unknown male female] 4 | end 5 | 6 | def down 7 | drop_enum :animal_sex 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20200924145521_create_shl_numbers.rb: -------------------------------------------------------------------------------- 1 | class CreateShlNumbers < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :shl_numbers do |t| 4 | t.string :code 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/factories/blazer/queries.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :blazer_query, class: Blazer::Query do 3 | name { 'Save the snails!' } 4 | statement { "SELECT * FROM animals" } 5 | creator { FactoryBot.create(:creator) } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /db/migrate/20200405190459_add_dates_to_operations.rb: -------------------------------------------------------------------------------- 1 | class AddDatesToOperations < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :operations, :operation_date, :datetime 4 | add_column :operations, :operation_type, :string 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20200109183112_create_temporary_files.rb: -------------------------------------------------------------------------------- 1 | class CreateTemporaryFiles < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :temporary_files do |t| 4 | t.text :contents 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/devise/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Welcome <%= @email %>!

2 | 3 |

You can confirm your account email through the link below:

4 | 5 |

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

6 | -------------------------------------------------------------------------------- /db/migrate/20200506173804_add_tank_relationship_to_family.rb: -------------------------------------------------------------------------------- 1 | class AddTankRelationshipToFamily < ActiveRecord::Migration[5.2] 2 | def change 3 | change_table :families do |table| 4 | table.references :tank, index: true, null: true 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20190726135958_create_facilities.rb: -------------------------------------------------------------------------------- 1 | class CreateFacilities < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :facilities do |t| 4 | t.string :name 5 | t.string :code 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20190726191807_rename_spawing_successes_shl_number_to_shl_case_number.rb: -------------------------------------------------------------------------------- 1 | class RenameSpawingSuccessesShlNumberToShlCaseNumber < ActiveRecord::Migration[5.2] 2 | def change 3 | rename_column :spawning_successes, :shl_number, :shl_case_number 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200506173350_add_family_relationship_to_operations.rb: -------------------------------------------------------------------------------- 1 | class AddFamilyRelationshipToOperations < ActiveRecord::Migration[5.2] 2 | def change 3 | change_table :operations do |table| 4 | table.references :family, index: true 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/models/animals_shl_number_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe AnimalsShlNumber, type: :model do 4 | it "Animals SHL Numbers has associations" do 5 | is_expected.to belong_to(:animal) 6 | is_expected.to belong_to(:shl_number) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20190726192306_rename_population_estimates_shl_number_to_shl_case_number.rb: -------------------------------------------------------------------------------- 1 | class RenamePopulationEstimatesShlNumberToShlCaseNumber < ActiveRecord::Migration[5.2] 2 | def change 3 | rename_column :population_estimates, :shl_number, :shl_case_number 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200203060633_change_population_estimate_abundace_field_type.rb: -------------------------------------------------------------------------------- 1 | class ChangePopulationEstimateAbundaceFieldType < ActiveRecord::Migration[5.2] 2 | def change 3 | change_column :population_estimates, :abundance, "integer USING abundance::integer" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200404224702_create_families.rb: -------------------------------------------------------------------------------- 1 | class CreateFamilies < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :families do |t| 4 | t.references :female 5 | t.references :male 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /public/samples/cohort.csv: -------------------------------------------------------------------------------- 1 | name,female_tag,male_tag,enclosure 2 | Cabrillo Marine Aquarium location enclosure cohort,Green_389,Blue_125,Cabrillo Marine Aquarium location enclosure 3 | The Abalone Farm location enclosure cohort,Green_578,Red_430,The Abalone Farm location enclosure 4 | ,,, -------------------------------------------------------------------------------- /db/migrate/20200404212335_create_tanks.rb: -------------------------------------------------------------------------------- 1 | class CreateTanks < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :tanks do |t| 4 | t.references :facility, foreign_key: true 5 | t.string :name 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /public/samples/animal.csv: -------------------------------------------------------------------------------- 1 | entry_year,entry_date,entry_point,tag,sex,collected 2 | 2020,2020-09-22T08:16:24-04:00,Collection Area 7,Green_389,female,true 3 | 2020,2020-09-22T10:13:01-08:00,,Red_562,female,false 4 | 2021,2021-10-11T12:20:11-03:00,Collection Area 2,Blue_123,male,true 5 | ,,,,, -------------------------------------------------------------------------------- /spec/factories/facility.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :facility do 3 | val = Faker::Name.unique.name 4 | name { val } 5 | sequence :code do |idx| 6 | "#{val.gsub(/[aeiou|AEIOU|\s]+/, '').strip}-#{idx}" 7 | end 8 | organization 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/factories/file_upload.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :file_upload do 3 | user 4 | organization 5 | status { 'Pending' } 6 | file { Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec', 'fixtures', 'files', 'animals.csv'), 'text/csv') } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/models/shl_number_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe ShlNumber, type: :model do 4 | it "SHL Number has associations" do 5 | is_expected.to have_many(:animals_shl_numbers) 6 | is_expected.to have_many(:animals).through(:animals_shl_numbers) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | if ENV['RAILS_ENV'] == 'test' 6 | require 'simplecov' 7 | SimpleCov.start 'rails' 8 | puts "required simplecov" 9 | end -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /app/assets/stylesheets/pages/operations.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | @import "mixins"; 3 | 4 | .operations__index{ 5 | background: #f3f7fa; 6 | min-height: $body-height; 7 | padding: 2rem 4rem; 8 | &__container{ 9 | background: white; 10 | min-height: 70vh; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /db/migrate/20211002202547_add_collected_to_animals.rb: -------------------------------------------------------------------------------- 1 | class AddCollectedToAnimals < ActiveRecord::Migration[6.1] 2 | def up 3 | add_column :animals, :collected, :boolean, default: false 4 | end 5 | 6 | def down 7 | remove_column :animals, :collected, :boolean 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/lib/aggregates.rb: -------------------------------------------------------------------------------- 1 | module Aggregates 2 | class Calculations 3 | # total egg, larval, or juvenile production by year (esp. how many year-old animals are produced annually) 4 | def self.offspring_production(_life_stage, _year) 5 | # TODO 6 | {} 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20200923202015_change_tanks_to_enclosures.rb: -------------------------------------------------------------------------------- 1 | class ChangeTanksToEnclosures < ActiveRecord::Migration[6.0] 2 | def change 3 | rename_table :tanks, :enclosures 4 | rename_column :cohorts, :tank_id, :enclosure_id 5 | rename_column :operations, :tank_id, :enclosure_id 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /public/samples/enclosure.csv: -------------------------------------------------------------------------------- 1 | facility_code,location_name,name 2 | AOP,Aquarium of the Pacific location,Aquarium of the Pacific location enclosure 1 3 | AOP,Aquarium of the Pacific location,Aquarium of the Pacific location enclosure 2 4 | CMA,Cabrillo Marine Aquarium location,Cabrillo Marine Aquarium location 1 5 | ,, -------------------------------------------------------------------------------- /spec/factories/users.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :user do 3 | email { "#{SecureRandom.hex}@test.com" } 4 | password { "password" } 5 | password_confirmation { "password" } 6 | organization 7 | role { :user } 8 | 9 | trait(:admin) { role { :admin } } 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | 9 | - package-ecosystem: "npm" 10 | directory: "/" 11 | schedule: 12 | interval: daily 13 | open-pull-requests-limit: 10 -------------------------------------------------------------------------------- /app/controllers/operations_controller.rb: -------------------------------------------------------------------------------- 1 | class OperationsController < ApplicationController 2 | def index 3 | @operations = Operation.for_organization(current_organization) 4 | .joins(:cohort, :enclosure) 5 | .includes(:cohort, :enclosure) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /config/reports_kit/reports/total_animals_by_cohort.yml: -------------------------------------------------------------------------------- 1 | # what we're counting/aggregating (y-axis) 2 | measure: animal 3 | 4 | contextual_filters: 5 | - for_organization 6 | 7 | filters: 8 | - created_at 9 | - cohort 10 | 11 | # columns/associations we're grouping by (x-axis) 12 | dimensions: 13 | - cohort 14 | -------------------------------------------------------------------------------- /db/migrate/20200924145950_create_animals_shl_numbers.rb: -------------------------------------------------------------------------------- 1 | class CreateAnimalsShlNumbers < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :animals_shl_numbers do |t| 4 | t.references :animal 5 | t.references :shl_number 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20210930124942_create_exit_types.rb: -------------------------------------------------------------------------------- 1 | class CreateExitTypes < ActiveRecord::Migration[6.1] 2 | def change 3 | create_table :exit_types do |t| 4 | t.string :name 5 | t.belongs_to :organization, null: false, foreign_key: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20211002202438_add_default_entry_point_to_animals.rb: -------------------------------------------------------------------------------- 1 | class AddDefaultEntryPointToAnimals < ActiveRecord::Migration[6.1] 2 | def up 3 | change_column_default :animals, :entry_point, '' 4 | end 5 | 6 | def down 7 | change_column_default :animals, :entry_point, nil 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/fixtures/animals.csv: -------------------------------------------------------------------------------- 1 | entry_year,entry_date,entry_point,tag,sex,unknown_column,collected 2 | 2020,2020-09-22T08:16:24-04:00,sample,G001,female,unknown_data,false 3 | 2020,2020-09-22T08:16:24-04:00,sample,Y002,female,unknown_data,false 4 | 2020,2020-09-22T08:16:24-04:00,sample,Y002,female,unknown_data,false 5 | -------------------------------------------------------------------------------- /spec/fixtures/files/animals.csv: -------------------------------------------------------------------------------- 1 | entry_year,entry_date,entry_point,tag,sex,unknown_column,collected 2 | 2020,2020-09-22T08:16:24-04:00,sample,G001,female,unknown_data,true 3 | 2020,2020-09-22T08:16:24-04:00,sample,Y002,female,unknown_data,true 4 | 2020,2020-09-22T08:16:24-04:00,sample,Y002,female,unknown_data,true 5 | -------------------------------------------------------------------------------- /spec/fixtures/files/basic_custom_measurement_invalid.csv: -------------------------------------------------------------------------------- 1 | date,subject_type,measurement_type,value,measurement_event,enclosure_name,cohort_name,tag,reason 2 | 9/01/21,Cohort,count,,September Survey,Test Enclosure,Test Cohort, 3 | 9/01/21,Cohort,invalid measurement type,24,September Survey,Test Enclosure,Test Cohort, 4 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "abalone", 3 | "private": true, 4 | "dependencies": { 5 | "@rails/webpacker": "5.4.3", 6 | "autoprefixer": "^10.4.8", 7 | "stimulus": "^3.1.0", 8 | "tailwindcss": "^1.9.6" 9 | }, 10 | "devDependencies": { 11 | "webpack-dev-server": "3.11.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/models/concerns/organization_scope.rb: -------------------------------------------------------------------------------- 1 | module OrganizationScope 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | belongs_to :organization 6 | validates :organization, presence: true 7 | 8 | scope :for_organization, ->(organization) { where(organization: organization) } 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/models/organization.rb: -------------------------------------------------------------------------------- 1 | class Organization < ApplicationRecord 2 | has_many :users 3 | has_many :cohorts 4 | has_many :enclosures 5 | has_many :measurements 6 | has_many :measurement_events 7 | has_many :operations 8 | has_many :facilities 9 | has_many :animals 10 | has_many :processed_files 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200923190307_change_families_to_cohort.rb: -------------------------------------------------------------------------------- 1 | class ChangeFamiliesToCohort < ActiveRecord::Migration[6.0] 2 | def change 3 | rename_table :families, :cohorts 4 | rename_column :operations, :family_id, :cohort_id 5 | rename_column :operations, :animals_added_family_id, :animals_added_cohort_id 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20200924210137_rework_animal_tags.rb: -------------------------------------------------------------------------------- 1 | class ReworkAnimalTags < ActiveRecord::Migration[6.0] 2 | def change 3 | remove_column :animals, :pii_tag 4 | remove_column :animals, :tag_id 5 | add_column :animals, :tag, :string, null: true 6 | add_index :animals, [:tag, :cohort_id], unique: true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/jobs/measurement_job.rb: -------------------------------------------------------------------------------- 1 | class MeasurementJob < ApplicationJob 2 | include ImportJob 3 | 4 | HEADERS = [ 5 | "Date", 6 | "Subject Type", 7 | "Measurement Type", 8 | "Value", 9 | "Measurement Event", 10 | "Enclosure Name", 11 | "Cohort Name", 12 | "Tag", 13 | "Reason" 14 | ].freeze 15 | end 16 | -------------------------------------------------------------------------------- /app/javascript/controllers/navbar_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "stimulus" 2 | 3 | export default class extends Controller { 4 | connect() { 5 | } 6 | 7 | open(){ 8 | $("#nav-burger").toggleClass("is-active"); 9 | $("#nav-menu").toggleClass("is-active"); 10 | $("#nav-menu").toggleClass("hidden"); 11 | } 12 | } -------------------------------------------------------------------------------- /app/jobs/mortality_event_job.rb: -------------------------------------------------------------------------------- 1 | class MortalityEventJob < ApplicationJob 2 | include ImportJob 3 | 4 | HEADERS = [ 5 | "Date", 6 | "Subject Type", 7 | "Measurement Type", 8 | "Value", 9 | "Measurement Event", 10 | "Enclosure Name", 11 | "Cohort Name", 12 | "Tag", 13 | "Reason" 14 | ].freeze 15 | end 16 | -------------------------------------------------------------------------------- /app/views/devise/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

4 | 5 |

Click the link below to unlock your account:

6 | 7 |

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

8 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('tailwindcss')('tailwind.config.js'), 4 | require('postcss-import'), 5 | require('postcss-flexbugs-fixes'), 6 | require('postcss-preset-env')({ 7 | autoprefixer: { 8 | flexbox: 'no-2009' 9 | }, 10 | stage: 3 11 | }) 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /spec/fixtures/files/basic_custom_mortality_events.csv: -------------------------------------------------------------------------------- 1 | date,subject_type,measurement_type,value,measurement_event,enclosure_name,cohort_name,tag,reason 2 | 10/01/21,Animal,animal mortality event,,October Survey,Test Enclosure,Test Cohort,M-AOP,Incidental 3 | 10/01/21,Cohort,cohort mortality event,2,October Survey,Test Enclosure,Test Cohort,M-AOP,Incidental -------------------------------------------------------------------------------- /db/migrate/20200405182059_create_operations.rb: -------------------------------------------------------------------------------- 1 | class CreateOperations < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :operations do |t| 4 | t.belongs_to :tank, foreign_key: true 5 | t.integer :animals_added 6 | t.integer :animals_added_family_id 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200405181908_create_measurements.rb: -------------------------------------------------------------------------------- 1 | class CreateMeasurements < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :measurements do |t| 4 | t.string :name 5 | t.string :value_type 6 | t.jsonb :value 7 | t.belongs_to :tank, foreign_key: true 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20200905154358_add_animal_family_tank_to_measurement.rb: -------------------------------------------------------------------------------- 1 | class AddAnimalFamilyTankToMeasurement < ActiveRecord::Migration[5.2] 2 | def change 3 | add_reference :measurements, :animal, foreign_key: true 4 | add_reference :measurements, :family, foreign_key: true 5 | add_reference :measurements, :tank, foreign_key: true 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20200922201229_create_file_uploads.rb: -------------------------------------------------------------------------------- 1 | class CreateFileUploads < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :file_uploads do |t| 4 | t.references :user, foreign_key: true 5 | t.references :organization, foreign_key: true 6 | t.text :status, null: false 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20200923150158_create_measurement_types.rb: -------------------------------------------------------------------------------- 1 | class CreateMeasurementTypes < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :measurement_types do |t| 4 | t.string :name # count || length 5 | t.string :unit # integer || cm 6 | t.references :organization 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/location.rb: -------------------------------------------------------------------------------- 1 | class Location < ApplicationRecord 2 | include OrganizationScope 3 | 4 | belongs_to :facility 5 | has_many :enclosures 6 | 7 | validates :name, :facility_id, presence: true 8 | 9 | delegate :name, :code, to: :facility, prefix: true 10 | 11 | def name_with_facility 12 | "#{facility_name} - #{name}" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/support/file_upload_helpers.rb: -------------------------------------------------------------------------------- 1 | module FileUploadHelpers 2 | # filenames parameter is either a string for a single file or an array of 3 | # strings if uploading multiple files 4 | def upload_file(category, filenames) 5 | select category, from: 'category' 6 | attach_file('input_files[]', filenames) 7 | click_on 'Submit' 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/devise/mailer/email_changed.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @email %>!

2 | 3 | <% if @resource.try(:unconfirmed_email?) %> 4 |

We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.

5 | <% else %> 6 |

We're contacting you to notify you that your email has been changed to <%= @resource.email %>.

7 | <% end %> 8 | -------------------------------------------------------------------------------- /db/migrate/20200924151343_create_locations.rb: -------------------------------------------------------------------------------- 1 | class CreateLocations < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :locations do |t| 4 | t.string :name 5 | t.belongs_to :facility, null: false, foreign_key: true 6 | t.belongs_to :organization, null: false, foreign_key: true 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20201020103647_create_mortality_events_table.rb: -------------------------------------------------------------------------------- 1 | class CreateMortalityEventsTable < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :mortality_events do |t| 4 | t.datetime :mortality_date 5 | t.references :animal 6 | t.references :cohort 7 | t.integer :mortality_count 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20200513172140_create_operation_batches.rb: -------------------------------------------------------------------------------- 1 | class CreateOperationBatches < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :operation_batches do |t| 4 | t.string :name 5 | t.timestamps 6 | end 7 | 8 | change_table :operations do |operations_schema| 9 | operations_schema.references :operation_batch 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20200404224902_create_consolidation_reports.rb: -------------------------------------------------------------------------------- 1 | class CreateConsolidationReports < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :consolidation_reports do |t| 4 | t.references :family, foreign_key: true 5 | t.references :tank_from 6 | t.references :tank_to 7 | t.string :total_animal 8 | 9 | t.timestamps 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /spec/system/reporting_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'When I visit the blazer reporting index page', type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it 'I see the button to create a new query' do 11 | visit reports.root_path 12 | expect(page).to have_selector(:link_or_button, 'New Query') 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20200404222231_create_post_settlement_inventories.rb: -------------------------------------------------------------------------------- 1 | class CreatePostSettlementInventories < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :post_settlement_inventories do |t| 4 | t.datetime :inventory_date 5 | t.integer :mean_standard_length 6 | t.integer :total_per_tank 7 | t.references :tank, foreign_key: true 8 | end 9 | end 10 | end 11 | 12 | -------------------------------------------------------------------------------- /db/migrate/20200404225209_create_animals.rb: -------------------------------------------------------------------------------- 1 | class CreateAnimals < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :animals do |t| 4 | t.integer :collection_year 5 | t.datetime :date_time_collected 6 | t.string :collection_position 7 | t.integer :pii_tag 8 | t.integer :tag_id 9 | t.string :sex 10 | 11 | t.timestamps 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /public/samples/length_gonad_score_template.csv: -------------------------------------------------------------------------------- 1 | date,subject_type,measurement_type,value,measurement_event,enclosure_name,cohort_name,tag,reason 2 | 9/01/21,Animal,length,14,September Survey,Aquarium of the Pacific location enclosure,Aquarium of the Pacific location enclosure cohort,F-AOP,, 3 | 9/01/21,Animal,gonad score,78,September Survey,Aquarium of the Pacific location enclosure,Aquarium of the Pacific location enclosure cohort,F-AOP,, -------------------------------------------------------------------------------- /app/lib/date_parser.rb: -------------------------------------------------------------------------------- 1 | class DateParser 2 | # Returns the parsed date if it's in the mm/dd/yy format, and nil otherwise 3 | def self.parse(date) 4 | DateTime.strptime(date, "%m/%d/%y") if date_regex.match? date 5 | rescue ArgumentError 6 | nil 7 | end 8 | 9 | # regex to checking mm/dd/yy format for strptime above 10 | def self.date_regex 11 | Regexp.new('\d{1,2}\/\d{1,2}\/\d{2}\z').freeze 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20200420193620_create_measurement_events.rb: -------------------------------------------------------------------------------- 1 | class CreateMeasurementEvents < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :measurement_events do |t| 4 | t.string :name 5 | t.references :tank, foreign_key: true 6 | 7 | t.timestamps 8 | end 9 | 10 | change_table :measurements do |t| 11 | t.references :measurement_event, foreign_key: true 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/sample_data_files/measurement/basic_measurement.csv: -------------------------------------------------------------------------------- 1 | date,subject_type,measurement_type,value,measurement_event,enclosure_name,cohort_name,tag,reason 2 | 9/01/21,Enclosure,count,42,September Survey,California Science Center location enclosure,California Science Center location enclosure cohort 3 | 9/01/21,Animal,length,14,September Survey,California Science Center location enclosure,California Science Center location enclosure cohort,F-CSC 4 | -------------------------------------------------------------------------------- /db/migrate/20190727173430_create_processed_files.rb: -------------------------------------------------------------------------------- 1 | class CreateProcessedFiles < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :processed_files do |t| 4 | t.string :filename 5 | t.string :original_filename 6 | t.string :category 7 | t.string :status 8 | t.jsonb :job_stats, null: false, default: '{}' 9 | t.text :job_errors 10 | 11 | t.timestamps 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/factories/animals.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :animal do 3 | entry_year { 1 } 4 | entry_date { "2020-04-04 18:52:09" } 5 | entry_point { "" } 6 | sequence(:tag) { |n| "G#{format('%03d', number: n)}" } 7 | sex { Animal.sexes.keys.sample } 8 | collected { false } 9 | organization 10 | 11 | factory(:male) { sex { 'male' } } 12 | factory(:female) { sex { 'female' } } 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/factories/locations.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :location do 3 | sequence(:name) { |n| "Location #{n}" } 4 | facility 5 | organization 6 | 7 | trait :with_enclosures do 8 | transient { enclosures_count { 1 } } 9 | 10 | after(:create) do |location, eval| 11 | create_list :enclosure, eval.enclosures_count, location: location 12 | location.reload 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/javascript/controllers/index.js: -------------------------------------------------------------------------------- 1 | // Load all the controllers within this directory and all subdirectories. 2 | // Controller files must be named *_controller.js. 3 | 4 | import { Application } from "stimulus" 5 | import { definitionsFromContext } from "stimulus/webpack-helpers" 6 | 7 | const application = Application.start() 8 | const context = require.context("controllers", true, /_controller\.js$/) 9 | application.load(definitionsFromContext(context)) 10 | -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | skip_before_action :authenticate_user! 3 | def index 4 | return unless current_user 5 | 6 | # Retrive current organization stats 7 | @facility_count = current_user.organization.facilities.count 8 | @cohort_count = current_user.organization.cohorts.count 9 | @animal_count = current_user.organization.animals.count 10 | end 11 | 12 | def show; end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20190726184116_create_pedigrees.rb: -------------------------------------------------------------------------------- 1 | class CreatePedigrees < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :pedigrees do |t| 4 | t.boolean :raw, null: false, default: true 5 | t.string :cohort 6 | t.string :shl_case_number 7 | t.date :spawning_date 8 | t.string :mother 9 | t.string :father 10 | t.string :seperate_cross_within_cohort 11 | 12 | t.timestamps 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/views/devise/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Someone has requested a link to change your password. You can do this through the link below.

4 | 5 |

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

6 | 7 |

If you didn't request this, please ignore this email.

8 |

Your password won't change until you access the link above and create a new one.

9 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | ljpRj4Zy8p8RHRt6IsV1Nxi/tXP6VUa59FPkiPzmt1hvyCR7V1syPn0maEOm8Dh9L8Z9o8/EwRu6N5w3xi61nTPPOnNpfrs5DvzfDGvdRT2HmHn5IWE464JALC9KiMxmKArIKyPYR17zYMeeMDhV/zIEgCLqaCtBa2sbMsO29JM9bG0MWmD3MWqHI3N6JMuR0AnynsuwuN4LGM4fyGZZke95c1fVLg8NLkmVaaTzhbJB/7rQ01S+5LLfDEpNdvESkCUVxQ04DVMhpuFlRFF8R5i+WKWeGH5hrVyc9rn0T/DOoS+wd3CNbRABlTNICWWnNG4n7bBg+ldE+/I/u1HlvLY/y6M15PXTnhGOojov4cFkRj+iTqHdUsOfPUAdvQsFjIFTO+tnyVT9pkgPKgTqk0Rp8+LMM4EoA4gA--u3F1Od+XLkMgV1ku--mBlOo7KruO1Yx9vOvbceAg== -------------------------------------------------------------------------------- /app/models/concerns/csv_exportable.rb: -------------------------------------------------------------------------------- 1 | module CsvExportable 2 | extend ActiveSupport::Concern 3 | 4 | class_methods do 5 | def exportable_columns 6 | column_names.reject { |col| %w[organization_id].include? col } 7 | end 8 | 9 | def to_csv 10 | CSV.generate(headers: true) do |csv| 11 | csv << exportable_columns 12 | all.each { |record| csv << exportable_columns.map { |attr| record.send(attr) } } 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20190726144739_create_spawning_success.rb: -------------------------------------------------------------------------------- 1 | class CreateSpawningSuccess < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :spawning_successes do |t| 4 | t.boolean :raw, null: false, default: true 5 | t.string :tag 6 | t.numeric :shl_number 7 | t.date :spawning_date 8 | t.date :date_attempted 9 | t.string :spawning_success 10 | t.numeric :nbr_of_eggs_spawned 11 | 12 | t.timestamps 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /bin/webpack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/webpack_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::WebpackRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /app/views/users/shared/_error_messages.html.erb: -------------------------------------------------------------------------------- 1 | <% if resource.errors.any? %> 2 |
3 |

4 | <%= I18n.t("errors.messages.not_saved", 5 | count: resource.errors.count, 6 | resource: resource.class.model_name.human.downcase) 7 | %> 8 |

9 | 14 |
15 | <% end %> 16 | -------------------------------------------------------------------------------- /app/javascript/packs/measurements.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded",function(){ 2 | let typeSelect = document.querySelector('#data_import_type') 3 | updateDataImportDownload(typeSelect.value) 4 | 5 | typeSelect.addEventListener('change', (e) => { 6 | updateDataImportDownload(e.target.value) 7 | }) 8 | }); 9 | 10 | const updateDataImportDownload = (val) => { 11 | document.querySelector('#data-import-template-download') 12 | .href=`/samples/${val}.csv` 13 | } 14 | -------------------------------------------------------------------------------- /db/migrate/20190726183847_create_population_estimates.rb: -------------------------------------------------------------------------------- 1 | class CreatePopulationEstimates < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :population_estimates do |t| 4 | t.boolean :raw, null: false, default: true 5 | t.date :sample_date 6 | t.string :shl_number 7 | t.date :spawning_date 8 | t.string :lifestage 9 | t.string :abundance 10 | t.string :facility 11 | t.string :notes 12 | 13 | t.timestamps 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20200922214542_migrate_animal_sex_to_postgres_enum.rb: -------------------------------------------------------------------------------- 1 | class MigrateAnimalSexToPostgresEnum < ActiveRecord::Migration[6.0] 2 | def up 3 | execute <<-DDL 4 | ALTER TABLE animals ALTER COLUMN sex TYPE animal_sex 5 | USING CASE sex 6 | WHEN 'male' THEN 'male'::animal_sex 7 | WHEN 'female' THEN 'female'::animal_sex 8 | WHEN NULL THEN 'unknown'::animal_sex 9 | END; 10 | ALTER TABLE animals ALTER COLUMN sex SET NOT NULL 11 | DDL 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /bin/webpack-dev-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/dev_server_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::DevServerRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /app/controllers/cohort_imports_controller.rb: -------------------------------------------------------------------------------- 1 | class CohortImportsController < ApplicationController 2 | def new 3 | @cohort_import_form = CsvImportForm.new 4 | end 5 | 6 | def create 7 | @cohort_import_form = CsvImportForm.new csv_file: params.dig(:csv_import_form, :csv_file) 8 | 9 | if @cohort_import_form.save(user: current_user, organization: current_organization) 10 | redirect_to cohorts_path, notice: 'Processing file...' 11 | else 12 | render :new 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | include OrganizationScope 3 | 4 | enum role: %i[user admin] 5 | after_initialize :set_default_role, if: :new_record? 6 | 7 | def set_default_role 8 | self.role ||= :user 9 | end 10 | 11 | # Include default devise modules. Others available are: 12 | # :confirmable, :lockable, :timeoutable, :trackable, 13 | # :registerable, and :omniauthable 14 | devise :database_authenticatable, :recoverable, 15 | :rememberable, :validatable 16 | end 17 | -------------------------------------------------------------------------------- /spec/controllers/reports_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # Replaced by blazer reporting - 1/24/21 2 | 3 | # require 'rails_helper' 4 | # 5 | # describe ReportsController do 6 | # include Devise::Test::ControllerHelpers 7 | # 8 | # let(:user) { FactoryBot.create(:user) } 9 | # 10 | # before do 11 | # sign_in user 12 | # end 13 | # 14 | # describe '#index' do 15 | # it 'should have response code 200' do 16 | # get :index 17 | # expect(response.code).to eq '200' 18 | # end 19 | # end 20 | # end 21 | -------------------------------------------------------------------------------- /spec/jobs/measurement_job_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe MeasurementJob do 4 | let(:filename) { "basic_measurement.csv" } 5 | 6 | it_behaves_like "import job" do 7 | let!(:organization) { create(:organization) } 8 | let!(:measurement_type) { create(:measurement_type, name: 'count', unit: 'number', organization_id: organization.id) } 9 | let!(:measurement_type2) { create(:measurement_type, name: 'length', unit: 'number', organization_id: organization.id) } 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/factories/measurements.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :measurement do 3 | value { "25" } 4 | measurement_type 5 | association :subject, factory: :animal 6 | measurement_event 7 | organization 8 | 9 | # Polymorphic associations for measurements 10 | trait(:for_cohort) { association :subject, factory: :cohort } 11 | trait(:for_enclosure) { association :subject, factory: :enclosure } 12 | trait(:for_animal) { association :subject, factory: :animal } 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/views/enclosures/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to enclosures_path, class: "inline-flex items-center py-2 mb-4 font-bold rounded text-primary-dark hover:text-primary" do %> 4 | << Cancel 5 | <% end %> 6 |

New Enclosure

7 |
8 | <%= render 'form', enclosure: @enclosure %> 9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /spec/models/organization_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Organization, type: :model do 4 | it "Organization has associations" do 5 | is_expected.to have_many(:users) 6 | is_expected.to have_many(:facilities) 7 | is_expected.to have_many(:cohorts) 8 | is_expected.to have_many(:enclosures) 9 | is_expected.to have_many(:measurements) 10 | is_expected.to have_many(:measurement_events) 11 | is_expected.to have_many(:operations) 12 | is_expected.to have_many(:animals) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/assets/stylesheets/file_uploads.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Place all the styles related to the matching controller here. 3 | They will automatically be included in application.css. 4 | */ 5 | 6 | .container_with_scroll{ 7 | overflow-x: scroll; 8 | } 9 | .column.is-thinner{ 10 | padding: .15rem .75rem; 11 | } 12 | 13 | .has-text-success{ 14 | color: hsl(141, 53%, 53%); 15 | } 16 | 17 | .has-text-danger{ 18 | color: hsl(348, 100%, 61%); 19 | } 20 | 21 | .trash-ico{ 22 | img { 23 | width: 1.125rem; 24 | margin: 0 auto; 25 | } 26 | } -------------------------------------------------------------------------------- /app/views/users/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to users_path do %> 4 | 7 | <% end %> 8 |

New User

9 |
10 | <%= render 'form', user: @user %> 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /spec/support/csv/basic_custom_measurement_invalid.csv: -------------------------------------------------------------------------------- 1 | measurement_event,measurement,value,enclosure_name,animal_pii_tag,cohort_name, 2 | Michael Drinks the Water,Flavor,Salty,Support Rack 3,,, 3 | Michael Drinks the Water,Flavor,WAY too salty,AB-17,,, 4 | Michael Drinks the Water,Flavor,Good,,,, 5 | Michael Drinks the Correct Water,Flavor,Good,The Water Bottle at My Desk,,, 6 | Michael Drinks the Correct Water,Flavor,Excellent,Office Water Cooler,,, 7 | Michael last known test,Tanning Lotion Smell,Sort of like Sardine Oil,CB Husband Tanning Salon,,, 8 | -------------------------------------------------------------------------------- /app/views/animals/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to animals_path do %> 4 | 7 | <% end %> 8 |

New Animal

9 |
10 | <%= render 'form', animal: @animal %> 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /app/views/cohorts/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to cohorts_path do %> 4 | 7 | <% end %> 8 |

New Cohort

9 |
10 | <%= render 'form', cohort: @cohort %> 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /app/views/enclosures/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to enclosures_path, class: "inline-flex items-center py-2 mb-4 font-bold rounded text-primary-dark hover:text-primary" do %> 4 | << Cancel 5 | <% end %> 6 |

Editing Enclosure

7 |
8 | <%= render 'form', enclosure: @enclosure %> 9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /app/views/measurement_types/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

New Measurement Type

4 |
5 | <%= render 'form', measurement_type: @measurement_type %> 6 |
7 | <%= link_to '<< Back', measurement_types_path, class: 'text-primary-dark hover:text-primary font-bold py-2 rounded inline-flex items-center mb-4' %> 8 |
9 |
10 | -------------------------------------------------------------------------------- /db/migrate/20200923223021_add_object_changes_to_versions.rb: -------------------------------------------------------------------------------- 1 | # This migration adds the optional `object_changes` column, in which PaperTrail 2 | # will store the `changes` diff for each update event. See the readme for 3 | # details. 4 | class AddObjectChangesToVersions < ActiveRecord::Migration[6.0] 5 | # The largest text column available in all supported RDBMS. 6 | # See `create_versions.rb` for details. 7 | TEXT_BYTES = 1_073_741_823 8 | 9 | def change 10 | add_column :versions, :object_changes, :text, limit: TEXT_BYTES 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/models/ability.rb: -------------------------------------------------------------------------------- 1 | class Ability 2 | # Add in CanCan's ability definition DSL 3 | include CanCan::Ability 4 | 5 | def initialize(user) 6 | return unless user.present? 7 | 8 | can %i[show destroy index], ProcessedFile, organization_id: user.organization_id 9 | 10 | [ 11 | Animal, 12 | Cohort, 13 | Enclosure, 14 | ExitType, 15 | Facility, 16 | MeasurementType, 17 | User 18 | ].each do |model| 19 | can :manage, model, organization_id: user.organization_id 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/views/facilities/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to facilities_path do %> 4 | 7 | <% end %> 8 |

New Facility

9 |
10 | <%= render 'form', facility: @facility %> 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /app/views/measurement_types/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to '<< Back', measurement_types_path, class: 'text-primary-dark hover:text-primary font-bold py-2 rounded inline-flex items-center mb-4' %> 4 |

Editing Measurement Type

5 |
6 | <%= render 'form', measurement_type: @measurement_type %> 7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /db/migrate/20200923151254_add_references_and_attributes_to_measurements.rb: -------------------------------------------------------------------------------- 1 | class AddReferencesAndAttributesToMeasurements < ActiveRecord::Migration[6.0] 2 | def change 3 | add_reference :measurements, :subject, polymorphic: true, null: false 4 | remove_column :measurements, :name 5 | remove_column :measurements, :value_type 6 | remove_column :measurements, :animal_id 7 | remove_column :measurements, :tank_id 8 | remove_column :measurements, :family_id 9 | change_column :measurements, :value, :string, null: false 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/javascript/controllers/hello_controller.js: -------------------------------------------------------------------------------- 1 | // Visit The Stimulus Handbook for more details 2 | // https://stimulusjs.org/handbook/introduction 3 | // 4 | // This example controller works with specially annotated HTML like: 5 | // 6 | //
7 | //

8 | //
9 | 10 | import { Controller } from "stimulus" 11 | 12 | export default class extends Controller { 13 | static targets = [ "output" ] 14 | 15 | connect() { 16 | this.outputTarget.textContent = 'Hello, Stimulus!' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/views/exit_types/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to exit_types_path do %> 4 | 7 | <% end %> 8 |

New Exit Type

9 |
10 | <%= render 'form', exit_type: @exit_type %> 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /app/views/users/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to users_path do %> 4 | 7 | <% end %> 8 |

Editing User

9 |
10 | <%= render 'form', user: @user %> 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /db/migrate/20211002202239_update_animals_column_names.rb: -------------------------------------------------------------------------------- 1 | class UpdateAnimalsColumnNames < ActiveRecord::Migration[6.1] 2 | def up 3 | rename_column :animals, :collection_year, :entry_year 4 | rename_column :animals, :date_time_collected, :entry_date 5 | rename_column :animals, :collection_position, :entry_point 6 | end 7 | 8 | def down 9 | rename_column :animals, :entry_year, :collection_year 10 | rename_column :animals, :entry_date, :date_time_collected 11 | rename_column :animals, :entry_point, :collection_position 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/views/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend unlock instructions

2 | 3 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= render "devise/shared/error_messages", resource: resource %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 9 |
10 | 11 |
12 | <%= f.submit "Resend unlock instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "users/shared/links" %> 17 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /app/views/animals/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to animals_path do %> 4 | 7 | <% end %> 8 |

Editing Animal

9 |
10 | <%= render 'form', animal: @animal %> 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /app/views/cohorts/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to cohorts_path do %> 4 | 7 | <% end %> 8 |

Editing Cohort

9 |
10 | <%= render 'form', cohort: @cohort %> 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /app/views/facilities/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to facilities_path do %> 4 | 7 | <% end %> 8 |

Editing Facility

9 |
10 | <%= render 'form', facility: @facility %> 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /app/views/exit_types/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to exit_types_path do %> 4 | 7 | <% end %> 8 |

Editing Exit Type

9 |
10 | <%= render 'form', exit_type: @exit_type %> 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /app/views/locations/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to facility_locations_path do %> 4 | 7 | <% end %> 8 |

Editing Location

9 |
10 | <%= render 'form', location: @location, facility: @facility %> 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code 7 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'". 8 | # Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"] 9 | -------------------------------------------------------------------------------- /lib/templates/erb/scaffold/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%# frozen_string_literal: true %> 2 | <%%= simple_form_for(@<%= singular_table_name %>) do |f| %> 3 | <%%= f.error_notification %> 4 | <%%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %> 5 | 6 |
7 | <%- attributes.each do |attribute| -%> 8 | <%%= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> %> 9 | <%- end -%> 10 |
11 | 12 |
13 | <%%= f.button :submit %> 14 |
15 | <%% end %> 16 | -------------------------------------------------------------------------------- /app/views/locations/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to facility_locations_path(@facility) do %> 4 | 7 | <% end %> 8 |

New Location

9 |
10 | <%= render 'form', location: @location, facility: @facility %> 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | yarn = ENV["PATH"].split(File::PATH_SEPARATOR). 5 | select { |dir| File.expand_path(dir) != __dir__ }. 6 | product(["yarn", "yarn.cmd", "yarn.ps1"]). 7 | map { |dir, file| File.expand_path(file, dir) }. 8 | find { |file| File.executable?(file) } 9 | 10 | if yarn 11 | exec yarn, *ARGV 12 | else 13 | $stderr.puts "Yarn executable was not detected in the system." 14 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 15 | exit 1 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/fixtures/files/basic_custom_measurement.csv: -------------------------------------------------------------------------------- 1 | date,subject_type,measurement_type,value,measurement_event,enclosure_name,cohort_name,tag,reason 2 | 9/01/21,Cohort,count,24,September Survey,Test Enclosure,Test Cohort, 3 | 9/01/21,Enclosure,count,42,September Survey,Test Enclosure,Test Cohort, 4 | 9/01/21,Animal,length,14,September Survey,Test Enclosure,Test Cohort,F-AOP, 5 | 9/01/21,Animal,gonad score,78,September Survey,Test Enclosure,Test Cohort,F-AOP, 6 | 9/01/21,Animal,length,16,September Survey,Test Enclosure,Test Cohort,M-AOP, 7 | 9/01/21,Animal,gonad score,46,September Survey,Test Enclosure,Test Cohort,M-AOP, 8 | -------------------------------------------------------------------------------- /spec/system/csv_upload_confirmation_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "file confirm queue page", type: :system do 4 | let(:user) { create(:user) } 5 | let(:valid_file) { "#{Rails.root}/db/sample_data_files/measurement/basic_measurement.csv" } 6 | 7 | context "after uploading a file" do 8 | it "should have a link to view all files" do 9 | sign_in user 10 | visit new_file_upload_path 11 | upload_file("Measurement", [valid_file]) 12 | click_link('View All Files') 13 | expect(page).to have_current_path(file_uploads_path) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/assets/stylesheets/_mixins.scss: -------------------------------------------------------------------------------- 1 | @import './utils.scss'; 2 | 3 | //Small Devices 4 | @mixin mobile { 5 | @media (max-width: $mobile) { 6 | @content; 7 | } 8 | } 9 | 10 | //Medium (tablets) 11 | @mixin tablet { 12 | @media (max-width: $tablet) { 13 | @content; 14 | } 15 | } 16 | 17 | //Small laptops and desktops 18 | @mixin mediaLg { 19 | @media (max-width: $desktop-max) and (min-width: $tablet+1) { 20 | @content; 21 | } 22 | } 23 | 24 | //Large devices 25 | @mixin mediaXL { 26 | @media (min-width: $desktop-max) and (min-width: $desktop-min) { 27 | @content; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/views/measurements/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to measurements_path do %> 4 | 7 | <% end %> 8 |

Editing Measurement

9 |
10 | <%= render 'form', measurement: @measurement %> 11 |
12 |
13 |
14 | 15 | -------------------------------------------------------------------------------- /spec/models/measurement_event_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe MeasurementEvent, type: :model do 4 | subject(:measurement_event) { build_stubbed(:measurement_event) } 5 | 6 | it "has associations" do 7 | is_expected.to have_many(:measurements) 8 | is_expected.to belong_to(:organization) 9 | end 10 | 11 | describe "Validations >" do 12 | subject(:measurement_event) { build(:measurement_event) } 13 | 14 | it "has a valid factory" do 15 | expect(measurement_event).to be_valid 16 | end 17 | 18 | include_examples OrganizationScope 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/models/file_upload_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | describe FileUpload do 4 | subject(:file_upload) { build_stubbed(:file_upload) } 5 | 6 | it "has associations" do 7 | is_expected.to belong_to(:user) 8 | end 9 | 10 | describe "Validations >" do 11 | subject(:file_upload) { build(:file_upload) } 12 | 13 | it "has a valid factory" do 14 | expect(file_upload).to be_valid 15 | end 16 | 17 | include_examples OrganizationScope 18 | 19 | it { is_expected.to validate_presence_of(:status) } 20 | it { is_expected.to validate_presence_of(:user) } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/system/new_measurements_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'When I visit the New Measurements page', type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | visit new_measurement_path 9 | end 10 | 11 | it 'Then I can generate a template for my selected measurements' do 12 | expect(page).to have_selector('label.sr-only') 13 | expect(page).to have_content 'I am taking measurements of animals' 14 | expect(page).to have_content 'I want to measure length and gonad score' 15 | expect(page).to have_link('Generate Template') 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20210213212906_create_active_storage_variant_records.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20191206030411) 2 | class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0] 3 | def change 4 | create_table :active_storage_variant_records do |t| 5 | t.belongs_to :blob, null: false, index: false 6 | t.string :variation_digest, null: false 7 | 8 | t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true 9 | t.foreign_key :active_storage_blobs, column: :blob_id 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/system/download_csv_file_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "download CSV file from upload files", type: :system do 4 | include Devise::Test::IntegrationHelpers 5 | 6 | let(:user) { create(:user) } 7 | 8 | it "When I click on previous file name I download csv file" do 9 | file = FactoryBot.create(:processed_file, organization: user.organization) 10 | sign_in user 11 | visit file_uploads_path 12 | click_on(file.filename) 13 | filename = file.filename.gsub('(', '%28').gsub(')', '%29') 14 | expect(page.response_headers['Content-Disposition']).to include(filename) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20190727201619_add_processed_file_id_to_data.rb: -------------------------------------------------------------------------------- 1 | class AddProcessedFileIdToData < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :spawning_successes, :processed_file_id, :integer 4 | add_column :wild_collections, :processed_file_id, :integer 5 | add_column :mortality_trackings, :processed_file_id, :integer 6 | add_column :population_estimates, :processed_file_id, :integer 7 | add_column :pedigrees, :processed_file_id, :integer 8 | add_column :untagged_animal_assessments, :processed_file_id, :integer 9 | add_column :tagged_animal_assessments, :processed_file_id, :integer 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200506175333_rename_operations_operation_type_to_prevent_rails_naming_collisions.rb: -------------------------------------------------------------------------------- 1 | class RenameOperationsOperationTypeToPreventRailsNamingCollisions < ActiveRecord::Migration[5.2] 2 | def change 3 | # `type` is a reserved word in Rails, that can cause 4 | # surprising behavior when using Single Table Iheritance 5 | # or Polymorphic assocations. 6 | 7 | # This is because Rails infers that fields named with `type` 8 | # indicate the class that should be used when deserializing 9 | # the data from the database into a Rails model. 10 | rename_column :operations, :operation_type, :action 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /.github/workflows/rubocop.yml: -------------------------------------------------------------------------------- 1 | name: rubocop 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - 'docs/**' 9 | - '*.md' 10 | - 'bin/*' 11 | pull_request: 12 | branches: 13 | - main 14 | paths-ignore: 15 | - 'docs/**' 16 | - '*.md' 17 | - 'bin/*' 18 | 19 | jobs: 20 | ruby_lint: 21 | 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | 27 | - name: Set up Ruby 28 | uses: ruby/setup-ruby@v1 29 | with: 30 | bundler-cache: true 31 | 32 | - name: Run Rubocop 33 | run: bundle exec rubocop -------------------------------------------------------------------------------- /app/assets/images/icons/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /spec/system/locations/index_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the locations index page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "Then I see a list of locations" do 11 | facility = create(:facility) 12 | locations = create_list(:location, 3, facility: facility, organization: user.organization) 13 | 14 | visit facility_locations_path(facility) 15 | 16 | locations.each do |location| 17 | expect(page).to have_content(location.name) 18 | expect(page).to have_content(location.facility_name) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe User, type: :model do 4 | let!(:user) { create(:user) } 5 | 6 | describe "Validations >" do 7 | subject(:user) { build(:user) } 8 | 9 | it "has a valid factory" do 10 | expect(user).to be_valid 11 | end 12 | 13 | it_behaves_like OrganizationScope 14 | 15 | it { is_expected.to validate_presence_of(:email) } 16 | it { is_expected.to validate_presence_of(:password) } 17 | end 18 | 19 | describe "Callbacks >" do 20 | it "initializes with a default role of 'user'" do 21 | expect(User.new.role).to eq("user") 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /db/migrate/20190726191528_create_untagged_animal_assessments.rb: -------------------------------------------------------------------------------- 1 | class CreateUntaggedAnimalAssessments < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :untagged_animal_assessments do |t| 4 | t.boolean :raw, null: false, default: true 5 | t.date :measurement_date 6 | t.string :cohort 7 | t.date :spawning_date 8 | t.numeric :growout_rack 9 | t.string :growout_column 10 | t.numeric :growout_trough 11 | t.numeric :length 12 | t.numeric :mass 13 | t.string :gonad_score 14 | t.string :predicted_sex 15 | t.text :notes 16 | 17 | t.timestamps 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /.github/workflows/brakeman.yml: -------------------------------------------------------------------------------- 1 | name: brakeman 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - 'docs/**' 9 | - '*.md' 10 | - 'bin/*' 11 | pull_request: 12 | branches: 13 | - main 14 | paths-ignore: 15 | - 'docs/**' 16 | - '*.md' 17 | - 'bin/*' 18 | 19 | jobs: 20 | brakeman: 21 | 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | 27 | - name: Set up Ruby 28 | uses: ruby/setup-ruby@v1 29 | with: 30 | bundler-cache: true 31 | 32 | - name: Run Brakeman 33 | run: bundle exec brakeman -Aw1 --no-pager -------------------------------------------------------------------------------- /app/helpers/file_uploads_helper.rb: -------------------------------------------------------------------------------- 1 | module FileUploadsHelper 2 | def file_upload_status_style(status) 3 | if success? status 4 | 'fa fa-check has-text-success' 5 | elsif pending? status 6 | 'fas fa-spinner' 7 | elsif failed? status 8 | 'fas fa-exclamation-triangle has-text-danger' 9 | end 10 | end 11 | 12 | def success?(status) 13 | status.include?('0 records had errors') || status == 'Processed' 14 | end 15 | 16 | def pending?(status) 17 | status.include?('Pending') || status == 'Running' 18 | end 19 | 20 | def failed?(status) 21 | status == 'Failed' || status.match(/[1-9]\d* records had errors/) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/forms/csv_import_form.rb: -------------------------------------------------------------------------------- 1 | class CsvImportForm 2 | include ActiveModel::Model 3 | 4 | attr_accessor :csv_file 5 | 6 | validates :csv_file, presence: true 7 | 8 | def save(user:, organization:) 9 | valid_form? && ImportCohortsJob.perform_later( 10 | FileUpload.create(user: user, organization: organization, status: 'Pending', file: csv_file) 11 | ) 12 | end 13 | 14 | private 15 | 16 | def valid_form? 17 | valid? && valid_content_type? 18 | end 19 | 20 | def valid_content_type? 21 | return true if csv_file&.content_type == 'text/csv' 22 | 23 | errors.add :csv_file, 'Invalid file type. Please upload a CSV.' 24 | false 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend confirmation instructions

2 | 3 | <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= render "devise/shared/error_messages", resource: resource %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> 9 |
10 | 11 |
12 | <%= f.submit "Resend confirmation instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "users/shared/links" %> 17 | -------------------------------------------------------------------------------- /db/migrate/20190726211013_change_wild_collection_field_types.rb: -------------------------------------------------------------------------------- 1 | class ChangeWildCollectionFieldTypes < ActiveRecord::Migration[5.2] 2 | def change 3 | change_column :wild_collections, :collection_depth, "numeric USING collection_depth::numeric" 4 | change_column :wild_collections, :length, "numeric USING collection_depth::numeric" 5 | change_column :wild_collections, :weight, "numeric USING collection_depth::numeric" 6 | change_column :wild_collections, :otc_treatment_completion_date, "date USING otc_treatment_completion_date::date" 7 | rename_column :wild_collections, :final_holding_facility_date_of_arrival, :final_holding_facility_and_date_of_arrival 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [rubyforgood] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /app/assets/stylesheets/reports_kit.scss: -------------------------------------------------------------------------------- 1 | // Replaced by blazer reporting - 1/24/21 2 | 3 | @import "variables"; 4 | @import "mixins"; 5 | 6 | .abalone-chart { 7 | position: relative; 8 | } 9 | 10 | .reports_kit_report { 11 | form { 12 | select.select2, span.select2 { 13 | display: inline-block; 14 | width: 200px; 15 | } 16 | .select2-search { 17 | input { 18 | height: 2.1em !important; 19 | } 20 | } 21 | } 22 | } 23 | 24 | .reports_kit_visualization { 25 | width: 100%; 26 | } 27 | 28 | /* 29 | ** stick download buttons to upper-right of chart 30 | */ 31 | .reports_kit_actions { 32 | position: absolute; 33 | top: 0; 34 | right: 0; 35 | } 36 | -------------------------------------------------------------------------------- /spec/controllers/blazer/queries_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Blazer::QueriesController, type: :controller do 4 | routes { Blazer::Engine.routes } 5 | 6 | let(:user) { FactoryBot.create(:user) } 7 | 8 | before { sign_in(user) } 9 | 10 | describe 'index' do 11 | before do 12 | FactoryBot.create_list(:blazer_query, 3, creator: user) 13 | FactoryBot.create_list(:blazer_query, 7, creator: FactoryBot.create(:user)) 14 | end 15 | 16 | it 'returns queries scoped to current_user\'s organization' do 17 | get :index 18 | queries = JSON.parse(response.body) 19 | expect(queries.count).to eq(3) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | # Add Yarn node_modules folder to the asset load path. 9 | Rails.application.config.assets.paths << Rails.root.join('node_modules') 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in the app/assets 13 | # folder are already added. 14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 15 | -------------------------------------------------------------------------------- /db/migrate/20190726182945_create_mortality_tracking.rb: -------------------------------------------------------------------------------- 1 | class CreateMortalityTracking < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :mortality_trackings do |t| 4 | t.boolean :raw, default: true, null: false 5 | t.date :mortality_date 6 | t.string :cohort 7 | t.string :shl_case_number 8 | t.date :spawning_date 9 | t.integer :shell_box 10 | t.string :shell_container 11 | t.string :animal_location 12 | t.integer :number_morts 13 | t.string :approximation 14 | t.string :processed_by_shl 15 | t.string :initials 16 | t.string :tags 17 | t.string :comments 18 | 19 | t.timestamps 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/models/operation.rb: -------------------------------------------------------------------------------- 1 | class Operation < ApplicationRecord 2 | include OrganizationScope 3 | 4 | belongs_to :enclosure 5 | 6 | belongs_to :operation_batch, required: false 7 | belongs_to :cohort, required: false 8 | 9 | validates :cohort, presence: true, if: :add_cohort? 10 | 11 | def perform 12 | case action.to_sym 13 | when :remove_cohort 14 | enclosure.update(cohort: nil) 15 | when :add_cohort 16 | enclosure.update(cohort: cohort) 17 | else 18 | raise InvalidActionError, action 19 | end 20 | end 21 | 22 | private 23 | 24 | def add_cohort? 25 | action&.to_sym == :add_cohort 26 | end 27 | 28 | class InvalidActionError < StandardError; end 29 | end 30 | -------------------------------------------------------------------------------- /spec/fixtures/files/basic_custom_measurement_with_spaces.csv: -------------------------------------------------------------------------------- 1 | date,subject_type,measurement_type , value, measurement_event ,enclosure_name,cohort_name,tag, reason 2 | 9/01/21, Cohort, count, 24,September Survey, Test Enclosure, Test Cohort, 3 | 9/01/21,Enclosure,count , 42,September Survey,Test Enclosure, Test Cohort, 4 | 9/01/21, Animal , length, 14,September Survey,Test Enclosure,Test Cohort,F-AOP, 5 | 9/01/21,Animal,gonad score ,78, September Survey, Test Enclosure,Test Cohort , F-AOP, 6 | 9/01/21,Animal,length,16,September Survey, Test Enclosure,Test Cohort,M-AOP, 7 | 9/01/21 ,Animal, gonad score , 46,September Survey,Test Enclosure,Test Cohort, M-AOP, -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /db/migrate/20210213212905_add_service_name_to_active_storage_blobs.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20190112182829) 2 | class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0] 3 | def up 4 | unless column_exists?(:active_storage_blobs, :service_name) 5 | add_column :active_storage_blobs, :service_name, :string 6 | 7 | if configured_service = ActiveStorage::Blob.service.name 8 | ActiveStorage::Blob.unscoped.update_all(service_name: configured_service) 9 | end 10 | 11 | change_column :active_storage_blobs, :service_name, :string, null: false 12 | end 13 | end 14 | 15 | def down 16 | remove_column :active_storage_blobs, :service_name 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /public/samples/animal_count_measurements_template.csv: -------------------------------------------------------------------------------- 1 | date,subject_type,measurement_type,value,measurement_event,enclosure_name,cohort_name,tag,reason 2 | 9/01/21,Cohort,count,24,September Survey,Aquarium of the Pacific location enclosure,Aquarium of the Pacific location enclosure cohort,, 3 | 9/01/21,Enclosure,count,42,September Survey,Aquarium of the Pacific location enclosure,Aquarium of the Pacific location enclosure cohort,, 4 | 9/01/21,Animal,animal mortality event,,September Survey,Aquarium of the Pacific location enclosure,Aquarium of the Pacific location enclosure cohort,Green_289,sacrifice 5 | 9/01/21,Cohort,cohort mortality event,5,September Survey,Aquarium of the Pacific location enclosure,Aquarium of the Pacific location enclosure cohort,,natural causes 6 | -------------------------------------------------------------------------------- /spec/system/facilities/create_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the facility New page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "And fill out the form and click the submit button, a facility is created" do 11 | visit new_facility_path 12 | 13 | within('form') do 14 | fill_in 'Name', with: 'Aquarium of the Pacific' 15 | fill_in 'Code', with: 'AOP' 16 | click_on 'Submit' 17 | end 18 | 19 | expect(page).to have_content 'Facility was successfully created.' 20 | within('.container') do 21 | expect(page).to have_content 'Aquarium of the Pacific' 22 | expect(page).to have_content 'AOP' 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/system/cohorts/delete_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the cohort Index page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "Then I click the delete button, cohort should be deleted" do 11 | cohort1 = create(:cohort, organization: user.organization) 12 | cohort_count = Cohort.for_organization(user.organization).count 13 | 14 | visit cohorts_path 15 | 16 | link = find("a[data-method='delete'][href='#{cohort_path(cohort1.id)}']") 17 | 18 | within('tbody') do 19 | expect(page).to have_xpath('.//tr', count: cohort_count) 20 | link.click 21 | expect(page).to have_xpath('.//tr', count: (cohort_count - 1)) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/system/locations/new_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the location New page", type: :system do 4 | let(:user) { create(:user) } 5 | let!(:facility) { create(:facility, organization_id: user.organization_id) } 6 | 7 | before do 8 | sign_in user 9 | end 10 | 11 | it "Then I see the location form" do 12 | visit new_facility_location_path(facility) 13 | 14 | expect(page).to have_content("New Location") 15 | expect(page).to have_content("Name") 16 | end 17 | 18 | it "I can create a new location" do 19 | visit new_facility_location_path(facility) 20 | 21 | fill_in('Name', with: "Secret location") 22 | click_button('Submit') 23 | 24 | expect(page).to have_content("Secret location") 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /db/migrate/20190726191836_create_tagged_animal_assessments.rb: -------------------------------------------------------------------------------- 1 | class CreateTaggedAnimalAssessments < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :tagged_animal_assessments do |t| 4 | t.boolean :raw, null: false, default: true 5 | t.date :measurement_date 6 | t.string :shl_case_number 7 | t.date :spawning_date 8 | t.string :tag 9 | t.string :from_growout_rack 10 | t.string :from_growout_column 11 | t.string :from_growout_trough 12 | t.string :to_growout_rack 13 | t.string :to_growout_column 14 | t.string :to_growout_trough 15 | t.numeric :length 16 | t.string :gonad_score 17 | t.string :predicted_sex 18 | t.text :notes 19 | 20 | t.timestamps 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/system/animals/create_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the animal New page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "And fill out the form and click the submit button, animal should be created" do 11 | cohort = create(:cohort, organization: user.organization) 12 | 13 | visit new_animal_path 14 | 15 | within('form') do 16 | fill_in 'animal_tag', with: "G127" 17 | select "Male", from: 'animal_sex' 18 | select cohort.name, from: 'animal_cohort_id' 19 | fill_in 'animal_shl_numbers_codes', with: "c123,k123-32" 20 | click_on 'Submit' 21 | end 22 | 23 | expect(page).to have_content 'Animal was successfully created.' 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/system/exit_types/show_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the exit type Show page", type: :system do 4 | it "I see information of a specific exit type" do 5 | user = create(:user, :admin) 6 | exit_type = create(:exit_type, name: "test", organization: user.organization) 7 | 8 | sign_in user 9 | 10 | visit exit_type_path(exit_type) 11 | 12 | expect(page).to have_content(exit_type.name) 13 | end 14 | 15 | it "I can't see an exit type of another organization" do 16 | user = create(:user, :admin) 17 | exit_type = create(:exit_type, name: "test") 18 | 19 | sign_in user 20 | 21 | visit exit_type_path(exit_type) 22 | 23 | expect(page).to have_content("You are not authorized to access this resource.") 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/controllers/passwords_controller.rb: -------------------------------------------------------------------------------- 1 | class PasswordsController < ApplicationController 2 | before_action :set_user 3 | before_action :current_user? 4 | 5 | def edit; end 6 | 7 | def update 8 | if @user.update(user_params) 9 | bypass_sign_in(@user) 10 | redirect_to edit_password_url, notice: 'Password was successfully updated.' 11 | else 12 | render :edit 13 | end 14 | end 15 | 16 | private 17 | 18 | def user_params 19 | params.require(:user).permit( 20 | :password 21 | ).merge(organization_id: current_organization.id) 22 | end 23 | 24 | def set_user 25 | @user = User.find(params[:id]) 26 | end 27 | 28 | def current_user? 29 | redirect_to root_path, alert: "Not authorized" unless current_user == @user 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/system/facilities/destroy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the facility Index page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "Then I click the delete button, the facility is deleted" do 11 | facility = create(:facility, organization: user.organization) 12 | facility_count = Facility.for_organization(user.organization).count 13 | 14 | visit facilities_path 15 | 16 | link = find("a[data-method='delete'][href='#{facility_path(facility.id)}']") 17 | 18 | within('tbody') do 19 | expect(page).to have_xpath('.//tr', count: facility_count) 20 | link.click 21 | expect(page).to have_xpath('.//tr', count: (facility_count - 1)) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/system/animals/destroy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the animal Index page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "Then I click the delete button, animal should be deleted" do 11 | animal1 = create(:animal, entry_year: 2, tag: "G123", organization: user.organization) 12 | animal_count = Animal.for_organization(user.organization).count 13 | 14 | visit animals_path 15 | 16 | link = find("a[data-method='delete'][href='#{animal_path(animal1.id)}']") 17 | 18 | within('tbody') do 19 | expect(page).to have_xpath('.//tr', count: animal_count) 20 | link.click 21 | expect(page).to have_xpath('.//tr', count: (animal_count - 1)) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/system/locations/update_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the location Edit page", type: :system do 4 | let(:user) { create(:user) } 5 | let!(:facility) { create(:facility, organization_id: user.organization_id) } 6 | 7 | before do 8 | sign_in user 9 | end 10 | 11 | it "And fill out the form and click the submit button, location should be updated" do 12 | location = create(:location, facility: facility) 13 | 14 | visit edit_facility_location_path(facility, location) 15 | 16 | within('form') do 17 | fill_in 'location_name', with: "Public location" 18 | click_on 'Submit' 19 | end 20 | 21 | expect(page).to have_content 'Location was successfully updated.' 22 | expect(page).to have_content "Public location" 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/system/enclosures/destroy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the enclosure Index page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "Then I click the delete button, enclosure should be deleted" do 11 | enclosure1 = create(:enclosure, organization: user.organization) 12 | enclosure_count = Enclosure.for_organization(user.organization).count 13 | 14 | visit enclosures_path 15 | 16 | link = find("a[data-method='delete'][href='#{enclosure_path(enclosure1.id)}']") 17 | 18 | within('tbody') do 19 | expect(page).to have_xpath('.//tr', count: enclosure_count) 20 | link.click 21 | expect(page).to have_xpath('.//tr', count: (enclosure_count - 1)) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/system/facilities/show_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the facility Show page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "I see information of a specific facility" do 11 | facility = create(:facility, organization: user.organization) 12 | 13 | visit facility_path(facility) 14 | 15 | within('.container') do 16 | expect(page).to have_content facility.name 17 | expect(page).to have_content facility.code 18 | end 19 | end 20 | 21 | it "I can't see a facility of another organization" do 22 | facility = create(:facility) 23 | 24 | visit facility_path(facility) 25 | 26 | expect(page).to have_content("You are not authorized to access this resource.") 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/system/enclosures/show_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the enclosure Show page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "Then I see information of a specific enclosure" do 11 | enclosure = FactoryBot.create(:enclosure, organization: user.organization) 12 | 13 | visit enclosure_path(enclosure) 14 | 15 | expect(page).to have_content(enclosure.name) 16 | expect(page).to have_content(enclosure.location.name) 17 | end 18 | 19 | it "I can't see an enclosure of another organization" do 20 | enclosure = FactoryBot.create(:enclosure) 21 | 22 | visit enclosure_path(enclosure) 23 | 24 | expect(page).to have_content("You are not authorized to access this resource.") 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/models/enclosure.rb: -------------------------------------------------------------------------------- 1 | class Enclosure < ApplicationRecord 2 | has_paper_trail 3 | 4 | include OrganizationScope 5 | include CsvExportable 6 | 7 | belongs_to :location, optional: true 8 | has_many :operations, dependent: :destroy 9 | has_many :measurements, as: :subject 10 | 11 | has_one :cohort, required: false 12 | 13 | validates :name, uniqueness: { scope: %i[organization_id location_id] } 14 | 15 | delegate :name, to: :location, prefix: true, allow_nil: true 16 | delegate :facility_name, to: :location, allow_nil: true 17 | delegate :facility_code, to: :location, allow_nil: true 18 | 19 | def self.exportable_columns 20 | column_names.reject { |col| %w[organization_id facility_id].include? col }.insert(2, 'facility_name') 21 | end 22 | 23 | def empty? 24 | cohort.blank? 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/views/users/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

User

6 |
7 |
8 |
9 |
Organization
10 | <%= @user.organization.name %> 11 |
Email
12 | <%= @user.email %> 13 |
14 | 15 | <%= link_to '<< BACK', users_path, class: "mr-3 mb-3 button-outline-primary" %> 16 | <%= link_to "Edit", edit_user_path(@user), class: "button-outline-primary mt-4" %> 17 |
18 |
19 | -------------------------------------------------------------------------------- /spec/system/exit_types/destroy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the exit type Index page", type: :system do 4 | let(:user) { create(:user, role: 'admin') } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "Then I click the delete button, exit type should be deleted" do 11 | exit_type = FactoryBot.create(:exit_type, organization: user.organization) 12 | exit_type_count = ExitType.for_organization(user.organization).count 13 | 14 | visit exit_types_path 15 | 16 | link = find("a[data-method='delete'][href='#{exit_type_path(exit_type.id)}']") 17 | 18 | within('tbody') do 19 | expect(page).to have_xpath('.//tr', count: exit_type_count) 20 | link.click 21 | expect(page).to have_xpath('.//tr', count: (exit_type_count - 1)) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's 5 | // vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require rails-ujs 14 | //= require activestorage 15 | //= require_tree . 16 | //= require jquery3 17 | //= require reports_kit/application 18 | -------------------------------------------------------------------------------- /spec/system/csv_upload_pagination_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "pagination for file uploads page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | context "when viewing the list of file uploads" do 7 | it "should paginate the list" do 8 | Pagy::DEFAULT[:items] = 3 9 | files = FactoryBot.create_list(:processed_file, 5, organization_id: user.organization_id) 10 | sign_in user 11 | visit file_uploads_path 12 | expect(page).to have_content(files.first.filename, count: 3) 13 | end 14 | 15 | it "should display page numbers" do 16 | Pagy::DEFAULT[:items] = 3 17 | FactoryBot.create_list(:processed_file, 5, organization_id: user.organization_id) 18 | sign_in user 19 | visit file_uploads_path 20 | expect(page).to have_link("2") 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/system/cohorts/create_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the cohort New page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "And fill out the form and click the submit button, cohort should be created" do 11 | female = create(:animal, sex: :female, tag: "G888", organization_id: user.organization_id) 12 | male = create(:animal, sex: :male, tag: "G987", organization_id: user.organization_id) 13 | 14 | visit new_cohort_path 15 | 16 | within('form') do 17 | fill_in 'cohort_name', with: "Gary's cohort" 18 | select female.tag, from: 'cohort_female_id' 19 | select male.tag, from: 'cohort_male_id' 20 | click_on 'Submit' 21 | end 22 | 23 | expect(page).to have_content 'Cohort was successfully created.' 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/system/exit_types/create_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the exit type New page", type: :system do 4 | let(:user) { create(:user, role: 'admin') } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "And fill out the form and click the submit button, exit type should be created" do 11 | visit new_exit_type_path 12 | 13 | within('form') do 14 | fill_in 'exit_type_name', with: "test" 15 | click_on 'Submit' 16 | end 17 | 18 | expect(page).to have_content 'Exit type was successfully created.' 19 | end 20 | 21 | it "And click the submit button without fill the name, an error message must be shown" do 22 | visit new_exit_type_path 23 | 24 | within('form') do 25 | click_on 'Submit' 26 | end 27 | 28 | expect(page).to have_content "Name can't be blank" 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/system/passwords/update_password_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the user Change password page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | context "As an regular user" do 11 | it "Then I can update my own the password" do 12 | visit edit_password_path(user) 13 | 14 | within('form') do 15 | fill_in('Password', with: "anewpassword") 16 | click_on('Submit') 17 | end 18 | 19 | expect(page).to have_content('Password was successfully updated.') 20 | end 21 | 22 | it "Then I should not authorized to change other user password" do 23 | another_user = create(:user) 24 | 25 | visit edit_password_path(another_user) 26 | 27 | expect(page).to have_content('Not authorized') 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/system/locations/show_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the location Show page" do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "Then I see information of a specific location" do 11 | facility = create(:facility) 12 | location = create(:location, facility: facility) 13 | enclosure1 = create(:enclosure, location: location) 14 | enclosure2 = create(:enclosure, location: location) 15 | enclosure3 = create(:enclosure, location: location) 16 | 17 | visit facility_location_path(facility, location) 18 | 19 | expect(page).to have_content(location.name) 20 | expect(page).to have_content(facility.name) 21 | expect(page).to have_content(enclosure1.name) 22 | expect(page).to have_content(enclosure2.name) 23 | expect(page).to have_content(enclosure3.name) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/javascript/controllers/animals_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "stimulus" 2 | 3 | export default class extends Controller { 4 | connect() { 5 | this.displayEntryPoint() 6 | this.onInputChange() 7 | } 8 | 9 | displayEntryPoint() { 10 | document.querySelector('#animal_collected_true').checked 11 | ? this.showEntryPoint() 12 | : this.hideEntryPoint() 13 | } 14 | 15 | hideEntryPoint() { 16 | document.querySelector('.animals__entry-point').style.display = 'none'; 17 | } 18 | 19 | showEntryPoint() { 20 | document.querySelector('.animals__entry-point').style.display = 'block'; 21 | } 22 | 23 | onInputChange() { 24 | window.addEventListener('change', (event) => { 25 | if (event.target.value === 'true') { 26 | this.showEntryPoint(); 27 | } else { 28 | this.hideEntryPoint(); 29 | } 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /db/migrate/20190726182100_create_wild_collections.rb: -------------------------------------------------------------------------------- 1 | class CreateWildCollections < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :wild_collections do |t| 4 | t.boolean :raw, null: false, default: true 5 | t.string :tag 6 | t.date :collection_date 7 | t.string :general_location 8 | t.string :precise_location 9 | t.point :collection_coodinates 10 | t.string :proximity_to_nearest_neighbor 11 | t.string :collection_method_notes 12 | t.string :foot_condition_notes 13 | t.string :collection_depth 14 | t.string :length 15 | t.string :weight 16 | t.string :gonad_score 17 | t.string :predicted_sex 18 | t.string :initial_holding_facility 19 | t.string :final_holding_facility_date_of_arrival 20 | t.string :otc_treatment_completion_date 21 | 22 | t.timestamps 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/system/locations/destroy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the location Index page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "Then I click the delete button, location should be deleted" do 11 | facility = create(:facility, organization: user.organization) 12 | location = create(:location, facility: facility, organization: user.organization) 13 | location_count = Location.for_organization(user.organization).count 14 | 15 | visit facility_locations_path(facility) 16 | 17 | link = find("a[data-method='delete'][href='#{facility_location_path(facility, location)}']") 18 | 19 | within('tbody') do 20 | expect(page).to have_xpath('.//tr', count: location_count) 21 | link.click 22 | expect(page).to have_xpath('.//tr', count: (location_count - 1)) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/system/measurement_types/show_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the measurement_type Show page", type: :system do 4 | let(:user) { create(:user, role: "admin") } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "Then I see information of a specific measurement_type" do 11 | measurement_type = FactoryBot.create(:measurement_type, organization: user.organization) 12 | 13 | visit measurement_type_path(measurement_type) 14 | 15 | expect(page).to have_content(measurement_type.name) 16 | expect(page).to have_content(measurement_type.unit) 17 | end 18 | 19 | it "I can't see a measurement_type of another organization" do 20 | measurement_type = FactoryBot.create(:measurement_type) 21 | 22 | visit measurement_type_path(measurement_type) 23 | 24 | expect(page).to have_content("You are not authorized to access this resource.") 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/views/animals/csv_upload.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Upload Animals

4 |
5 | <%= link_to 'Click here', root_path + 'samples/animal.csv' %> for a sample csv file. 6 |
7 | 8 |
9 | <%= form_with(local: true) do |form| %> 10 |
11 | <%= form.file_field :animal_csv, required: true, accept: 'text/csv' %> 12 |
13 | 14 |
15 |
16 | <%= form.submit('Submit', class: 'button-primary') %> 17 |
18 |
19 | <% end %> 20 |
21 | 22 | <%= link_to '<< Back', animals_path, class: 'button-outline-primary' %> 23 |
24 |
25 | -------------------------------------------------------------------------------- /spec/factories/processed_files.rb: -------------------------------------------------------------------------------- 1 | # rubocop:disable Lint/RedundantCopDisableDirective, Layout/LineLength 2 | # == Schema Information 3 | # 4 | # Table name: processed_files 5 | # 6 | # id :bigint not null, primary key 7 | # filename :string 8 | # original_filename :string 9 | # category :string 10 | # status :string 11 | # job_stats :jsonb not null 12 | # job_errors :text 13 | # created_at :datetime not null 14 | # updated_at :datetime not null 15 | # 16 | # rubocop:enable Layout/LineLength, Lint/RedundantCopDisableDirective 17 | 18 | FactoryBot.define do 19 | factory :processed_file do 20 | filename { "Measurement_12172018(original).csv" } 21 | category { "Measurement" } 22 | status { "Processed" } 23 | job_stats { "{}" } 24 | job_errors {} 25 | temporary_file 26 | organization 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/views/measurements/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

New Measurements

4 |
5 |
6 | <%= label_tag(:data_import_type, 'Select measurement', class: 'sr-only') %> 7 | <%= select_tag(:data_import_type, options_for_select([ 8 | ['I am taking measurements of animals', :animal_count_measurements_template], 9 | ['I want to measure length and gonad score', :length_gonad_score_template] 10 | ])) %> 11 |
12 |
13 | <%= link_to 'Generate Template', '#', class: 'button-primary', id: 'data-import-template-download' %> 14 |
15 |
16 |
17 |
18 | 19 | <%= javascript_pack_tag 'measurements', 'data-turbolinks-track': 'reload' %> 20 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a way to update your development environment automatically. 14 | # Add necessary update steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | puts "\n== Updating database ==" 24 | system! 'bin/rails db:migrate' 25 | 26 | puts "\n== Removing old logs and tempfiles ==" 27 | system! 'bin/rails log:clear tmp:clear' 28 | 29 | puts "\n== Restarting application server ==" 30 | system! 'bin/rails restart' 31 | end 32 | -------------------------------------------------------------------------------- /spec/system/cohorts/update_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the cohort Edit page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "And fill out the form and click the submit button, cohort should be updated" do 11 | cohort = create(:cohort, organization: user.organization) 12 | 13 | visit edit_cohort_path(cohort) 14 | 15 | within('form') do 16 | fill_in 'cohort_name', with: "Gary's new cohort" 17 | click_on 'Submit' 18 | end 19 | 20 | expect(page).to have_content 'Cohort was successfully updated.' 21 | expect(page).to have_content "Gary's new cohort" 22 | end 23 | 24 | it "I can't update a cohort of another organization" do 25 | cohort = create(:cohort) 26 | 27 | visit edit_cohort_path(cohort) 28 | 29 | expect(page).to have_content 'You are not authorized to access this resource.' 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | .tool-versions 16 | .env 17 | 18 | # Ignore uploaded files in development 19 | /storage/* 20 | !/storage/.keep 21 | 22 | /public/assets 23 | .byebug_history 24 | 25 | # Ignore master key for decrypting credentials and more. 26 | /config/master.key 27 | /config/local_env.yml 28 | .*.swp 29 | .idea/ 30 | 31 | .DS_Store 32 | .vscode 33 | .generators 34 | 35 | /public/packs 36 | /public/packs-test 37 | /node_modules 38 | /yarn-error.log 39 | yarn-debug.log* 40 | .yarn-integrity 41 | /coverage 42 | -------------------------------------------------------------------------------- /app/views/exit_types/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: exit_type, local: true, html: { class: "w-full max-w-md" }) do |form| %> 2 | <% if exit_type.errors.any? %> 3 |
4 |

<%= pluralize(exit_type.errors.count, "error") %> prohibited this exit type from being saved:

5 | 10 |
11 | <% end %> 12 | 13 |
14 |
15 | <%= form.label :name, class: 'text-sm text-caption-light font-medium uppercase' %> 16 |
17 |
18 | <%= form.text_field :name, class: 'input mb-2' %> 19 |
20 |
21 | 22 |
23 |
24 | <%= form.submit('Submit', class: 'bg-primary-dark hover:bg-primary text-white font-bold py-2 px-4 rounded inline-flex items-center mt-4') %> 25 |
26 |
27 | <% end %> 28 | -------------------------------------------------------------------------------- /spec/system/animals/upload_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the animal Upload Csv page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before { sign_in user } 7 | 8 | it "Then I can upload a CSV of animals to import" do 9 | visit csv_upload_animals_path 10 | 11 | expect(page).to have_link("Click here") 12 | expect(page).to have_content("Click here for a sample csv file.") 13 | 14 | attach_file('animal_csv', "#{Rails.root}/spec/fixtures/files/animals.csv") 15 | 16 | click_on 'Submit' 17 | 18 | expect(page).to have_current_path(animals_path) 19 | 20 | visit animals_path 21 | 22 | within('tbody') { expect(page).to have_xpath('.//tr', count: 2) } 23 | 24 | # Verify no duplicates and assigned to correct org 25 | animals = Animal.where(tag: "Y002") 26 | expect(animals.count).to eql(1) 27 | expect(animals.first.organization_id).to eql(user.organization.id) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/views/locations/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: location, local: true, url: [facility, location], html: { class: "w-full max-w-md" }) do |form| %> 2 | <% if location.errors.any? %> 3 |
4 |

<%= pluralize(location.errors.count, "error") %> prohibited this location from being saved:

5 | 6 | 11 |
12 | <% end %> 13 | 14 |
15 |
16 | <%= form.label :name, class: 'text-sm text-caption-light font-medium uppercase' %> 17 |
18 |
19 | <%= form.text_field :name, class: 'input mb-2' %> 20 |
21 |
22 | 23 |
24 | <%= form.submit('Submit', class: 'bg-primary-dark hover:bg-primary text-white font-bold py-2 px-4 rounded inline-flex items-center mt-4') %> 25 |
26 | 27 | <% end %> 28 | 29 | -------------------------------------------------------------------------------- /app/views/cohort_imports/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Upload Cohorts

4 |
5 | <%= link_to 'Download a sample csv file', root_path + 'samples/cohort.csv' %> 6 |
7 |
8 | <%= simple_form_for(@cohort_import_form, url: cohort_imports_path) do |f| %> 9 |
10 | <%= f.input :csv_file, as: :file, label: false, required: true, input_html: { accept: 'text/csv' } %> 11 |
12 | 13 |
14 |
15 | <%= f.submit('Submit', class: 'button-primary') %> 16 |
17 |
18 | <% end %> 19 |
20 | 21 | <%= link_to '<< Back', cohorts_path, class: 'button-outline-primary' %> 22 |
23 |
24 | -------------------------------------------------------------------------------- /app/views/devise/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Change your password

2 | 3 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> 4 | <%= render "devise/shared/error_messages", resource: resource %> 5 | <%= f.hidden_field :reset_password_token %> 6 | 7 |
8 | <%= f.label :password, "New password" %>
9 | <% if @minimum_password_length %> 10 | (<%= @minimum_password_length %> characters minimum)
11 | <% end %> 12 | <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %> 13 |
14 | 15 |
16 | <%= f.label :password_confirmation, "Confirm new password" %>
17 | <%= f.password_field :password_confirmation, autocomplete: "new-password" %> 18 |
19 | 20 |
21 | <%= f.submit "Change my password" %> 22 |
23 | <% end %> 24 | 25 | <%= render "users/shared/links" %> 26 | -------------------------------------------------------------------------------- /config/locales/simple_form.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | simple_form: 3 | "yes": 'Yes' 4 | "no": 'No' 5 | required: 6 | text: 'required' 7 | mark: '*' 8 | # You can uncomment the line below if you need to overwrite the whole required html. 9 | # When using html, text and mark won't be used. 10 | # html: '*' 11 | error_notification: 12 | default_message: "Please review the problems below:" 13 | # Examples 14 | # labels: 15 | # defaults: 16 | # password: 'Password' 17 | # user: 18 | # new: 19 | # email: 'E-mail to sign in.' 20 | # edit: 21 | # email: 'E-mail.' 22 | # hints: 23 | # defaults: 24 | # username: 'User name to sign in.' 25 | # password: 'No special characters, please.' 26 | # include_blanks: 27 | # defaults: 28 | # age: 'Rather not say' 29 | # prompts: 30 | # defaults: 31 | # age: 'Select your age' 32 | -------------------------------------------------------------------------------- /app/models/cohort.rb: -------------------------------------------------------------------------------- 1 | class Cohort < ApplicationRecord 2 | has_paper_trail 3 | 4 | include OrganizationScope 5 | include CsvExportable 6 | 7 | belongs_to :male, class_name: 'Animal', optional: true 8 | belongs_to :female, class_name: 'Animal', optional: true 9 | 10 | has_many :animals 11 | has_many :measurements, as: :subject 12 | has_many :mortality_events 13 | 14 | belongs_to :enclosure, required: false 15 | 16 | validates :name, presence: true, uniqueness: { scope: :organization_id } 17 | 18 | delegate :name, to: :enclosure, prefix: true, allow_nil: true 19 | 20 | def self.exportable_columns 21 | %w[id name female_tag male_tag enclosure_name created_at updated_at] 22 | end 23 | 24 | delegate :tag, to: :female, prefix: true, allow_nil: true 25 | delegate :tag, to: :male, prefix: true, allow_nil: true 26 | 27 | def to_s 28 | name.blank? ? "Cohort #{id}" : name 29 | end 30 | 31 | def mortality_count 32 | mortality_events.count 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /spec/models/enclosure_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Enclosure, type: :model do 4 | subject(:enclosure) { build_stubbed(:enclosure) } 5 | 6 | it "has associations" do 7 | is_expected.to belong_to(:location).optional 8 | is_expected.to belong_to(:organization) 9 | is_expected.to have_many(:measurements) 10 | end 11 | 12 | describe "Validations >" do 13 | subject(:enclosure) { build(:enclosure) } 14 | 15 | it "has a valid factory" do 16 | expect(enclosure).to be_valid 17 | end 18 | 19 | it_behaves_like OrganizationScope 20 | 21 | it { is_expected.to validate_uniqueness_of(:name).scoped_to(:organization_id, :location_id) } 22 | end 23 | 24 | describe "#empty?" do 25 | it "returns false with a cohort" do 26 | enclosure.cohort = build(:cohort) 27 | expect(enclosure).not_to be_empty 28 | end 29 | 30 | it "returns true without a cohort" do 31 | expect(enclosure).to be_empty 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/system/enclosures/new_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the enclosure New page", type: :system do 4 | let(:user) { create(:user) } 5 | let!(:location) { create(:location, organization_id: user.organization_id) } 6 | 7 | before do 8 | sign_in user 9 | end 10 | 11 | it "Then I see the enclosure form" do 12 | visit new_enclosure_path 13 | 14 | expect(page).to have_content("New Enclosure") 15 | expect(page).to have_content("Location") 16 | expect(page).to have_content("Name") 17 | expect(page).to have_select("enclosure_location_id", with_options: [location.name_with_facility]) 18 | end 19 | 20 | it "I can create a new enclosure" do 21 | visit new_enclosure_path 22 | 23 | select(location.name, from: 'Location') 24 | fill_in('Name', with: "Frank's Enclosure") 25 | click_button('Submit') 26 | 27 | expect(page).to have_content("Frank's Enclosure") 28 | expect(page).to have_content(location.name) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/assets/images/icons/pencil.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /spec/system/delete_failed_file_upload_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "upload Measurement category", type: :system do 4 | include Devise::Test::IntegrationHelpers 5 | 6 | let(:user) { create(:user) } 7 | 8 | before do 9 | sign_in user 10 | end 11 | 12 | context 'when user file is not successfully uploaded' do 13 | it "user can removes the failed upload if it belong to the current organization" do 14 | processed_file = create(:processed_file, status: "Failed", organization: user.organization) 15 | processed_files_count = ProcessedFile.for_organization(user.organization).count 16 | 17 | visit file_uploads_path 18 | 19 | link = find("a[data-method='delete'][href='#{file_upload_path(processed_file.id)}']") 20 | within('tbody') do 21 | expect(page).to have_xpath('.//tr', count: processed_files_count) 22 | link.click 23 | expect(page).to have_xpath('.//tr', count: (processed_files_count - 1)) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/models/location_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Location, type: :model do 4 | subject(:location) { build_stubbed(:location) } 5 | 6 | it "has associations" do 7 | is_expected.to have_many(:enclosures) 8 | is_expected.to belong_to(:facility) 9 | end 10 | 11 | describe "Validations >" do 12 | subject(:location) { build_stubbed(:location) } 13 | 14 | it "has a valid factory" do 15 | expect(location).to be_valid 16 | end 17 | 18 | include_examples OrganizationScope 19 | 20 | it { is_expected.to validate_presence_of(:name) } 21 | it { is_expected.to validate_presence_of(:facility_id) } 22 | end 23 | 24 | describe "#name_with_facility" do 25 | before do 26 | location.name = "best location" 27 | location.facility.name = "best facility" 28 | end 29 | 30 | it "returns the name of the location combined with the name of the facility" do 31 | expect(location.name_with_facility).to eq("best facility - best location") 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/system/exit_types/index_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the exit type index page", type: :system do 4 | let(:user) { create(:user, role: "admin") } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "Then I see a list of exit_types" do 11 | exit_types = FactoryBot.create_list(:exit_type, 3, organization: user.organization) 12 | 13 | visit exit_types_path 14 | 15 | exit_types.each do |exit_type| 16 | expect(page).to have_content(exit_type.name) 17 | end 18 | end 19 | 20 | it "Then I can't see exit_types of another organization" do 21 | exit_type = FactoryBot.create(:exit_type) 22 | 23 | visit exit_types_path 24 | 25 | expect(page).not_to have_content(exit_type.name) 26 | end 27 | 28 | context "As a non-admin user" do 29 | it "Then I should not have access to the exit types index page" do 30 | user.update(role: "user") 31 | 32 | visit exit_types_path 33 | 34 | expect(page).to have_content 'Not authorized' 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/views/users/shared/_links.html.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 | <%= link_to "Log in", new_session_path(resource_name) %>
3 | <% end %> 4 | 5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "Sign up", new_registration_path(resource_name) %>
7 | <% end %> 8 | 9 | 10 | 11 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 12 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
13 | <% end %> 14 | 15 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 16 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
17 | <% end %> 18 | 19 | <%- if devise_mapping.omniauthable? %> 20 | <%- resource_class.omniauth_providers.each do |provider| %> 21 | <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %>
22 | <% end %> 23 | <% end %> 24 | -------------------------------------------------------------------------------- /spec/support/shared_contexts/rake.rb: -------------------------------------------------------------------------------- 1 | require "rake" 2 | 3 | shared_context "rake" do 4 | let(:rake) { Rake::Application.new } 5 | # use the text we pass to `describe` to calculate the task we're going to run 6 | let(:task_name) { self.class.top_level_description } 7 | # `task_path` is the path to the file itself, relative to `Rails.root` 8 | let(:task_path) { "lib/tasks/#{task_name.split(':').first}" } 9 | subject { rake[task_name] } 10 | 11 | # exclude the path to the task we're testing so we have the task available. 12 | # this only matters when you're running more than one test on a rake task 13 | def loaded_files_excluding_current_rake_file 14 | $LOADED_FEATURES.reject { |file| file == Rails.root.join("#{task_path}.rake").to_s } 15 | end 16 | 17 | before do 18 | Rake.application = rake 19 | Rake.application.rake_require(task_path, [Rails.root.to_s], loaded_files_excluding_current_rake_file) 20 | 21 | # load Rails stack from inside the lib folder 22 | Rake::Task.define_task(:environment) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |

Forgot your password?

6 | 7 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> 8 | <%= render "devise/shared/error_messages", resource: resource %> 9 | 10 |
11 | <%= f.label :email %>
12 | <%= f.email_field :email, autofocus: true, autocomplete: "email", class: "form-control" %> 13 |
14 | 15 |
16 | <%= f.submit "Send me reset password instructions", class: "button-primary" %> 17 |
18 | <% end %> 19 | 20 | <%= render "users/shared/links" %> 21 | 22 |
23 |
24 | <%= image_tag 'fogg.png' %> 25 |
26 |
27 |
-------------------------------------------------------------------------------- /app/views/file_uploads/upload.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

File Upload(s)

4 |

5 | <%= link_to(new_file_upload_path) do %> 6 | 9 | <% end %> 10 | <%= link_to(file_uploads_path) do %> 11 | 14 | <% end %> 15 |

16 | <% @file_uploads.each do |file_upload| %> 17 |

18 | <%= file_upload.result_message %>
19 | Category: <%= file_upload.category %>
20 | Filename: <%= file_upload.filename %>
21 |

22 | <% end %> 23 |
24 |
25 | -------------------------------------------------------------------------------- /spec/models/exit_type_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe ExitType, type: :model do 4 | subject(:exit_type) { build_stubbed(:exit_type) } 5 | 6 | describe "Validations >" do 7 | subject(:exit_type) { build(:exit_type) } 8 | 9 | it "has a valid factory" do 10 | expect(exit_type).to be_valid 11 | end 12 | 13 | it_behaves_like OrganizationScope 14 | 15 | it { is_expected.to validate_presence_of(:name) } 16 | 17 | context "with an associated mortality_events" do 18 | subject(:exit_type) { create(:exit_type) } 19 | 20 | before do 21 | create(:mortality_event, exit_type: exit_type) 22 | end 23 | 24 | it "cannot be destroyed" do 25 | expect { exit_type.destroy } 26 | .not_to change(ExitType, :count) 27 | end 28 | 29 | it "adds an error to the exit_type" do 30 | exit_type.destroy 31 | expect(exit_type.errors.full_messages) 32 | .to eq(["Cannot delete record because dependent mortality events exist"]) 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/tasks/scheduler.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | namespace :scheduler do 4 | desc 'Delete stale temporary files' 5 | # This rake task is meant to be run once per day to cleanse temporary files 6 | # from the database. It will also log if any temporary files have not been 7 | # successfully processed. 8 | task delete_stale_temporary_files: :environment do 9 | # collect all temporary_files over a week old 10 | TemporaryFile.where('created_at < ?', 7.days.ago).each do |file| 11 | # check if they have been processed successfully 12 | if file.processed_file && file.processed_file.status == 'Processed' 13 | # if yes, then we are safe to destroy the file 14 | file.destroy 15 | else 16 | # if no, then we log the file to be handled later 17 | warning_message = <<-MESSAGE 18 | Warning: TemporaryFile with id #{file.id} is more than 19 | 7 days old but has not been processed successfully. 20 | MESSAGE 21 | Rails.logger.warn warning_message 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/system/users/update_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the user Edit page", type: :system do 4 | let(:organization) { create(:organization) } 5 | let(:admin) { create(:user, :admin, organization: organization) } 6 | 7 | before do 8 | sign_in admin 9 | end 10 | 11 | context "As an admin user" do 12 | it "Then I can update a user" do 13 | user = create(:user, organization: organization) 14 | 15 | visit edit_user_path(user) 16 | 17 | within('form') do 18 | fill_in('Email', with: "another@email.com") 19 | fill_in('Password', with: "anewpassword") 20 | click_on('Submit') 21 | end 22 | 23 | expect(page).to have_content('User was successfully updated.') 24 | expect(page).to have_content('another@email.com') 25 | end 26 | end 27 | 28 | it "I can't update a measurement type of another organization" do 29 | user = create(:user) 30 | 31 | visit edit_user_path(user) 32 | 33 | expect(page).to have_content 'You are not authorized to access this resource.' 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/javascript/packs/application.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console:0 */ 2 | // This file is automatically compiled by Webpack, along with any other files 3 | // present in this directory. You're encouraged to place your actual application logic in 4 | // a relevant structure within app/javascript and only use these pack files to reference 5 | // that code so it'll be compiled. 6 | // 7 | // To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate 8 | // layout file, like app/views/layouts/application.html.erb 9 | 10 | 11 | // Uncomment to copy all static images under ../images to the output folder and reference 12 | // them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>) 13 | // or the `imagePath` JavaScript helper below. 14 | // 15 | // const images = require.context('../images', true) 16 | // const imagePath = (name) => images(name, true) 17 | //= require jquery 18 | //= require jquery_ujs 19 | 20 | $(function() { 21 | setTimeout(function(){ 22 | $("[role='alert']").slideUp(500); 23 | }, 10000); 24 | }); 25 | 26 | import "controllers" 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Ruby for Good 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /spec/requests/measurements_index_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'When I visit the Measurements page', type: :system do 4 | let(:organization2) { create(:organization, name: 'Black Abalone') } 5 | let(:user) { create(:user, organization: organization) } 6 | let!(:measurement2) { create(:measurement, organization: organization2, value: organization2.id) } 7 | let(:organization) { create(:organization, name: 'White Abalone') } 8 | let!(:measurements) { create_list(:measurement, 3, organization: user.organization, value: organization.id) } 9 | 10 | before do 11 | sign_in user 12 | visit measurements_path 13 | end 14 | 15 | it 'Displays measurements for current organizations', :aggregate_failures do 16 | within('tbody') do 17 | expect(page).to have_xpath('.//tr', count: measurements.count) 18 | expect(page).to have_content organization.id 19 | end 20 | end 21 | 22 | it 'Then will not display second organization measurement' do 23 | within('tbody') do 24 | expect(page).not_to have_content organization2.id 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/system/animals/show_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the animal Show page", type: :system do 4 | it "I see information of a specific animal" do 5 | user = create(:user, :admin) 6 | cohort = create(:cohort) 7 | animal = create(:animal, cohort: cohort, organization: user.organization) 8 | 9 | sign_in user 10 | 11 | visit animal_path(animal) 12 | 13 | expect(page).to have_content(animal.entry_year) 14 | expect(page).to have_content(animal.entry_date) 15 | expect(page).to have_content(animal.entry_point) 16 | expect(page).to have_content(animal.tag) 17 | expect(page).to have_content(animal.sex.titleize) 18 | expect(page).to have_content(animal.cohort.name) 19 | end 20 | 21 | it "I can't see an animal of another organization" do 22 | user = create(:user, :admin) 23 | cohort = create(:cohort) 24 | animal = create(:animal, cohort: cohort) 25 | 26 | sign_in user 27 | 28 | visit animal_path(animal) 29 | 30 | expect(page).to have_content("You are not authorized to access this resource.") 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/envvars.rake: -------------------------------------------------------------------------------- 1 | namespace :envvars do 2 | desc 'Load environment variables' 3 | task :load do 4 | # Grab the current value of :default_env 5 | environment = fetch(:default_env, {}) 6 | 7 | on roles(:app) do 8 | # Read in the environment file 9 | lines = capture("cat #{fetch(:deploy_to)}/.environment") 10 | lines.each_line do |line| 11 | # Remove the "export " keyword, we have no use for that here 12 | line = line.sub /^export /, "" 13 | # Clean up the input by removing line breaks, tabs etc 14 | line = line.gsub /[\t\r\n\f]+/, "" 15 | 16 | # Grab the key and value from the line 17 | key, value = line.split("=") 18 | 19 | # Remove surrounding quotes if present 20 | value = value.slice(1..-2) if value.start_with?('"') && value.end_with?('"') 21 | 22 | # Store the value in our :default_env copy 23 | environment.store(key, value) 24 | end 25 | 26 | # Finally, update the global :default_env variable again 27 | set :default_env, environment 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/views/enclosures/csv_upload.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Upload Enclosures

4 |
5 | <%= link_to 'Click here', root_path + 'samples/enclosure.csv' %> for a sample csv file. 6 |
7 |
8 | <%= link_to 'Click here', facilities_path %> to view facilities codes and location names. 9 |
10 | 11 |
12 | <%= form_with(local: true) do |form| %> 13 |
14 | <%= form.file_field :enclosure_csv, required: true, accept: 'text/csv' %> 15 |
16 | 17 |
18 |
19 | <%= form.submit('Submit', class: 'button-primary') %> 20 |
21 |
22 | <% end %> 23 |
24 | 25 | <%= link_to '<< Back', enclosures_path, class: 'button-outline-primary' %> 26 |
27 |
28 | -------------------------------------------------------------------------------- /spec/system/cohorts/show_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the cohort Show page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "Then I see information of a specific cohort" do 11 | cohort = create(:cohort, organization: user.organization) 12 | enclosure = create(:enclosure) 13 | 14 | cohort.enclosure = enclosure 15 | cohort.save 16 | 17 | visit cohort_path(cohort) 18 | 19 | within('.container') do 20 | expect(page).to have_content cohort.name 21 | expect(page).to have_content cohort.female_tag 22 | expect(page).to have_content cohort.male_tag 23 | expect(page).to have_content cohort.enclosure.name 24 | end 25 | end 26 | 27 | it "I can't see a cohort of another organization" do 28 | cohort = create(:cohort) 29 | enclosure = create(:enclosure) 30 | 31 | cohort.enclosure = enclosure 32 | cohort.save 33 | 34 | visit cohort_path(cohort) 35 | 36 | expect(page).to have_content("You are not authorized to access this resource.") 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/system/facilities/update_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the facility Edit page", type: :system do 4 | let(:user) { create(:user) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "And fill out the form and click the submit button, the facility is updated" do 11 | facility = create(:facility, organization: user.organization) 12 | 13 | visit edit_facility_path(facility) 14 | 15 | within('form') do 16 | fill_in 'facility_name', with: 'Aquarium of the Pacific' 17 | fill_in 'facility_code', with: 'AOP' 18 | click_on 'Submit' 19 | end 20 | 21 | expect(page).to have_content 'Facility was successfully updated.' 22 | within('.container') do 23 | expect(page).to have_content 'Aquarium of the Pacific' 24 | expect(page).to have_content 'AOP' 25 | end 26 | end 27 | 28 | it "I can't update a facility of another organization" do 29 | facility = create(:facility) 30 | 31 | visit edit_facility_path(facility) 32 | 33 | expect(page).to have_content 'You are not authorized to access this resource.' 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/system/measurement_types/new_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the measurement_type New page", type: :system do 4 | let(:user) { create(:user, role: "admin") } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "Then I see the measurement_type form" do 11 | visit new_measurement_type_path 12 | 13 | expect(page).to have_content("New Measurement Type") 14 | expect(page).to have_content("Name") 15 | expect(page).to have_content("Unit") 16 | end 17 | 18 | it "I can create a new measurement_type" do 19 | visit new_measurement_type_path 20 | 21 | fill_in('Name', with: "Length") 22 | fill_in('Unit', with: "cm") 23 | click_button('Submit') 24 | 25 | expect(page).to have_content("Length") 26 | expect(page).to have_content("cm") 27 | end 28 | 29 | context "As a non-admin user" do 30 | it "Then I should not have access to the measurement types action form" do 31 | user.update(role: "user") 32 | 33 | visit new_measurement_type_path 34 | 35 | expect(page).to have_content 'Not authorized' 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /db/migrate/20200922203301_create_active_storage_tables.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20170806125915) 2 | class CreateActiveStorageTables < ActiveRecord::Migration[5.2] 3 | def change 4 | create_table :active_storage_blobs do |t| 5 | t.string :key, null: false 6 | t.string :filename, null: false 7 | t.string :content_type 8 | t.text :metadata 9 | t.bigint :byte_size, null: false 10 | t.string :checksum, null: false 11 | t.datetime :created_at, null: false 12 | 13 | t.index [ :key ], unique: true 14 | end 15 | 16 | create_table :active_storage_attachments do |t| 17 | t.string :name, null: false 18 | t.references :record, null: false, polymorphic: true, index: false 19 | t.references :blob, null: false 20 | 21 | t.datetime :created_at, null: false 22 | 23 | t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true 24 | t.foreign_key :active_storage_blobs, column: :blob_id 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/system/users/delete_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the users Index page", type: :system do 4 | let(:organization) { create(:organization) } 5 | let(:user) { create(:user, :admin, organization: organization) } 6 | 7 | before do 8 | sign_in user 9 | end 10 | 11 | it "Then I click the delete button, user should be deleted" do 12 | user1 = create(:user, organization: user.organization) 13 | 14 | visit users_path 15 | 16 | link = find("a[data-method='delete'][href='#{user_path(user1.id)}']") 17 | within('tbody') do 18 | expect do 19 | link.click 20 | end.to change { page.all(:xpath, './/tr').count }.by(-1) 21 | end 22 | end 23 | 24 | it "Then I cannot delete myself" do 25 | create(:user, organization: user.organization) 26 | users_count = User.for_organization(user.organization).count 27 | 28 | visit users_path 29 | 30 | within('tbody') do 31 | expect(page).to have_xpath('.//tr', count: users_count) 32 | expect(page).not_to have_selector("a[data-method='delete'][href='#{user_path(user.id)}']") 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/assets/stylesheets/_variables.scss: -------------------------------------------------------------------------------- 1 | $color-primary: #9d9dd3; 2 | $color-secondary: #eb5757; 3 | $color-dark: #333333; 4 | $color-light: #ffffff; 5 | $color-primary-light: rgba($color: $color-primary, $alpha: 0.7); 6 | $color-primary-dark: #6767ca; 7 | $color-warning: #f2994a; 8 | $color-success: #27ae60; 9 | $color-dark-light: #eee; 10 | $color-green-bg: #67CAB8; 11 | $color-grey-bg: #EEEEEE; 12 | 13 | $body-height: calc(100vh - 38px); 14 | 15 | .color-primary { 16 | color: $color-primary !important; 17 | } 18 | 19 | .bg-primary { 20 | background-color: $color-primary !important; 21 | } 22 | 23 | .color-secondary { 24 | color: $color-secondary !important; 25 | } 26 | 27 | .bg-secondary { 28 | background-color: $color-secondary !important; 29 | } 30 | 31 | .bg-secondary { 32 | background-color: $color-secondary !important; 33 | } 34 | 35 | .bg-dark-light { 36 | background-color: $color-dark-light !important; 37 | } 38 | 39 | .bg-dark { 40 | background-color: $color-dark !important; 41 | } 42 | 43 | .color-light { 44 | color: $color-light !important; 45 | } 46 | 47 | .bg-light { 48 | background-color: $color-light !important; 49 | } -------------------------------------------------------------------------------- /app/models/facility.rb: -------------------------------------------------------------------------------- 1 | # rubocop:disable Lint/RedundantCopDisableDirective, Layout/LineLength 2 | # == Schema Information 3 | # 4 | # Table name: facilities 5 | # 6 | # id :bigint not null, primary key 7 | # name :string 8 | # code :string 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # 12 | # rubocop:enable Layout/LineLength, Lint/RedundantCopDisableDirective 13 | 14 | class Facility < ApplicationRecord 15 | has_paper_trail 16 | 17 | include OrganizationScope 18 | include CsvExportable 19 | 20 | has_many :locations 21 | 22 | validates :name, presence: true 23 | validates :code, presence: true, uniqueness: { scope: :organization_id } 24 | 25 | after_commit { Rails.cache.delete('facility_codes') } 26 | 27 | def self.valid_codes 28 | Rails.cache.fetch('facility_codes') { pluck(:code).map(&:upcase) } 29 | end 30 | 31 | def self.valid_code?(code) 32 | valid_codes.include?(code.upcase) 33 | end 34 | 35 | # Replaced by blazer reporting - 1/24/21 36 | # ReportsKit uses this for default labeling 37 | def to_s 38 | code 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.0.3-alpine AS builder 2 | 3 | ARG RAILS_ROOT=/usr/src/app/ 4 | WORKDIR $RAILS_ROOT 5 | 6 | RUN apk add --update --no-cache \ 7 | build-base \ 8 | curl-dev \ 9 | nodejs \ 10 | postgresql-dev \ 11 | tzdata \ 12 | git \ 13 | yarn 14 | 15 | COPY package*.json yarn.lock Gemfile* $RAILS_ROOT 16 | RUN yarn install --check-files --frozen-lockfile &&\ 17 | bundle config --global frozen 1 && bundle install 18 | 19 | ### BUILD STEP DONE ### 20 | 21 | FROM ruby:3.0.3-alpine 22 | 23 | ARG RAILS_ROOT=/usr/src/app/ 24 | WORKDIR $RAILS_ROOT 25 | 26 | RUN set -eux; \ 27 | addgroup -S app; \ 28 | adduser -S -D -G app -H -h $RAILS_ROOT -s /bin/sh app; \ 29 | chown -R app:app $RAILS_ROOT 30 | 31 | RUN apk add --update --no-cache \ 32 | bash\ 33 | nodejs \ 34 | postgresql-client \ 35 | su-exec \ 36 | tzdata \ 37 | yarn \ 38 | && rm -rf /var/cache/apk/* 39 | 40 | COPY --from=builder $RAILS_ROOT $RAILS_ROOT 41 | COPY --from=builder /usr/local/bundle/ /usr/local/bundle/ 42 | 43 | COPY . . 44 | 45 | RUN chown -R app:app $RAILS_ROOT 46 | 47 | EXPOSE 3000 48 | 49 | ENTRYPOINT ["./docker-entrypoint.sh"] 50 | CMD ["abalone"] 51 | -------------------------------------------------------------------------------- /app/models/processed_file.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # rubocop:disable Lint/RedundantCopDisableDirective, Layout/LineLength 4 | # == Schema Information 5 | # 6 | # Table name: processed_files 7 | # 8 | # id :bigint not null, primary key 9 | # filename :string 10 | # original_filename :string 11 | # category :string 12 | # status :string 13 | # job_stats :jsonb not null 14 | # job_errors :text 15 | # created_at :datetime not null 16 | # updated_at :datetime not null 17 | # 18 | # rubocop:enable Layout/LineLength, Lint/RedundantCopDisableDirective 19 | 20 | class ProcessedFile < ApplicationRecord 21 | include OrganizationScope 22 | 23 | belongs_to :temporary_file, 24 | optional: true, 25 | inverse_of: :processed_file, 26 | dependent: :destroy 27 | belongs_to :organization 28 | 29 | validates :job_stats, exclusion: { in: [nil, ""] } 30 | 31 | after_initialize :set_default_job_stats, if: :new_record? 32 | 33 | private 34 | 35 | def set_default_job_stats 36 | self.job_stats = {} 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/views/facilities/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: facility, local: true, html: { class: "w-full max-w-md" }) do |form| %> 2 | <% if facility.errors.any? %> 3 |
4 |

<%= pluralize(facility.errors.count, "error") %> prohibited this facility from being saved:

5 | 10 |
11 | <% end %> 12 |
13 |
14 | <%= form.label :name, class: 'text-sm text-caption-light font-medium uppercase' %> 15 |
16 |
17 | <%= form.text_field :name, class: 'input mb-2' %> 18 |
19 |
20 |
21 |
22 | <%= form.label :code, class: 'text-sm text-caption-light font-medium uppercase' %> 23 |
24 |
25 | <%= form.text_field :code, class: 'input mb-2' %> 26 |
27 |
28 |
29 |
30 | <%= form.submit('Submit', class: 'bg-primary-dark hover:bg-primary text-white font-bold py-2 px-4 rounded inline-flex items-center mt-4') %> 31 |
32 |
33 | <% end %> 34 | -------------------------------------------------------------------------------- /spec/system/enclosures/update_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the enclosure Edit page", type: :system do 4 | let(:user) { create(:user) } 5 | let!(:location) { create(:location, organization_id: user.organization_id) } 6 | 7 | before do 8 | sign_in user 9 | end 10 | 11 | it "And fill out the form and click the submit button, enclosure should be updated" do 12 | enclosure = create(:enclosure, organization: user.organization) 13 | 14 | visit edit_enclosure_path(enclosure) 15 | 16 | within('form') do 17 | select(location.name_with_facility, from: 'Location') 18 | fill_in 'enclosure_name', with: "Gary's old enclosure" 19 | click_on 'Submit' 20 | end 21 | 22 | expect(page).to have_content 'Enclosure was successfully updated.' 23 | expect(page).to have_content(location.name) 24 | expect(page).to have_content "Gary's old enclosure" 25 | end 26 | 27 | it "I can't update an enclosure of another organization" do 28 | enclosure = create(:enclosure) 29 | 30 | visit edit_enclosure_path(enclosure) 31 | 32 | expect(page).to have_content 'You are not authorized to access this resource.' 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/system/measurement_types/destroy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the measurement_type Index page", type: :system do 4 | let(:user) { create(:user, role: "admin") } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | it "Then I click the delete button, measurement_type should be deleted" do 11 | measurement_type1 = create(:measurement_type, organization: user.organization) 12 | measurement_type_count = MeasurementType.for_organization(user.organization).count 13 | 14 | visit measurement_types_path 15 | 16 | link = find("a[data-method='delete'][href='#{measurement_type_path(measurement_type1.id)}']") 17 | 18 | within('tbody') do 19 | expect(page).to have_xpath('.//tr', count: measurement_type_count) 20 | link.click 21 | expect(page).to have_xpath('.//tr', count: (measurement_type_count - 1)) 22 | end 23 | end 24 | 25 | context "As a non-admin user" do 26 | it "Then I should not have access to the measurement types action form" do 27 | user.update(role: "user") 28 | 29 | visit measurement_types_path 30 | 31 | expect(page).to have_content 'Not authorized' 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /app/views/measurement_types/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: measurement_type, local: true, html: { class: "w-full max-w-md" }) do |form| %> 2 | <% if measurement_type.errors.any? %> 3 |
4 |

<%= pluralize(measurement_type.errors.count, "error") %> prohibited this measurement_type from being saved:

5 | 6 | 11 |
12 | <% end %> 13 | 14 |
15 |
16 | <%= form.label :name, class: 'text-sm text-caption-light font-medium uppercase' %> 17 |
18 |
19 | <%= form.text_field :name, class: 'input mb-2' %> 20 |
21 | 22 |
23 | <%= form.label :unit, class: 'text-sm text-caption-light font-medium uppercase' %> 24 |
25 |
26 | <%= form.text_field :unit, class: 'input mb-2' %> 27 |
28 | 29 |
30 | <%= form.submit('Submit', class: 'bg-primary-dark hover:bg-primary text-white font-bold py-2 px-4 rounded inline-flex items-center mt-4') %> 31 |
32 |
33 | <% end %> 34 | -------------------------------------------------------------------------------- /app/views/file_uploads/show_processing_csv_errors.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Detailed processed file with errors

4 | 5 |
6 | <%= link_to "".html_safe, new_file_upload_path %> 7 |
8 |
9 | Filename: <%= @processed_file.filename %> processed at <%= @processed_file.updated_at %>
10 | Processed At: <%= @processed_file.updated_at %>
11 | Category: <%= @processed_file.category %>
12 | Statistics: <%= @processed_file.job_stats %>
13 | 14 |
15 | 16 |
17 | 18 | 19 | <%- @headers.each do |attr|%> 20 | 21 | <%- end %> 22 | 23 | 24 | <%- @records.each do |record| %> 25 | 26 | <%- @headers.each do |attr|%> 27 | 28 | <%- end %> 29 | 30 | <% end %> 31 | 32 |
<%= attr %>
<%= record[attr.to_s] %>
33 |
34 |
35 |
36 | -------------------------------------------------------------------------------- /app/views/reports/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |

Reports

6 | 7 |
8 |
    9 |
  • 10 | <%= link_to "Total Animals by Cohort", reports_path %> 11 |
  • 12 |
13 |
14 |
15 | 16 |
17 | <%= render_report 'total_animals_by_cohort', 18 | context_params: { organization_id: current_user.organization_id }, 19 | actions: [] do |report| %> 20 | <%= report.form do |f| %> 21 |
22 | <%= f.multi_autocomplete :cohort, placeholder: 'Cohort...' %> 23 | <%= f.date_range :created_at %> 24 |
25 | <% end %> 26 |
27 | <%= report.export_csv_button 'Download CSV', class: 'button-primary' %> 28 | <%= report.export_xls_button 'Download XLS', class: 'button-primary' %> 29 |
30 | <% end %> 31 |
32 |
33 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path('..', __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to setup or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at anytime and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies 21 | system! 'bin/yarn' 22 | 23 | # puts "\n== Copying sample files ==" 24 | # unless File.exist?('config/database.yml') 25 | # FileUtils.cp 'config/database.yml.sample', 'config/database.yml' 26 | # end 27 | 28 | puts "\n== Preparing database ==" 29 | system! 'bin/rails db:prepare' 30 | 31 | puts "\n== Removing old logs and tempfiles ==" 32 | system! 'bin/rails log:clear tmp:clear' 33 | 34 | puts "\n== Restarting application server ==" 35 | system! 'bin/rails restart' 36 | end 37 | -------------------------------------------------------------------------------- /app/views/file_uploads/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Processed File

4 |
5 |
6 | File name: 7 |
8 |
9 | <%= @processed_file.filename %> 10 |
11 |
12 |
13 |
14 | Processed at: 15 |
16 |
17 | <%= @processed_file.updated_at %> 18 |
19 |
20 |
21 |
22 | 23 | 24 | <%- @headers.each do |attr|%> 25 | 26 | <%- end %> 27 | 28 | 29 | <%- @records.each do |record| %> 30 | 31 | <%- record.display_data.each do |data_point|%> 32 | 33 | <%- end %> 34 | 35 | <% end %> 36 | 37 |
<%= attr %>
<%= data_point %>
38 |
39 |
40 |
41 | -------------------------------------------------------------------------------- /app/views/measurement_types/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

<%= notice %>

4 |
5 | <%= link_to measurement_types_path do %> 6 | 9 | <% end %> 10 | <%= link_to edit_measurement_type_path(@measurement_type) do %> 11 | 14 | <% end %> 15 |
16 |
17 |

Measurement Type

18 |
19 |
20 |
Name
21 | <%= @measurement_type.name %> 22 |
Unit
23 | <%= @measurement_type.unit %> 24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Log in

5 | 6 | <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> 7 |
8 | <%= f.label :email %>
9 | <%= f.email_field :email, autofocus: true, autocomplete: "email", class: "border form-control w-44" %> 10 |
11 | 12 |
13 | <%= f.label :password %>
14 | <%= f.password_field :password, autocomplete: "current-password", class: "border form-control w-44" %> 15 |
16 | 17 | <% if devise_mapping.rememberable? %> 18 |
19 | <%= f.check_box :remember_me %> 20 | <%= f.label :remember_me %> 21 |
22 | <% end %> 23 | 24 | 25 | 26 |
27 | <%= f.submit "LOGIN", class: "button-primary" %> 28 |
29 | <% end %> 30 | <%= render "users/shared/links" %> 31 |
32 |
33 | <%= image_tag 'fogg.png' %> 34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | include Pagy::Backend 3 | before_action :authenticate_user! 4 | before_action :set_paper_trail_whodunnit 5 | before_action :set_current_user 6 | 7 | helper_method def current_organization 8 | current_user.organization 9 | end 10 | 11 | def authorize_admin! 12 | redirect_to root_path, alert: "Not authorized" unless current_user.admin? 13 | end 14 | 15 | # Auth check for Blazer reporting 16 | def require_organization 17 | redirect_to root_path unless current_user && current_organization 18 | end 19 | 20 | # Save current_user to a place Blazer and models can access it. 21 | def set_current_user 22 | Current.user = current_user 23 | end 24 | 25 | # Blazer's before_action_method call to 26 | # make sure a user is logged in and associated 27 | # with an organizatio as the app does. 28 | def blazer_setup 29 | require_organization 30 | set_current_user 31 | end 32 | 33 | # Error page for CanCanCan 34 | rescue_from CanCan::AccessDenied do 35 | flash[:alert] = 'You are not authorized to access this resource.' 36 | redirect_back fallback_location: root_path, status: 302 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/system/users/new_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the User New page", type: :system do 4 | let(:user) { create(:user, :admin) } 5 | 6 | before do 7 | sign_in user 8 | end 9 | 10 | context "As an admin user" do 11 | it "Then I see a user form" do 12 | visit new_user_path 13 | 14 | expect(page).to have_content("New User") 15 | expect(page).to have_content("Email") 16 | expect(page).to have_content("Password") 17 | expect(page).to have_select("user_role", with_options: %w[user admin]) 18 | end 19 | 20 | it "I can create a new user" do 21 | visit new_user_path 22 | 23 | fill_in("Email", with: "test@example.com") 24 | fill_in("Password", with: "password") 25 | select("user", from: 'Role') 26 | click_button('Submit') 27 | 28 | expect(page).to have_content("test@example.com") 29 | expect(page).to have_content(user.organization.name) 30 | end 31 | end 32 | 33 | context "As a non-admin user" do 34 | it "Then I should not have access to the user form" do 35 | user.update(role: "user") 36 | 37 | visit new_user_path 38 | 39 | expect(page).to have_content 'Not authorized' 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /app/views/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Change Password

4 |
5 | <%= form_with(url: password_path(@user), model: @user, local: true) do |form| %> 6 | <% if @user.errors.any? %> 7 |
8 |

<%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:

9 | 10 |
    11 | <% @user.errors.full_messages.each do |message| %> 12 |
  • <%= message %>
  • 13 | <% end %> 14 |
15 |
16 | <% end %> 17 | 18 |
19 | <%= form.label :password, class: 'label' %> 20 | <%= form.password_field :password, class: 'input' %> 21 |
22 | 23 |
24 |
25 | <%= form.submit('Submit', class: 'button-primary') %> 26 |
27 |
28 | <% end %> 29 |
30 | <%= link_to('<< Back', users_path, class: "button-outline-primary") if @user.admin? %> 31 |
32 |
33 | -------------------------------------------------------------------------------- /db/migrate/20200927001420_install_blazer.rb: -------------------------------------------------------------------------------- 1 | class InstallBlazer < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :blazer_queries do |t| 4 | t.references :creator 5 | t.string :name 6 | t.text :description 7 | t.text :statement 8 | t.string :data_source 9 | t.timestamps null: false 10 | end 11 | 12 | create_table :blazer_audits do |t| 13 | t.references :user 14 | t.references :query 15 | t.text :statement 16 | t.string :data_source 17 | t.datetime :created_at 18 | end 19 | 20 | create_table :blazer_dashboards do |t| 21 | t.references :creator 22 | t.string :name 23 | t.timestamps null: false 24 | end 25 | 26 | create_table :blazer_dashboard_queries do |t| 27 | t.references :dashboard 28 | t.references :query 29 | t.integer :position 30 | t.timestamps null: false 31 | end 32 | 33 | create_table :blazer_checks do |t| 34 | t.references :creator 35 | t.references :query 36 | t.string :state 37 | t.string :schedule 38 | t.text :emails 39 | t.text :slack_channels 40 | t.string :check_type 41 | t.text :message 42 | t.datetime :last_run_at 43 | t.timestamps null: false 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_action :authorize_admin! 3 | load_and_authorize_resource 4 | 5 | def index 6 | @users = current_organization.users 7 | end 8 | 9 | def show 10 | redirect_to users_path unless @user 11 | end 12 | 13 | def new 14 | @user = User.new 15 | end 16 | 17 | def create 18 | @user = User.new(user_params) 19 | if @user.save 20 | redirect_to user_path(@user), notice: 'User was successfully created.' 21 | else 22 | render :new 23 | end 24 | end 25 | 26 | def edit; end 27 | 28 | def update 29 | if @user.update(user_params) 30 | redirect_to @user, notice: 'User was successfully updated.' 31 | else 32 | render :edit 33 | end 34 | end 35 | 36 | def destroy 37 | notice_message = 'User cannot delete itself.' 38 | 39 | unless @user == current_user 40 | @user.destroy 41 | notice_message = 'User was successfully destroyed.' 42 | end 43 | 44 | redirect_to users_url, notice: notice_message 45 | end 46 | 47 | private 48 | 49 | def user_params 50 | params.require(:user).permit( 51 | :email, 52 | :password, 53 | :role 54 | ).merge(organization_id: current_organization.id) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/system/users/show_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "When I visit the user Show page", type: :system do 4 | it "as an admin, I see information of a specific user" do 5 | user = create(:user, :admin) 6 | sign_in user 7 | 8 | visit user_path(user) 9 | 10 | expect(page).to have_content(user.email) 11 | expect(page).to have_content(user.organization.name) 12 | end 13 | 14 | it "as a user, I see information of a specific user" do 15 | user = create(:user) 16 | sign_in user 17 | 18 | visit user_path(user) 19 | 20 | expect(page).not_to have_content(user.email) 21 | expect(page).to have_content(user.organization.name) 22 | end 23 | 24 | it "as a user who's not logged in, I see no user specific information" do 25 | user = create(:user) 26 | 27 | visit user_path(user) 28 | 29 | expect(page).not_to have_content(user.email) 30 | expect(page).not_to have_content("Your organization: #{user.organization.name}") 31 | end 32 | 33 | it "I can't see a facility of another organization" do 34 | user = create(:user, :admin) 35 | sign_in user 36 | 37 | another_user = create(:user) 38 | visit user_path(another_user) 39 | 40 | expect(page).to have_content("You are not authorized to access this resource.") 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /app/views/enclosures/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: enclosure, local: true, html: { class: "w-full" }) do |form| %> 2 | <% if enclosure.errors.any? %> 3 |
4 |

<%= pluralize(enclosure.errors.count, "error") %> prohibited this enclosure from being saved:

5 | 6 | 11 |
12 | <% end %> 13 | 14 |
15 |
16 | <%= form.label :location_id, class: 'text-sm text-caption-light font-medium uppercase' %> 17 |
18 |
19 | <%= form.collection_select :location_id, @locations, :id, :name_with_facility, { include_blank: 'Select Location' }, { class: 'input w-full mb-2' } %> 20 |
21 |
22 | 23 |
24 |
25 | <%= form.label :name, class: 'text-sm text-caption-light font-medium uppercase' %> 26 |
27 |
28 | <%= form.text_field :name, class: 'input w-full mb-2' %> 29 |
30 |
31 | 32 |
33 |
34 | <%= form.submit('Submit', class: 'bg-primary-dark hover:bg-primary text-white font-bold py-2 px-4 rounded inline-flex items-center mt-4') %> 35 |
36 |
37 | <% end %> 38 | -------------------------------------------------------------------------------- /spec/models/processed_file_spec.rb: -------------------------------------------------------------------------------- 1 | # rubocop:disable Lint/RedundantCopDisableDirective, Layout/LineLength 2 | # == Schema Information 3 | # 4 | # Table name: processed_files 5 | # 6 | # id :bigint not null, primary key 7 | # filename :string 8 | # original_filename :string 9 | # category :string 10 | # status :string 11 | # job_stats :jsonb not null 12 | # job_errors :text 13 | # created_at :datetime not null 14 | # updated_at :datetime not null 15 | # 16 | # rubocop:enable Layout/LineLength, Lint/RedundantCopDisableDirective 17 | 18 | require 'rails_helper' 19 | 20 | RSpec.describe ProcessedFile, type: :model do 21 | subject(:processed_file) { build_stubbed(:processed_file) } 22 | 23 | it "has associations" do 24 | is_expected.to belong_to(:temporary_file).dependent(:destroy).optional 25 | is_expected.to belong_to(:organization) 26 | end 27 | 28 | describe "Validations >" do 29 | subject(:processed_file) { build(:processed_file) } 30 | 31 | it "has a valid factory" do 32 | expect(processed_file).to be_valid 33 | end 34 | 35 | it_behaves_like OrganizationScope 36 | 37 | it { is_expected.to validate_exclusion_of(:job_stats).in_array([nil, ""]) } 38 | end 39 | end 40 | --------------------------------------------------------------------------------