Compare commits

..

19 Commits

Author SHA1 Message Date
Daniel O'Connor
9b1699b061 Merge pull request #4215 from Growstuff/activities-detail
Surface more Activities detail
2025-09-07 14:39:13 +09:30
Daniel O'Connor
06c907742d Merge pull request #4216 from Growstuff/CloCkWeRX-patch-2
Update ci-features-posts.yml
2025-09-07 14:38:54 +09:30
Daniel O'Connor
d8b84e611b Update ci-features-posts.yml 2025-09-07 14:37:18 +09:30
Daniel O'Connor
c0ab0b085e Merge pull request #4214 from Growstuff/fix-cards
Fix styling of cards to space evenly
2025-09-07 14:34:14 +09:30
Daniel O'Connor
99d50a7d4b Merge pull request #4212 from Growstuff/fix-ordering
Change garden sort order to be by name and planting by most to least recent when creating an activity
2025-09-07 14:33:46 +09:30
Daniel O'Connor
84da4c0f4f Fix styling of cards to space evenly 2025-09-07 04:24:22 +00:00
Daniel O'Connor
f650d1b8fa Change garden sort order to be by name and planting by most to lease recent when creating an activity 2025-09-07 03:54:20 +00:00
dependabot[bot]
91ef85da0b Merge pull request #4203 from Growstuff/dependabot/bundler/rubocop-1.80.2 2025-09-05 19:09:32 +00:00
dependabot[bot]
30f799c4b9 Bump rubocop from 1.80.1 to 1.80.2
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.80.1 to 1.80.2.
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.80.1...v1.80.2)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.80.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-05 08:10:40 +00:00
dependabot[bot]
9283e64366 Merge pull request #4204 from Growstuff/dependabot/bundler/puma-7.0.0 2025-09-05 08:09:30 +00:00
dependabot[bot]
b0b759ef60 Bump puma from 6.6.1 to 7.0.0
Bumps [puma](https://github.com/puma/puma) from 6.6.1 to 7.0.0.
- [Release notes](https://github.com/puma/puma/releases)
- [Changelog](https://github.com/puma/puma/blob/master/History.md)
- [Commits](https://github.com/puma/puma/compare/v6.6.1...v7.0.0)

---
updated-dependencies:
- dependency-name: puma
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-05 07:59:48 +00:00
Daniel O'Connor
83cf4117fb Merge pull request #4201 from Growstuff/feature/preserve-screenshots-as-artifacts
CI: Preserve screenshots as build artifacts in all feature workflows
2025-09-05 17:29:15 +09:30
Daniel O'Connor
521e649cac Merge pull request #4202 from Growstuff/dependabot/bundler/pg-1.6.2
Bump pg from 1.6.1 to 1.6.2
2025-09-04 20:46:29 +09:30
dependabot[bot]
1dc587d4b5 Bump pg from 1.6.1 to 1.6.2
Bumps [pg](https://github.com/ged/ruby-pg) from 1.6.1 to 1.6.2.
- [Changelog](https://github.com/ged/ruby-pg/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ged/ruby-pg/compare/v1.6.1...v1.6.2)

---
updated-dependencies:
- dependency-name: pg
  dependency-version: 1.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-04 00:26:26 +00:00
Daniel O'Connor
77a6e96ece Merge branch 'dev' into feature/preserve-screenshots-as-artifacts 2025-09-04 08:43:44 +09:30
dependabot[bot]
2e89dc0c4f Merge pull request #4200 from Growstuff/dependabot/bundler/rubocop-rspec-3.7.0 2025-09-03 09:49:39 +00:00
dependabot[bot]
dc1b46c7be Bump rubocop-rspec from 3.6.0 to 3.7.0
Bumps [rubocop-rspec](https://github.com/rubocop/rubocop-rspec) from 3.6.0 to 3.7.0.
- [Release notes](https://github.com/rubocop/rubocop-rspec/releases)
- [Changelog](https://github.com/rubocop/rubocop-rspec/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop-rspec/compare/v3.6.0...v3.7.0)

---
updated-dependencies:
- dependency-name: rubocop-rspec
  dependency-version: 3.7.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-03 05:55:09 +00:00
google-labs-jules[bot]
d61227bad0 CI: Preserve screenshots as build artifacts
Adjust the behaviour of capybara-screenshot / GitHub CI to preserve the screenshots as a build artifact.

This change adds a step to all `ci-features-*.yml` workflows to upload the
`tmp/screenshots` directory as a build artifact on failure. This will help with
debugging failing feature tests.
2025-09-02 23:30:25 +00:00
google-labs-jules[bot]
765fab1104 CI: Preserve screenshots as build artifacts
Adjust the behaviour of capybara-screenshot / GitHub CI to preserve the screenshots as a build artifact.

This change adds a step to the `ci-features.yml` workflow to upload the
`tmp/screenshots` directory as a build artifact on failure. This will help with
debugging failing feature tests.
2025-09-02 23:30:02 +00:00
25 changed files with 229 additions and 116 deletions

View File

@@ -100,3 +100,10 @@ jobs:
- name: Run rspec (admin/)
run: bundle exec rspec spec/features/admin/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tmp/screenshots

View File

@@ -100,3 +100,10 @@ jobs:
- name: Run rspec (comments/)
run: bundle exec rspec spec/features/comments/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tmp/screenshots

View File

@@ -101,3 +101,9 @@ jobs:
- name: Run rspec (conversations/)
run: bundle exec rspec spec/features/conversations/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tmp/screenshots

View File

@@ -100,3 +100,10 @@ jobs:
- name: Run rspec (crops/)
run: bundle exec rspec spec/features/crops/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tmp/screenshots

View File

@@ -100,3 +100,10 @@ jobs:
- name: Run rspec (gardens/)
run: bundle exec rspec spec/features/gardens/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tmp/screenshots

View File

@@ -99,4 +99,11 @@ jobs:
run: bundle exec rails search:reindex
- name: Run rspec (harvests/)
run: bundle exec rspec spec/features/harvests/ -fd -t ~@flaky
run: bundle exec rspec spec/features/harvests/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tmp/screenshots

View File

@@ -99,4 +99,11 @@ jobs:
run: bundle exec rails search:reindex
- name: Run rspec (home/)
run: bundle exec rspec spec/features/home/ -fd -t ~@flaky
run: bundle exec rspec spec/features/home/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tmp/screenshots

View File

@@ -99,4 +99,11 @@ jobs:
run: bundle exec rails search:reindex
- name: Run rspec (members/)
run: bundle exec rspec spec/features/members/ -fd -t ~@flaky
run: bundle exec rspec spec/features/members/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tmp/screenshots

View File

@@ -100,3 +100,10 @@ jobs:
- name: Run rspec (places/)
run: bundle exec rspec spec/features/places/ -fd
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tmp/screenshots

View File

@@ -100,3 +100,10 @@ jobs:
- name: Run rspec (plantings/)
run: bundle exec rspec spec/features/plantings/ -fd
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tmp/screenshots

View File

@@ -1,4 +1,4 @@
name: CI Features - Admin
name: CI Features - Posts
on: [pull_request]
@@ -100,3 +100,10 @@ jobs:
- name: Run rspec (posts/)
run: bundle exec rspec spec/features/posts/ -fd
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tmp/screenshots

View File

@@ -100,3 +100,10 @@ jobs:
- name: Run rspec (seeds/)
run: bundle exec rspec spec/features/seeds/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tmp/screenshots

View File

@@ -99,4 +99,11 @@ jobs:
run: bundle exec rails search:reindex
- name: Run rspec (timeline/)
run: bundle exec rspec spec/features/timeline/ -fd -t ~@flaky
run: bundle exec rspec spec/features/timeline/ -fd -t ~@flaky
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tmp/screenshots

View File

@@ -108,4 +108,11 @@ jobs:
run: bundle exec rspec spec/features/photos/ -fd
- name: Run rspec (rss/)
run: bundle exec rspec spec/features/rss/ -fd
run: bundle exec rspec spec/features/rss/ -fd
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tmp/screenshots

View File

@@ -457,8 +457,8 @@ GEM
racc
percy-capybara (5.0.0)
capybara (>= 3)
pg (1.6.1)
pg (1.6.1-x86_64-linux)
pg (1.6.2)
pg (1.6.2-x86_64-linux)
platform-api (3.8.0)
heroics (~> 0.1.1)
moneta (~> 1.0.0)
@@ -475,7 +475,7 @@ GEM
date
stringio
public_suffix (6.0.1)
puma (6.6.1)
puma (7.0.0)
nio4r (~> 2.0)
query_diet (0.7.2)
racc (1.8.1)
@@ -601,7 +601,7 @@ GEM
rswag-ui (2.16.0)
actionpack (>= 5.2, < 8.1)
railties (>= 5.2, < 8.1)
rubocop (1.80.1)
rubocop (1.80.2)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@@ -630,7 +630,7 @@ GEM
rubocop-rake (0.7.1)
lint_roller (~> 1.1)
rubocop (>= 1.72.1)
rubocop-rspec (3.6.0)
rubocop-rspec (3.7.0)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rspec_rails (2.31.0)

View File

@@ -132,6 +132,8 @@ section {
border-radius: 5%;
margin: 0.5em 0.5em 0.5em 0;
width: 200px;
align-items: stretch;
justify-content: space-between;
.img-card {
border-top-left-radius: 5%;

View File

@@ -21,6 +21,28 @@ module ApplicationHelper
classes
end
# Similar to Rails' time_ago_in_words, but gives a more standard
# output like "in 3 days" or "5 months ago".
# Also handles the case where from_time is a Date and to_time is a Date
# (in which case it just says "today" if they're the same date).
#
# NOTE: This is similar to distance_of_time_in_words but different enough
# that I think it's worth having a separate helper for it.
#
# from_time - the starting time (Time or Date)
# to_time - the ending time (Time or Date). Default: now (Time.zone.now)
# include_seconds - whether to include seconds in the calculation
#
# Returns a string like "in 3 days" or "5 months ago"
def standard_time_distance(from_time, to_time = 0, include_seconds = false)
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
'in ' + distance_of_time_in_words(from_time, to_time, include_seconds:)
end
def count_github_contibutors
File.open(Rails.root.join('CONTRIBUTORS.md')).readlines.grep(/^-/).size
end

View File

@@ -9,7 +9,9 @@ module SearchActivities
mappings: {
properties: {
active: { type: :boolean },
created_at: { type: :integer }
created_at: { type: :integer },
updated_at: { type: :integer },
due_date: { type: :date }
}
}
@@ -23,8 +25,10 @@ module SearchActivities
category:,
garden_id:,
garden_name: garden&.name,
garden_slug: garden&.garden_slug,
planting_id:,
planting_name: planting&.crop&.name,
planting_slug: planting&.slug,
description:,
# owner

View File

@@ -53,6 +53,7 @@ class Harvest < ApplicationRecord
delegate :name, :slug, to: :crop, prefix: true
delegate :login_name, :slug, to: :owner, prefix: true
delegate :name, to: :plant_part, prefix: true
##
## Validations
@@ -108,7 +109,7 @@ class Harvest < ApplicationRecord
def to_s
# 50 individual apples, weighing 3lb
# 2 buckets of apricots, weighing 10kg
"#{quantity_to_human} #{unit_to_human} #{plant_part_name_to_human} of #{crop_name} #{weight_to_human}".strip
"#{quantity_to_human} #{unit_to_human} #{crop_name_to_human} #{weight_to_human}".strip
end
def quantity_to_human
@@ -131,13 +132,13 @@ class Harvest < ApplicationRecord
"weighing #{number_to_human(weight_quantity, strip_insignificant_zeros: true)} #{weight_unit}"
end
def plant_part_name_to_human
def crop_name_to_human
if unit != 'individual' # buckets of apricot*s*
plant_part.name.pluralize
crop.name.pluralize
elsif quantity == 1
plant_part.name
crop.name
else
plant_part.name.pluralize
crop.name.pluralize
end.to_s
end

View File

@@ -11,10 +11,6 @@ class PlantPart < ApplicationRecord
scope :joins_members, -> { joins("INNER JOIN members ON members.id = harvests.owner_id") }
def whole_plant?
name == 'whole plant'
end
def to_s
name
end

View File

@@ -20,16 +20,23 @@
- if can? :destroy, activity
.dropdown-divider
= delete_button(activity, classes: 'dropdown-item text-danger')
= link_to activity_path(slug: activity.slug) do
.card-body.text-center
.card-body
= link_to activity_path(slug: activity.slug) do
%h4= activity.name
.text-center= activity.description
- if activity.garden
.text-center= activity.garden
- if activity.planting
.text-center= activity.planting
%small.due-date{title: activity.due_date}
= standard_time_distance(activity.due_date.to_date, Time.zone.now.to_date)
%div
%small.text-justify{title: activity.description}= activity.description.truncate(150)
%p
%ul.list-unstyled
- if activity.garden_name && activity.garden_slug
%li
%small= link_to activity.garden_name, garden_path(slug: activity.garden_slug)
- if activity.planting_name && activity.planting_slug
%li
%small= link_to activity.planting_name, planting_path(slug: activity.planting_slug)
.card-footer
.float-right
%span.chip.member-chip
= link_to member_path(slug: activity.owner_slug) do
= activity.owner_login_name
%small.chip.member-chip
= link_to member_path(slug: activity.owner_slug) do
= activity.owner_login_name

View File

@@ -27,13 +27,13 @@
.row
.col-md-4
= f.collection_radio_buttons(:garden_id, @activity.owner.gardens.active,
= f.collection_radio_buttons(:garden_id, @activity.owner.gardens.active.order_by_name,
:id, :name,
label: 'Is this for a specific garden?')
= link_to "Add a garden.", new_garden_path
.col-md-4
= f.collection_radio_buttons(:planting_id, @activity.owner.plantings.active,
= f.collection_radio_buttons(:planting_id, @activity.owner.plantings.active.recent,
:id, :crop_name,
label: 'Is this for a specific planting?')
= link_to "Add a planting.", new_planting_path

View File

@@ -12,7 +12,7 @@
#{harvest.owner} harvested #{display_quantity(harvest)}.
.float-right= render 'members/location', member: harvest.owner
.harvest-timeago
%small #{distance_of_time_in_words(harvest.harvested_at, Time.zone.now)} ago.
%small #{standard_time_distance(harvest.harvested_at, Time.zone.now.to_date)}
%li.list-group-item= link_to "View all #{crop.name} harvests", crop_harvests_path(crop), class: 'card-link'
- if crop.approved?
- if current_member

View File

@@ -46,7 +46,7 @@
%h3
Harvested
= editable :date, @harvest, :harvested_at, display_field: '.harvested_at'
%strong.harvested_at #{distance_of_time_in_words @harvest.harvested_at, Time.zone.now.to_date} ago
%strong.harvested_at #{standard_time_distance @harvest.harvested_at, Time.zone.now.to_date}
%span.harvested_at= I18n.l @harvest.harvested_at
.card{class: @harvest.quantity.present? ? '' : 'text-muted'}

View File

@@ -149,94 +149,78 @@ describe Harvest do
end
context "stringification" do
let(:whole_plant) { FactoryBot.create(:plant_part, name: "whole plant") }
let(:leaf) { FactoryBot.create(:plant_part, name: "leaf") }
let(:fruit) { FactoryBot.create(:plant_part, name: "fruit") }
let(:other) { FactoryBot.create(:plant_part, name: "other") }
let(:crop) { FactoryBot.create(:crop, name: "apricot") }
let(:apricot) { FactoryBot.create(:crop, name: "apricot") }
let(:lettuce) { FactoryBot.create(:crop, name: "lettuce") }
it "apricots" do
@h = FactoryBot.create(:harvest, crop:,
quantity: nil,
unit: nil,
weight_quantity: nil,
weight_unit: nil)
expect(@h.to_s).to eq "apricots"
end
context "apricots" do
it "apricots" do
@h = FactoryBot.create(:harvest, crop: apricot,
plant_part: fruit,
quantity: nil,
unit: nil,
weight_quantity: nil,
weight_unit: nil)
expect(@h.to_s).to eq "fruits of apricot"
end
it "1 individual apricot" do
@h = FactoryBot.create(:harvest, crop:,
quantity: 1,
unit: 'individual',
weight_quantity: nil,
weight_unit: nil)
expect(@h.to_s).to eq "1 individual apricot"
end
it "1 individual apricot" do
@h = FactoryBot.create(:harvest, crop: apricot,
plant_part: fruit,
quantity: 1,
unit: 'individual',
weight_quantity: nil,
weight_unit: nil)
expect(@h.to_s).to eq "1 individual fruit of apricot"
end
it "10 individual apricots" do
@h = FactoryBot.create(:harvest, crop:,
quantity: 10,
unit: 'individual',
weight_quantity: nil,
weight_unit: nil)
expect(@h.to_s).to eq "10 individual apricots"
end
it "10 individual apricots" do
@h = FactoryBot.create(:harvest, crop: apricot,
plant_part: fruit,
quantity: 10,
unit: 'individual',
weight_quantity: nil,
weight_unit: nil)
expect(@h.to_s).to eq "10 individual fruits of apricot"
end
it "1 bushel of apricots" do
@h = FactoryBot.create(:harvest, crop:,
quantity: 1,
unit: 'bushel',
weight_quantity: nil,
weight_unit: nil)
expect(@h.to_s).to eq "1 bushel of apricots"
end
it "1 bushel of apricots" do
@h = FactoryBot.create(:harvest, crop: apricot,
plant_part: fruit,
quantity: 1,
unit: 'bushel',
weight_quantity: nil,
weight_unit: nil)
expect(@h.to_s).to eq "1 bushel of fruits of apricot"
end
it "1.5 bushels of apricots" do
@h = FactoryBot.create(:harvest, crop:,
quantity: 1.5,
unit: 'bushel',
weight_quantity: nil,
weight_unit: nil)
expect(@h.to_s).to eq "1.5 bushels of apricots"
end
it "1.5 bushels of apricots" do
@h = FactoryBot.create(:harvest, crop: apricot,
plant_part: fruit,
quantity: 1.5,
unit: 'bushel',
weight_quantity: nil,
weight_unit: nil)
expect(@h.to_s).to eq "1.5 bushels of fruits of apricot"
end
it "10 bushels of apricots" do
@h = FactoryBot.create(:harvest, crop:,
quantity: 10,
unit: 'bushel',
weight_quantity: nil,
weight_unit: nil)
expect(@h.to_s).to eq "10 bushels of apricots"
end
it "10 bushels of apricots" do
@h = FactoryBot.create(:harvest, crop: apricot,
plant_part: fruit,
quantity: 10,
unit: 'bushel',
weight_quantity: nil,
weight_unit: nil)
expect(@h.to_s).to eq "10 bushels of fruits of apricot"
end
it "apricots weighing 1.2 kg" do
@h = FactoryBot.create(:harvest, crop:,
quantity: nil,
unit: nil,
weight_quantity: 1.2,
weight_unit: 'kg')
expect(@h.to_s).to eq "apricots weighing 1.2 kg"
end
it "apricots weighing 1.2 kg" do
@h = FactoryBot.create(:harvest, crop: apricot,
plant_part: fruit,
quantity: nil,
unit: nil,
weight_quantity: 1.2,
weight_unit: 'kg')
expect(@h.to_s).to eq "fruits of apricot weighing 1.2 kg"
end
it "10 bushels of apricots weighing 100 kg" do
@h = FactoryBot.create(:harvest, crop: apricot,
plant_part: fruit,
quantity: 10,
unit: 'bushel',
weight_quantity: 100,
weight_unit: 'kg')
expect(@h.to_s).to eq "10 bushels of fruits of apricot weighing 100 kg"
end
it "10 bushels of apricots weighing 100 kg" do
@h = FactoryBot.create(:harvest, crop:,
quantity: 10,
unit: 'bushel',
weight_quantity: 100,
weight_unit: 'kg')
expect(@h.to_s).to eq "10 bushels of apricots weighing 100 kg"
end
end