diff --git a/.github/workflows/ci-features-admin.yml b/.github/workflows/ci-features-admin.yml index ff491a422..539bafd8e 100644 --- a/.github/workflows/ci-features-admin.yml +++ b/.github/workflows/ci-features-admin.yml @@ -74,7 +74,7 @@ jobs: sudo apt-get -y install libpq-dev google-chrome-stable - name: Install NodeJS - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: '12' diff --git a/.github/workflows/ci-features-comments.yml b/.github/workflows/ci-features-comments.yml index 398fc30e5..3aba86370 100644 --- a/.github/workflows/ci-features-comments.yml +++ b/.github/workflows/ci-features-comments.yml @@ -74,7 +74,7 @@ jobs: sudo apt-get -y install libpq-dev google-chrome-stable - name: Install NodeJS - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: '12' diff --git a/.github/workflows/ci-features-conversations.yml b/.github/workflows/ci-features-conversations.yml index c1fa33b66..225b9a129 100644 --- a/.github/workflows/ci-features-conversations.yml +++ b/.github/workflows/ci-features-conversations.yml @@ -74,7 +74,7 @@ jobs: sudo apt-get -y install libpq-dev google-chrome-stable - name: Install NodeJS - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: '12' diff --git a/.github/workflows/ci-features-crops.yml b/.github/workflows/ci-features-crops.yml index 4a999de2a..70a69a7da 100644 --- a/.github/workflows/ci-features-crops.yml +++ b/.github/workflows/ci-features-crops.yml @@ -74,7 +74,7 @@ jobs: sudo apt-get -y install libpq-dev google-chrome-stable - name: Install NodeJS - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: '12' diff --git a/.github/workflows/ci-features-gardens.yml b/.github/workflows/ci-features-gardens.yml index f64a8d36a..b48002eb3 100644 --- a/.github/workflows/ci-features-gardens.yml +++ b/.github/workflows/ci-features-gardens.yml @@ -74,7 +74,7 @@ jobs: sudo apt-get -y install libpq-dev google-chrome-stable - name: Install NodeJS - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: '12' diff --git a/.github/workflows/ci-features-harvests.yml b/.github/workflows/ci-features-harvests.yml index 41a29f859..00eedbb9a 100644 --- a/.github/workflows/ci-features-harvests.yml +++ b/.github/workflows/ci-features-harvests.yml @@ -74,7 +74,7 @@ jobs: sudo apt-get -y install libpq-dev google-chrome-stable - name: Install NodeJS - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: '12' diff --git a/.github/workflows/ci-features-home.yml b/.github/workflows/ci-features-home.yml index 1676ca10e..f58358f28 100644 --- a/.github/workflows/ci-features-home.yml +++ b/.github/workflows/ci-features-home.yml @@ -74,7 +74,7 @@ jobs: sudo apt-get -y install libpq-dev google-chrome-stable - name: Install NodeJS - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: '12' diff --git a/.github/workflows/ci-features-members.yml b/.github/workflows/ci-features-members.yml index 8bce06f19..9fa4a307d 100644 --- a/.github/workflows/ci-features-members.yml +++ b/.github/workflows/ci-features-members.yml @@ -74,7 +74,7 @@ jobs: sudo apt-get -y install libpq-dev google-chrome-stable - name: Install NodeJS - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: '12' diff --git a/.github/workflows/ci-features-places.yml b/.github/workflows/ci-features-places.yml index ebb175946..f65b2185b 100644 --- a/.github/workflows/ci-features-places.yml +++ b/.github/workflows/ci-features-places.yml @@ -74,7 +74,7 @@ jobs: sudo apt-get -y install libpq-dev google-chrome-stable - name: Install NodeJS - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: '12' diff --git a/.github/workflows/ci-features-plantings.yml b/.github/workflows/ci-features-plantings.yml index fdfc7b0b9..eafa326c9 100644 --- a/.github/workflows/ci-features-plantings.yml +++ b/.github/workflows/ci-features-plantings.yml @@ -74,7 +74,7 @@ jobs: sudo apt-get -y install libpq-dev google-chrome-stable - name: Install NodeJS - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: '12' diff --git a/.github/workflows/ci-features-posts.yml b/.github/workflows/ci-features-posts.yml index 9b973163a..4d58f546c 100644 --- a/.github/workflows/ci-features-posts.yml +++ b/.github/workflows/ci-features-posts.yml @@ -74,7 +74,7 @@ jobs: sudo apt-get -y install libpq-dev google-chrome-stable - name: Install NodeJS - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: '12' diff --git a/.github/workflows/ci-features-seeds.yml b/.github/workflows/ci-features-seeds.yml index d38390c95..e5d5bdbb3 100644 --- a/.github/workflows/ci-features-seeds.yml +++ b/.github/workflows/ci-features-seeds.yml @@ -74,7 +74,7 @@ jobs: sudo apt-get -y install libpq-dev google-chrome-stable - name: Install NodeJS - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: '12' diff --git a/.github/workflows/ci-features-timeline.yml b/.github/workflows/ci-features-timeline.yml index a5c8c830b..0ddc0660d 100644 --- a/.github/workflows/ci-features-timeline.yml +++ b/.github/workflows/ci-features-timeline.yml @@ -74,7 +74,7 @@ jobs: sudo apt-get -y install libpq-dev google-chrome-stable - name: Install NodeJS - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: '12' diff --git a/.github/workflows/ci-features.yml b/.github/workflows/ci-features.yml index c2d3cc2ba..4b5afc437 100644 --- a/.github/workflows/ci-features.yml +++ b/.github/workflows/ci-features.yml @@ -74,7 +74,7 @@ jobs: sudo apt-get -y install libpq-dev google-chrome-stable - name: Install NodeJS - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: '12' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eaed8103e..f9aa42abf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,7 +89,7 @@ jobs: sudo apt-get -y install libpq-dev google-chrome-stable - name: Install NodeJS - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: '12' diff --git a/Gemfile.lock b/Gemfile.lock index 6247f9810..41f0f1878 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -142,7 +142,7 @@ GEM erubi (>= 1.0.0) rack (>= 0.9.0) rouge (>= 1.0.0) - bigdecimal (3.2.2) + bigdecimal (3.2.3) bluecloth (2.2.0) bonsai-elasticsearch-rails (7.0.1) elasticsearch-model (< 8) @@ -198,7 +198,7 @@ GEM comfy_bootstrap_form (4.0.9) rails (>= 5.0.0) concurrent-ruby (1.3.5) - connection_pool (2.5.3) + connection_pool (2.5.4) crass (1.0.6) crowdin-api (1.12.0) open-uri (>= 0.1.0, < 0.2.0) @@ -257,9 +257,9 @@ GEM excon (1.2.5) logger execjs (2.10.0) - factory_bot (6.5.4) + factory_bot (6.5.5) activesupport (>= 6.1.0) - factory_bot_rails (6.5.0) + factory_bot_rails (6.5.1) factory_bot (~> 6.5) railties (>= 6.1.0) faker (3.5.2) @@ -475,7 +475,7 @@ GEM date stringio public_suffix (6.0.1) - puma (7.0.0) + puma (7.0.2) nio4r (~> 2.0) query_diet (0.7.2) racc (1.8.1) diff --git a/app/controllers/api/v1/activities_controller.rb b/app/controllers/api/v1/activities_controller.rb new file mode 100644 index 000000000..a79f9c778 --- /dev/null +++ b/app/controllers/api/v1/activities_controller.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Api + module V1 + # This controller is intentionally empty. + # The `jsonapi-resources` gem provides the necessary actions. + class ActivitiesController < BaseController + end + end +end diff --git a/app/controllers/crops_controller.rb b/app/controllers/crops_controller.rb index 5a3822e64..8a76566d0 100644 --- a/app/controllers/crops_controller.rb +++ b/app/controllers/crops_controller.rb @@ -192,6 +192,8 @@ class CropsController < ApplicationController :parent_id, :perennial, :request_notes, :reason_for_rejection, :rejection_notes, + :row_spacing, :spread, :height, + :sowing_method, :sun_requirements, :growing_degree_days, scientific_names_attributes: %i(scientific_name _destroy id) ) end diff --git a/app/controllers/gardens_controller.rb b/app/controllers/gardens_controller.rb index 7bbf05f7b..3d50dfa03 100644 --- a/app/controllers/gardens_controller.rb +++ b/app/controllers/gardens_controller.rb @@ -20,6 +20,7 @@ class GardensController < DataController def show @current_plantings = @garden.plantings.current.where.not(failed: true).includes(:crop, :owner).order(planted_at: :desc) @current_activities = @garden.activities.current.includes(:owner).order(created_at: :desc) + @finished_activities = @garden.activities.finished.includes(:owner).order(created_at: :desc) @finished_plantings = @garden.plantings.finished.includes(:crop) @suggested_companions = Crop.approved.where( id: CropCompanion.where(crop_a_id: @current_plantings.select(:crop_id)).select(:crop_b_id) diff --git a/app/controllers/plantings_controller.rb b/app/controllers/plantings_controller.rb index 07a73cbce..c123fb869 100644 --- a/app/controllers/plantings_controller.rb +++ b/app/controllers/plantings_controller.rb @@ -37,6 +37,7 @@ class PlantingsController < DataController @photos = @planting.photos.includes(:owner).order(date_taken: :desc) @harvests = Harvest.search(where: { planting_id: @planting.id }) @current_activities = @planting.activities.current.includes(:owner).order(created_at: :desc) + @finished_activities = @planting.activities.finished.includes(:owner).order(created_at: :desc) @matching_seeds = matching_seeds @crop = @planting.crop diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 526beadc1..6377e9aa2 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -38,9 +38,9 @@ module ApplicationHelper return 'today' if from_time.is_a?(Date) && (from_time == to_time) return 'now' if from_time == to_time - return distance_of_time_in_words(from_time, to_time, include_seconds:) + ' ago' if from_time > to_time + return "#{distance_of_time_in_words(from_time, to_time, include_seconds:)} ago" if from_time < to_time - 'in ' + distance_of_time_in_words(from_time, to_time, include_seconds:) + "in #{distance_of_time_in_words(from_time, to_time, include_seconds:)}" end def count_github_contibutors diff --git a/app/models/activity.rb b/app/models/activity.rb index cbdfbbf89..30bde60e1 100644 --- a/app/models/activity.rb +++ b/app/models/activity.rb @@ -30,4 +30,20 @@ class Activity < ApplicationRecord def to_s name end + + def garden_name + garden&.name + end + + def garden_slug + garden&.slug + end + + def planting_name + planting&.crop&.name + end + + def planting_slug + planting&.crop&.slug + end end diff --git a/app/models/concerns/open_farm_data.rb b/app/models/concerns/open_farm_data.rb index 64bcee0b5..891a71c07 100644 --- a/app/models/concerns/open_farm_data.rb +++ b/app/models/concerns/open_farm_data.rb @@ -8,14 +8,6 @@ module OpenFarmData fetch_attr('main_image_path') end - def height - fetch_attr('height') - end - - def spread - fetch_attr('spread') - end - def svg_icon icon = fetch_attr('svg_icon') return icon if icon.present? @@ -31,10 +23,6 @@ module OpenFarmData fetch_attr('description') end - def row_spacing - fetch_attr('row_spacing') - end - def common_names fetch_attr('common_names') end @@ -43,22 +31,10 @@ module OpenFarmData fetch_attr('binomial_name') end - def sowing_method - fetch_attr('sowing_method') - end - def main_image_path fetch_attr('main_image_path') end - def sun_requirements - fetch_attr('sun_requirements') - end - - def growing_degree_days - fetch_attr('growing_degree_days') - end - def processing_pictures fetch_attr('processing_pictures') end diff --git a/app/resources/api/v1/activity_resource.rb b/app/resources/api/v1/activity_resource.rb new file mode 100644 index 000000000..72256146b --- /dev/null +++ b/app/resources/api/v1/activity_resource.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Api + module V1 + class ActivityResource < BaseResource + immutable + + has_one :owner, class_name: 'Member' + has_one :garden + has_one :planting + + attribute :name + attribute :description + attribute :category + attribute :finished + attribute :due_date + + filter :owner + filter :owner_id + filter :garden + filter :garden_id + filter :planting + filter :planting_id + filter :category + end + end +end diff --git a/app/resources/api/v1/garden_resource.rb b/app/resources/api/v1/garden_resource.rb index 4b4a32bbe..47dcd7858 100644 --- a/app/resources/api/v1/garden_resource.rb +++ b/app/resources/api/v1/garden_resource.rb @@ -10,6 +10,13 @@ module Api has_many :photos attribute :name + + filter :owner + filter :owner_id + filter :active + filter :garden_type + filter :location + filter :slug end end end diff --git a/app/resources/api/v1/harvest_resource.rb b/app/resources/api/v1/harvest_resource.rb index 502bee5bf..2013390a6 100644 --- a/app/resources/api/v1/harvest_resource.rb +++ b/app/resources/api/v1/harvest_resource.rb @@ -16,6 +16,15 @@ module Api attribute :weight_quantity attribute :weight_unit attribute :si_weight + + filter :owner + filter :owner_id + filter :crop + filter :crop_id + filter :planting + filter :planting_id + filter :plant_part + filter :harvested_at end end end diff --git a/app/resources/api/v1/planting_resource.rb b/app/resources/api/v1/planting_resource.rb index 4d24036fb..8a5bd4659 100644 --- a/app/resources/api/v1/planting_resource.rb +++ b/app/resources/api/v1/planting_resource.rb @@ -36,6 +36,10 @@ module Api filter :owner filter :owner_id filter :finished + filter :active, apply: ->(records, _value, _options) { records.active } + filter :failed, apply: ->(records, _value, _options) { records.failed } + filter :sunniness + filter :perennial, apply: ->(records, _value, _options) { records.perennial } attribute :percentage_grown delegate :percentage_grown, to: :@model diff --git a/app/resources/api/v1/seed_resource.rb b/app/resources/api/v1/seed_resource.rb index 82dd53ee2..dc1016cd9 100644 --- a/app/resources/api/v1/seed_resource.rb +++ b/app/resources/api/v1/seed_resource.rb @@ -17,6 +17,15 @@ module Api attribute :organic attribute :gmo attribute :heirloom + + filter :owner + filter :owner_id + filter :crop + filter :crop_id + filter :tradable_to + filter :organic + filter :gmo + filter :heirloom end end end diff --git a/app/views/crops/_form.html.haml b/app/views/crops/_form.html.haml index 18d082172..8797bfe81 100644 --- a/app/views/crops/_form.html.haml +++ b/app/views/crops/_form.html.haml @@ -41,6 +41,14 @@ = f.radio_button(:perennial, true, label: "Perennial") %span.help-block Living more than two years + %h2 OpenFarm Data + = f.number_field :row_spacing, label: 'Row Spacing (cm)', min: 0 + = f.number_field :spread, label: 'Spread (cm)', min: 0 + = f.number_field :height, label: 'Height (cm)', min: 0 + = f.text_field :sowing_method + = f.text_field :sun_requirements + = f.number_field :growing_degree_days, min: 0 + - unless @crop.approved? = link_to 'Search wikipedia', "https://en.wikipedia.org/w/index.php?search=#{@crop.name}", target: '_blank' = f.url_field :en_wikipedia_url, id: "en_wikipedia_url", label: 'Wikipedia URL' diff --git a/app/views/crops/_openfarm_data.html.haml b/app/views/crops/_openfarm_data.html.haml new file mode 100644 index 000000000..900a47c90 --- /dev/null +++ b/app/views/crops/_openfarm_data.html.haml @@ -0,0 +1,33 @@ +- if crop.row_spacing || crop.spread || crop.height || crop.sowing_method || crop.sun_requirements || crop.growing_degree_days + = cute_icon + .card + .card-body + %h4 OpenFarm Data + %ul.list-group.list-group-flush + - if crop.row_spacing + %li.list-group-item + %strong Row Spacing: + = crop.row_spacing + cm + - if crop.spread + %li.list-group-item + %strong Spread: + = crop.spread + cm + - if crop.height + %li.list-group-item + %strong Height: + = crop.height + cm + - if crop.sowing_method + %li.list-group-item + %strong Sowing Method: + = crop.sowing_method + - if crop.sun_requirements + %li.list-group-item + %strong Sun Requirements: + = crop.sun_requirements + - if crop.growing_degree_days + %li.list-group-item + %strong Growing Degree Days: + = crop.growing_degree_days diff --git a/app/views/crops/show.html.haml b/app/views/crops/show.html.haml index 8478b09d4..25edfc4c8 100644 --- a/app/views/crops/show.html.haml +++ b/app/views/crops/show.html.haml @@ -111,6 +111,8 @@ = render 'harvests', crop: @crop = render 'find_seeds', crop: @crop + = render 'openfarm_data', crop: @crop + = cute_icon .card .card-body diff --git a/app/views/gardens/_previously.haml b/app/views/gardens/_previously.haml index c0c8d9968..8da1fa25b 100644 --- a/app/views/gardens/_previously.haml +++ b/app/views/gardens/_previously.haml @@ -16,3 +16,8 @@ .col-md-12 %p Nothing has been planted here. +- if @finished_activities&.size&.positive? + %h2 Finished activities in garden + .index-cards + - @finished_activities.each do |activity| + = render "activities/card", activity: activity \ No newline at end of file diff --git a/app/views/gardens/index.html.haml b/app/views/gardens/index.html.haml index 277990856..3930cdf71 100644 --- a/app/views/gardens/index.html.haml +++ b/app/views/gardens/index.html.haml @@ -11,6 +11,9 @@ .row .col-md-2 + %small + %a{href: "#content"} + Skip to main content = render 'layouts/nav', model: Garden %label = link_to show_inactive_tickbox_path('gardens', owner: @owner, show_all: @show_all) do @@ -20,7 +23,7 @@ %hr/ = render @owner - .col-md-10 + .col-md-10#content - if @gardens.empty? %p There are no gardens to display. - if can?(:create, Garden) && @owner == current_member diff --git a/app/views/harvests/_planting.haml b/app/views/harvests/_planting.haml index cb11a9baa..ffbf710a3 100644 --- a/app/views/harvests/_planting.haml +++ b/app/views/harvests/_planting.haml @@ -9,4 +9,4 @@ - if @harvest.planting.present? && @harvest.planting.overall_rating.blank? .alert.alert-info{role: "alert"} This harvest is from a planting that hasn't been rated yet. - = link_to "Rate this planting", edit_planting_path(@harvest.planting), class: 'alert-link' + = link_to "Rate this planting", edit_planting_path(@harvest.planting, anchor: "planting_overall_rating"), class: 'alert-link' diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 6e8c60d9a..7b63ee8f3 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -11,11 +11,11 @@ %br %p - if current_member.plantings.active.any? - = link_to member_path(current_member, anchor: "#content"), class: 'btn btn-dark' do + = link_to member_path(current_member, anchor: "content"), class: 'btn btn-dark' do = planting_icon Track my plantings %p - = link_to member_gardens_path(current_member), class: 'btn btn-dark' do + = link_to member_gardens_path(current_member, anchor: "content"), class: 'btn btn-dark' do = garden_icon Show me my garden - else diff --git a/app/views/layouts/_menu.haml b/app/views/layouts/_menu.haml index 749db0261..17a70150d 100644 --- a/app/views/layouts/_menu.haml +++ b/app/views/layouts/_menu.haml @@ -2,10 +2,10 @@ %ul.navbar-nav.mr-auto.bg-dark - if signed_in? %li.nav-item - = link_to timeline_index_path, method: :get, class: 'nav-link text-white' do + = link_to timeline_index_path, method: :get, class: 'nav-link text-white', title: "Timeline" do = image_tag 'icons/notification.svg', class: 'img img-icon', alt: "Notifications" %li.nav-item - = link_to member_gardens_path(current_member), class: 'nav-link text-white', title: "My gardens" do + = link_to member_gardens_path(current_member, anchor: "content"), class: 'nav-link text-white', title: "My gardens" do = image_icon 'gardens' %li.nav-item.dropdown %a.nav-link.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-bs-toggle" => "dropdown", href: "#", role: "button"} diff --git a/app/views/members/show.html.haml b/app/views/members/show.html.haml index d4654b8f8..4779bc88e 100644 --- a/app/views/members/show.html.haml +++ b/app/views/members/show.html.haml @@ -83,14 +83,14 @@ .row %section.order-3.order-md-1.col-12= render "map", member: @member - if @harvesting.size.positive? - %section.harvests.order-2.order-md-1.col-12 + %section.harvests.order-2.order-md-1.col-12#harvests %h2 Ready to harvest .index-cards - @harvesting.each do |planting| = render 'plantings/thumbnail', planting: planting - if @others.size.positive? - %section.planting-progress.order-2.order-md-1.col-12 + %section.planting-progress.order-2.order-md-1.col-12#planting-progress %h2 Progress report %p Still growing and not ready for harvesting. .list-group @@ -99,7 +99,7 @@ %span= render 'plantings/tiny', planting: planting %span= render 'plantings/progress', planting: planting - if @late.size.positive? - %section.late.order-2.order-md-1.col-12 + %section.late.order-2.order-md-1.col-12#late %h2 Late %p These plantings are at the end of their lifecycle. @@ -109,7 +109,7 @@ - @late.each do |planting| = render 'plantings/thumbnail', planting: planting - if @super_late.any? - %section.superlate.order-2.order-md-1.col-12 + %section.superlate.order-2.order-md-1.col-12#superlate %h2 Super late %p We suspect the following plantings finished long ago and no longer need tracking. @@ -122,14 +122,14 @@ planted on #{planting.planted_at.to_date} - if @harvests.any? - %section.havests.order-2.order-md-1.col-12 + %section.havests.order-2.order-md-1.col-12#recent-harvests %h2 Recent Harvests .index-cards - @harvests.each do |harvest| = render 'harvests/thumbnail', harvest: harvest - if @activity.any? - %section.activity.order-2.order-md-1.col-12 + %section.activity.order-2.order-md-1.col-12#activity %h2 Activity .list-group - @activity.each do |event| diff --git a/app/views/plantings/_form.html.haml b/app/views/plantings/_form.html.haml index f0c40f4e1..c287c4f7c 100644 --- a/app/views/plantings/_form.html.haml +++ b/app/views/plantings/_form.html.haml @@ -27,7 +27,7 @@ .row .col-md-8 - = f.collection_radio_buttons(:garden_id, @planting.owner.gardens.active, + = f.collection_radio_buttons(:garden_id, @planting.owner.gardens.active.order_by_name, :id, :name, required: true, label: 'Where did you plant it?') = link_to "Add a garden.", new_garden_path diff --git a/app/views/plantings/_modal.html.haml b/app/views/plantings/_modal.html.haml index d00c6d3f4..928c480d2 100644 --- a/app/views/plantings/_modal.html.haml +++ b/app/views/plantings/_modal.html.haml @@ -9,7 +9,7 @@ %p Which garden is the planting in? %ul.list-group - - planting.owner.gardens.active.order(:name).each do |garden| + - planting.owner.gardens.active.order_by_name.each do |garden| %li.list-group-item = link_to plantings_path(planting: {crop_id: planting.crop_id, garden_id: garden.id}), method: :post do .md-v-line diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index 9841b8329..dc5a10f55 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -89,7 +89,11 @@ - else .col-md-12 %p Nothing is currently planned here. - + - if @finished_activities&.size&.positive? + %h2 Finished activities for planting + .index-cards + - @finished_activities.each do |activity| + = render "activities/card", activity: activity .col-md-4.col-xs-12 = render @planting.crop diff --git a/config/routes.rb b/config/routes.rb index 084cf98e0..700acc657 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -141,6 +141,7 @@ Rails.application.routes.draw do namespace :api do namespace :v1 do + jsonapi_resources :activities jsonapi_resources :crops jsonapi_resources :gardens jsonapi_resources :harvests diff --git a/db/migrate/20240101010101_add_fields_to_crops.rb b/db/migrate/20240101010101_add_fields_to_crops.rb new file mode 100644 index 000000000..59cbcda90 --- /dev/null +++ b/db/migrate/20240101010101_add_fields_to_crops.rb @@ -0,0 +1,10 @@ +class AddFieldsToCrops < ActiveRecord::Migration[5.2] + def change + add_column :crops, :row_spacing, :integer + add_column :crops, :spread, :integer + add_column :crops, :height, :integer + add_column :crops, :sowing_method, :string + add_column :crops, :sun_requirements, :string + add_column :crops, :growing_degree_days, :integer + end +end diff --git a/db/migrate/20240101010102_populate_crop_fields_from_openfarm_data.rb b/db/migrate/20240101010102_populate_crop_fields_from_openfarm_data.rb new file mode 100644 index 000000000..32c35aead --- /dev/null +++ b/db/migrate/20240101010102_populate_crop_fields_from_openfarm_data.rb @@ -0,0 +1,21 @@ +class PopulateCropFieldsFromOpenfarmData < ActiveRecord::Migration[5.2] + def up + Crop.find_each do |crop| + if crop.openfarm_data.present? + attributes = crop.openfarm_data.fetch('attributes', {}) + crop.update_columns( + row_spacing: attributes['row_spacing'], + spread: attributes['spread'], + height: attributes['height'], + sowing_method: attributes['sowing_method'], + sun_requirements: attributes['sun_requirements'], + growing_degree_days: attributes['growing_degree_days'] + ) + end + end + end + + def down + # This migration is not reversible. + end +end diff --git a/db/schema.rb b/db/schema.rb index b09d3956b..982f1e06a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -252,6 +252,12 @@ ActiveRecord::Schema[7.2].define(version: 2025_09_01_130830) do t.jsonb "openfarm_data" t.integer "harvests_count", default: 0 t.integer "photo_associations_count", default: 0 + t.integer "row_spacing" + t.integer "spread" + t.integer "height" + t.string "sowing_method" + t.string "sun_requirements" + t.integer "growing_degree_days" t.index ["creator_id"], name: "index_crops_on_creator_id" t.index ["name"], name: "index_crops_on_name" t.index ["parent_id"], name: "index_crops_on_parent_id" diff --git a/spec/controllers/crops_controller_spec.rb b/spec/controllers/crops_controller_spec.rb index 2dec49ae7..3b3b4ab10 100644 --- a/spec/controllers/crops_controller_spec.rb +++ b/spec/controllers/crops_controller_spec.rb @@ -100,6 +100,36 @@ describe CropsController do it { expect { subject }.to change(Crop, :count).by(1) } it { expect { subject }.to change(AlternateName, :count).by(2) } it { expect { subject }.to change(ScientificName, :count).by(1) } + + context 'with openfarm data' do + let(:crop_params) do + { + crop: { + name: 'aubergine', + en_wikipedia_url: "https://en.wikipedia.org/wiki/Eggplant", + row_spacing: 10, + spread: 20, + height: 30, + sowing_method: 'direct', + sun_requirements: 'full sun', + growing_degree_days: 100 + }, + alt_name: { '1': "egg plant", '2': "purple apple" }, + sci_name: { '1': "fancy sci name", '2': "" } + } + end + + it 'saves openfarm data' do + subject + crop = Crop.last + expect(crop.row_spacing).to eq(10) + expect(crop.spread).to eq(20) + expect(crop.height).to eq(30) + expect(crop.sowing_method).to eq('direct') + expect(crop.sun_requirements).to eq('full sun') + expect(crop.growing_degree_days).to eq(100) + end + end end end diff --git a/spec/features/crops/creating_a_crop_spec.rb b/spec/features/crops/creating_a_crop_spec.rb index afaaad7e2..f87a44fbb 100644 --- a/spec/features/crops/creating_a_crop_spec.rb +++ b/spec/features/crops/creating_a_crop_spec.rb @@ -19,6 +19,14 @@ describe "Crop", :js do click_button class: "add-altname-row" fill_in "alt_name[3]", with: "Jazmin" fill_in "alt_name[4]", with: "Matsurika" + + fill_in "crop_row_spacing", with: "12" + fill_in "crop_spread", with: "30" + fill_in "crop_height", with: "10" + fill_in "crop_sowing_method", with: "directly into final position" + + fill_in "crop_sun_requirements", with: "full sun" + fill_in "crop_growing_degree_days", with: 100 end end end diff --git a/spec/requests/api/v1/activities_request_spec.rb b/spec/requests/api/v1/activities_request_spec.rb new file mode 100644 index 000000000..553c39856 --- /dev/null +++ b/spec/requests/api/v1/activities_request_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Activities', type: :request do + subject { JSON.parse response.body } + + let(:headers) { { 'Accept' => 'application/vnd.api+json' } } + let!(:activity) { FactoryBot.create(:activity, garden: create(:garden), planting: create(:planting)) } + let!(:activity2) { FactoryBot.create(:activity) } + + it '#index' do + get('/api/v1/activities', params: {}, headers:) + expect(subject['data'].size).to eq(2) + end + + it '#show' do + get("/api/v1/activities/#{activity.id}", params: {}, headers:) + expect(subject['data']['id']).to eq(activity.id.to_s) + end + + context 'filtering' do + it 'filters by owner' do + get("/api/v1/activities?filter[owner-id]=#{activity.owner.id}", params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(activity.id.to_s) + end + + it 'filters by garden' do + get("/api/v1/activities?filter[garden-id]=#{activity.garden.id}", params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(activity.id.to_s) + end + + it 'filters by planting' do + get("/api/v1/activities?filter[planting-id]=#{activity.planting.id}", params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(activity.id.to_s) + end + + it 'filters by category' do + get("/api/v1/activities?filter[category]=#{activity.category}", params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(2) + expect(subject['data'][0]['id']).to eq(activity.id.to_s) + expect(subject['data'][1]['id']).to eq(activity2.id.to_s) + end + end +end diff --git a/spec/requests/api/v1/gardens_request_spec.rb b/spec/requests/api/v1/gardens_request_spec.rb index 601d835d9..344ea74d1 100644 --- a/spec/requests/api/v1/gardens_request_spec.rb +++ b/spec/requests/api/v1/gardens_request_spec.rb @@ -50,6 +50,33 @@ RSpec.describe 'Gardens', type: :request do expect(subject['data']).to include(garden_encoded_as_json_api) end + context 'filtering' do + let!(:garden2) { FactoryBot.create(:garden, active: false, garden_type: FactoryBot.create(:garden_type)) } + pending 'filters by active' do + get('/api/v1/gardens?filter[active]=true', params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(garden.id.to_s) + end + + it 'filters by garden_type' do + get("/api/v1/gardens?filter[garden_type]=#{garden2.garden_type.id}", params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(garden2.id.to_s) + end + + it 'filters by owner' do + get("/api/v1/gardens?filter[owner_id]=#{garden2.owner.id}", params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(2) + expect(subject['data'][1]['id']).to eq(garden2.id.to_s) + end + end + it '#create' do expect do post '/api/v1/gardens', params: { 'garden' => { 'name' => 'can i make this' } }, headers: diff --git a/spec/requests/api/v1/harvests_request_spec.rb b/spec/requests/api/v1/harvests_request_spec.rb index 7f0290544..f49b7b88f 100644 --- a/spec/requests/api/v1/harvests_request_spec.rb +++ b/spec/requests/api/v1/harvests_request_spec.rb @@ -76,6 +76,39 @@ RSpec.describe 'Harvests', type: :request do it { expect(subject['data']).to eq(harvest_encoded_as_json_api) } end + context 'filtering' do + let!(:harvest2) { FactoryBot.create(:harvest, planting: create(:planting)) } + it 'filters by crop' do + get("/api/v1/harvests?filter[crop_id]=#{harvest2.crop.id}", params: {}, headers:) + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(harvest2.id.to_s) + end + + it 'filters by planting' do + get("/api/v1/harvests?filter[planting_id]=#{harvest2.planting.id}", params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(harvest2.id.to_s) + end + + it 'filters by plant_part' do + get("/api/v1/harvests?filter[plant_part]=#{harvest2.plant_part.id}", params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(harvest2.id.to_s) + end + + it 'filters by owner' do + get("/api/v1/harvests?filter[owner_id]=#{harvest2.owner.id}", params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(harvest2.id.to_s) + end + end + it '#create' do expect do put '/api/v1/harvests', headers:, params: { diff --git a/spec/requests/api/v1/plantings_request_spec.rb b/spec/requests/api/v1/plantings_request_spec.rb index b8b7bec12..5a406afe9 100644 --- a/spec/requests/api/v1/plantings_request_spec.rb +++ b/spec/requests/api/v1/plantings_request_spec.rb @@ -140,4 +140,36 @@ RSpec.describe 'Plantings', type: :request do end end end + + context 'filtering' do + let!(:planting2) { FactoryBot.create(:planting, failed: true, sunniness: 'shade') } + let!(:perennial_planting) { FactoryBot.create(:planting, crop: FactoryBot.create(:crop, perennial: true)) } + it 'filters by failed' do + get('/api/v1/plantings?filter[failed]=true', params: {}, headers:) + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(planting2.id.to_s) + end + + it 'filters by sunniness' do + get('/api/v1/plantings?filter[sunniness]=shade', params: {}, headers:) + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(planting2.id.to_s) + end + + it 'filters by perennial' do + get('/api/v1/plantings?filter[perennial]=true', params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(perennial_planting.id.to_s) + end + + it 'filters by active' do + get('/api/v1/plantings?filter[active]=true', params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(2) + expect(subject['data'][0]['id']).to eq(planting.id.to_s) + end + end end diff --git a/spec/requests/api/v1/seeds_request_spec.rb b/spec/requests/api/v1/seeds_request_spec.rb index d24b5bee1..00ee04684 100644 --- a/spec/requests/api/v1/seeds_request_spec.rb +++ b/spec/requests/api/v1/seeds_request_spec.rb @@ -78,4 +78,56 @@ RSpec.describe 'Seeds', type: :request do delete "/api/v1/seeds/#{seed.id}", params: {}, headers: end.to raise_error ActionController::RoutingError end + + context 'filtering' do + let!(:seed2) { FactoryBot.create(:seed, tradable_to: 'nationally', organic: 'certified organic', gmo: 'certified GMO-free', heirloom: 'heirloom') } + it 'filters by crop' do + get("/api/v1/seeds?filter[crop]=#{seed2.crop.id}", params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(seed2.id.to_s) + end + + + it 'filters by tradable_to' do + get('/api/v1/seeds?filter[tradable_to]=nationally', params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(seed2.id.to_s) + end + + it 'filters by organic' do + get('/api/v1/seeds?filter[organic]=certified organic', params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(seed2.id.to_s) + end + + it 'filters by gmo' do + get('/api/v1/seeds?filter[gmo]=certified GMO-free', params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(seed2.id.to_s) + end + + it 'filters by heirloom' do + get('/api/v1/seeds?filter[heirloom]=heirloom', params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(seed2.id.to_s) + end + + it 'filters by owner' do + get("/api/v1/seeds?filter[owner_id]=#{seed2.owner.id}", params: {}, headers:) + + expect(response.status).to eq 200 + expect(subject['data'].size).to eq(1) + expect(subject['data'][0]['id']).to eq(seed2.id.to_s) + end + end end