Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use event data #4000

Merged
merged 29 commits into from
Feb 12, 2024
Merged

Use event data #4000

merged 29 commits into from
Feb 12, 2024

Conversation

dorner
Copy link
Collaborator

@dorner dorner commented Dec 27, 2023

Note: This PR is rebased off of #3994 which should be merged first.

This PR contains all the remaining code needed for us to run our app fully in event-sourced mode.

With this merged, the system will be in the following state:

  • All inventory writes are double writes (to both events and inventory items)
  • Reads are put behind a feature flag. By default it still reads from inventory items. With the flag on, it instead reads from events.
  • On write, with the flag on, it uses the event sourcing validations to determine if there are any invalid events (i.e. trying to remove more inventory than a location has). With the flag off, it uses the current approach of inspecting inventory items.

Broadly, the PR contains the following changes:

  • Adds the event-read feature flag, which can be set on an organization level to turn event-source reading on.
  • Adds a new class, View::Inventory, which acts as a repository for event-calculated inventory. Since we aren't inspecting database rows, but getting the full state of the inventory as calculated from events, we want to ensure we aren't recalculating this more than once per request.
  • In order to facilitate this, code has changed to calculate the inventory once (generally in a controller action) and pass it through the view and any other methods necessary.
  • This includes making changes to the Exportable CSV-related methods to take an inventory object - the relevant classes simply overwrite the Exportable methods to enable this.
  • In general, the feature flag is checked at the top level (e.g in controller) and an inventory is generated if necessary. Then lower-down code will check for the existence of the inventory object to determine whether to use it or the current methods of inspecting inventory items.

One subtle change is that there are several places in the current system that differentiate between an inventory item not existing at all, or existing with a quantity of zero. The new event-driven method doesn't differentiate between these (since semantically they are the same case).

The PR has been split up into multiple commits, where each commit accomplishes one set of changes. Hopefully this will make the review more palatable.

For specs, I've introduced a new create_inventory method that ensures that all relevant events and inventory items are double-written whenever inventory needs to be created. For specs that currently look at the state of inventory items, I've added additional checks to validate the event-sourced inventory.

Ultimately, once this has been merged, tested and fully rolled out, we will remove all reference to inventory items from the app.

@dorner dorner force-pushed the use-event-data branch 2 times, most recently from 3b61a0b to ec47e19 Compare December 31, 2023 01:16
@dorner dorner force-pushed the use-event-data branch 4 times, most recently from fe3f540 to 16a3f6e Compare January 2, 2024 16:12
@dorner dorner marked this pull request as ready for review January 2, 2024 16:15
Remove spec that was causing issues (diffing isn't really needed any more anyway)
@dorner
Copy link
Collaborator Author

dorner commented Jan 22, 2024

@cielf as per discussion, pushed a commit that handles dealing with storage location change on update.

@cielf cielf requested a review from awwaiid January 26, 2024 14:36
@inventory = View::Inventory.new(current_organization.id)
end
@items_by_storage_collection_and_quantity = ItemsByStorageCollectionAndQuantityQuery.call(organization: current_organization,
inventory: @inventory,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: I verified that ItemsByStorageCollectionAndQuantityQuery handles nil inventory

Copy link
Collaborator

@awwaiid awwaiid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, but the tests don't pass. I created #4078 which pulls in the latest main to see if that would help, but ... not so much.

inventory = View::Inventory.new(@request.organization_id)
end

@request.request_items.map { |json| RequestItem.from_json(json, @request, inventory) }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NTS: Verified RequestItem new param

@@ -24,6 +24,9 @@ def show

@families = @partner.families
@children = @partner.children
if Event.read_events?(@partner.organization)
@inventory = View::Inventory.new(@partner.organization_id)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NTS: The view does a RequestItem.from_json(json, request, @inventory) with this, which is OK

.sort_by(&:name)
.map { |i| OpenStruct.new(name: i.name, id: i.item_id) }
else
organization.items.joins(:storage_locations).select(:id, :name).group(:id, :name).order(name: :asc)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NTS: This one is items scoped to organization which might change the results

@selected_item_category = filter_params[:containing]
@items = current_organization.storage_locations.items_inventoried
@items = StorageLocation.items_inventoried(current_organization, @inventory)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NTS: As long as these callers are already scoped to current organization storage locations this'll be fine (like here)

organization_id: adjustment.organization_id,
event_time: adjustment.created_at,
event_time: Time.zone.now,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems reasonable

@@ -63,16 +63,18 @@ class StorageLocation < ApplicationRecord
scope :for_csv_export, ->(organization, *) { where(organization: organization) }
scope :active_locations, -> { where(discarded_at: nil) }

def self.item_total(item_id)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NTS: Dead code

include Dry.Types()
end

module View
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling this a view might be confusing vs app/views/*. No better name immediately leaps to mind.

if error.nil?
ApplicationRecord.transaction do
deallocate_inventory_items
KitDeallocateEvent.publish(@kit, @storage_location, @decrease_by)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

end
end

context 'without inventory items' do
it 'should return true' do
kit = create(:kit, :with_item)
expect(kit.reload.can_deactivate?).to eq(true)
expect(kit.reload.can_deactivate?(nil)).to eq(true)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NTS: hmm. Seems like can_deactivate should work without an explicit param

end

it "gives an error if we attempt to adjust inventory below 0" do
quantity = -1 * (inventory_item_1.quantity + 1)
quantity = -101
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the explicitness of these

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me too. The dynamism of our specs really rubs me the wrong way since it's so hard to tell what things are supposed to be. Half the time the specs just copy the code from the thing they're testing. -_-

# Conflicts:
#	app/models/storage_location.rb
#	app/services/adjustment_create_service.rb
#	app/services/donation_create_service.rb
#	app/services/purchase_create_service.rb
#	spec/factories/donations.rb
#	spec/factories/purchases.rb
#	spec/models/storage_location_spec.rb
Copy link
Collaborator

@awwaiid awwaiid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I traced through, everything looks good!

@cielf
Copy link
Collaborator

cielf commented Feb 9, 2024

I've done some light testing of this and I'm ok with merging it in. Sanity check -- @awwaiid -- did your technical review include the changes in #3994?

@cielf
Copy link
Collaborator

cielf commented Feb 11, 2024

per @awwaiid, all the changes from #3994 are in #4000, so it can be closed and then we can merge this.

 Conflicts:
	app/views/audits/show.html.erb
	app/views/items/show.html.erb
	app/views/partners/dashboards/_requests_in_progress.html.erb
	app/views/storage_locations/show.html.erb
	db/schema.rb
	spec/models/event_spec.rb
@awwaiid
Copy link
Collaborator

awwaiid commented Feb 11, 2024

@dorner rspec (non-browser) failed and then I re-ran it without changes and it passed, so you should take a look -- https://github.com/rubyforgood/human-essentials/actions/runs/7862418075/job/21451870283?pr=4000

@cielf cielf merged commit 07cb695 into main Feb 12, 2024
20 checks passed
@cielf cielf deleted the use-event-data branch February 12, 2024 17:10
Copy link
Contributor

@dorner: Your PR Use event data is part of today's Human Essentials production release: 2024.02.18.
Thank you very much for your contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants