Merge pull request #5010 from Growstuff/feat/timeline-jobs

add finished jobs to the timeline
This commit is contained in:
Brenda Wallace authored and GitHub committed 2026-10-23 13:41:12 +13:00
commit d7103eb4af
6 files changed
+55 -9

No files matched your search

+9 -4
View File
@@ -28,7 +28,12 @@ module NeighboursHelper
day == today - 1 ? t('neighbours.day.yesterday', date:) : date
end
GROUP_ORDER = %w(planting seed seed_trade harvest transplant planted_out planting_finished seed_finished job garden_layout).freeze
# Planned and finished jobs, which have a name of their own and no page.
JOB_TYPES = %w(job job_finished).freeze
NAMED_TYPES = (JOB_TYPES + %w(garden_layout)).freeze
GROUP_ORDER = %w(planting seed seed_trade harvest transplant planted_out planting_finished seed_finished
job_finished job garden_layout).freeze
# The icon that goes with each kind of thing someone did.
def group_icon(kind)
@@ -37,7 +42,7 @@ module NeighboursHelper
when 'seed', 'seed_trade' then seed_icon
when 'harvest' then harvest_icon
when 'transplant', 'planted_out' then growing_icon
when 'planting_finished', 'seed_finished' then finished_icon
when 'planting_finished', 'seed_finished', 'job_finished' then finished_icon
when 'job' then icon('fas', 'calendar-days')
when 'garden_layout' then garden_icon
when 'photo' then photo_icon
@@ -156,7 +161,7 @@ module NeighboursHelper
# Jobs have no page of their own, so they link to their owner's list.
def thing_path(item)
item.type == 'job' ? member_jobs_path(item.record.owner) : polymorphic_path(thing_record(item))
JOB_TYPES.include?(item.type) ? member_jobs_path(item.record.owner) : polymorphic_path(thing_record(item))
end
def thing_record(item)
@@ -165,7 +170,7 @@ module NeighboursHelper
def thing_name(item)
record = thing_record(item)
%w(job garden_layout).include?(item.type) ? record.name : record.crop.name
NAMED_TYPES.include?(item.type) ? record.name : record.crop.name
end
def thing_details(item)
+2 -2
View File
@@ -17,12 +17,12 @@ class Neighbours
NEARBY_LIMIT = 100
SUGGESTIONS_SHOWN = 4
POSTS_SHOWN = 5
FEED_KINDS = %i(planting harvest seed photo garden_layout transplant planned_job planting_finished seed_finished).freeze
FEED_KINDS = %i(planting harvest seed photo garden_layout transplant planned_job job_finished planting_finished seed_finished).freeze
FEED_EVENTS = 15
MAX_OFFSET = 500
PHOTO_SUBJECTS = %w(Planting Harvest Seed Garden).freeze
EVENT_MODELS = { 'planting' => Planting, 'harvest' => Harvest, 'seed' => Seed, 'photo' => Photo, 'garden_layout' => Garden,
'transplant' => PlantingHistory, 'job' => Job, 'planting_finished' => Planting,
'transplant' => PlantingHistory, 'job' => Job, 'job_finished' => Job, 'planting_finished' => Planting,
'seed_finished' => Seed }.freeze
FEED_CACHE_FOR = 5.minutes
+10 -3
View File
@@ -18,8 +18,9 @@ class TimelineService
comment: :comments_query,
photo: :photos_query,
seed: :seeds_query,
job: :jobs_query,
planned_job: :planned_jobs_query,
job: :jobs_query,
planned_job: :planned_jobs_query,
job_finished: :jobs_finished_query,
like: :likes_query,
garden_layout: :garden_layouts_query,
transplant: :transplants_query,
@@ -30,7 +31,7 @@ class TimelineService
# Events of the given kinds, newest first. Garden layouts are only here when
# asked for, since the timeline page has nothing to say about them; the same goes
# for transplants, planned jobs and finished plantings and seeds.
# for transplants, planned and finished jobs, and finished plantings and seeds.
def self.query(kinds = DEFAULT_EVENTS)
kinds.map { |kind| public_send(EVENT_QUERIES.fetch(kind)) }
.reduce { |all, one| all.union_all(one) }
@@ -68,6 +69,12 @@ class TimelineService
"null as crop_id")
end
# Jobs as the day they were crossed off.
def self.jobs_finished_query
Job.where(finished: true).where.not(finished_at: nil)
.select(:id, "'job_finished' as event_type", 'jobs.finished_at as event_at', :owner_id, 'null as crop_id')
end
# Jobs as the day they were planned rather than the day they are due,
# for a feed of what people have done.
def self.planned_jobs_query
+3
View File
@@ -445,6 +445,9 @@ en:
empty: Nothing to show yet.
title: Latest from your friends and neighbours
group:
job_finished_html:
one: Finished a job, %{things}
other: Finished %{count} jobs, %{things}
job_html:
one: Planned a job, %{things}
other: Planned %{count} jobs, %{things}
+9
View File
@@ -123,6 +123,15 @@ describe 'My friends and neighbours' do
expect(page).to have_css('article', text: /friendly.*Planned a job, Weed the beans \(for #{due}\)/m)
end
it 'shows a friend finishing a job' do
friend = create(:member, login_name: 'friendly')
member.follows.create!(followed: friend)
create(:job, owner: friend, name: 'Net the berries', finished: true)
visit neighbours_path
expect(page).to have_css('article', text: /friendly.*Finished a job, Net the berries/m)
end
it 'shows only their own job under Me' do
friend = create(:member, login_name: 'friendly')
member.follows.create!(followed: friend)
+22
View File
@@ -229,6 +229,28 @@ describe Neighbours do
expect(items.map { |item| [item.type, item.record] }).to include(['job', job])
end
it 'has jobs people finished' do
job = create(:job, owner: friend, finished: true)
items = neighbours.feed.flat_map(&:updates).flat_map(&:items)
expect(items.map { |item| [item.type, item.record] }).to include(['job_finished', job])
end
it 'leaves out a job that is not finished as a finished job' do
create(:job, owner: friend, finished: false)
items = neighbours.feed.flat_map(&:updates).flat_map(&:items)
expect(items.map(&:type)).not_to include('job_finished')
end
it 'dates a finished job by when it was crossed off, not when it was due' do
job = create(:job, owner: friend, due_date: Time.zone.today - 1.month, finished: true)
job.update_column(:finished_at, Time.zone.now) # rubocop:disable Rails/SkipsModelValidations
days = neighbours.feed.select { |day| day.updates.flat_map(&:items).any? { |item| item.type == 'job_finished' } }
expect(days.first.day).to eq Time.zone.today
end
it 'dates a job by when it was planned, not when it is due' do
create(:job, owner: friend, due_date: Time.zone.today + 1.month)