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

feat(unique_count): Add active_unique_property to event stores #1710

Merged
merged 1 commit into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def per_event_aggregation

protected

def persisted_event_store_instante
def persisted_event_store_instance
event_store = event_store_class.new(
code: billable_metric.code,
subscription:,
Expand All @@ -137,7 +137,7 @@ def compute_event_aggregation
end

def persisted_sum
persisted_event_store_instante.prorated_sum(
persisted_event_store_instance.prorated_sum(
period_duration:,
persisted_duration: subscription.date_diff_with_timezone(from_datetime, to_datetime),
)
Expand All @@ -147,7 +147,7 @@ def recurring_value
previous_charge_fee_units = previous_charge_fee&.units
return previous_charge_fee_units if previous_charge_fee_units

recurring_value_before_first_fee = persisted_event_store_instante.sum
recurring_value_before_first_fee = persisted_event_store_instance.sum

((recurring_value_before_first_fee || 0) <= 0) ? nil : recurring_value_before_first_fee
end
Expand All @@ -174,7 +174,7 @@ def compute_grouped_event_aggregation
end

def grouped_persisted_sums
persisted_event_store_instante.grouped_prorated_sum(
persisted_event_store_instance.grouped_prorated_sum(
period_duration:,
persisted_duration: subscription.date_diff_with_timezone(from_datetime, to_datetime),
)
Expand Down
15 changes: 15 additions & 0 deletions app/services/events/stores/clickhouse_store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,21 @@ def grouped_count
prepare_grouped_result(::Clickhouse::EventsRaw.connection.select_all(sql).rows)
end

# NOTE: check if an event created before the current on belongs to an active (as in present and not removed)
# unique property
def active_unique_property?(event)
previous_event = events
.where('events_raw.properties[?] = ?', aggregation_property, event.properties[aggregation_property])
.where('events_raw.timestamp < ?', event.timestamp)
.reorder(timestamp: :desc)
.first

previous_event && (
previous_event.properties['operation_type'].nil? ||
previous_event.properties['operation_type'] == 'add'
)
end

def unique_count
query = Events::Stores::Clickhouse::UniqueCountQuery.new(store: self)
sql = ActiveRecord::Base.sanitize_sql_for_conditions(
Expand Down
15 changes: 15 additions & 0 deletions app/services/events/stores/postgres_store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ def grouped_count
prepare_grouped_result(results)
end

# NOTE: check if an event created before the current on belongs to an active (as in present and not removed)
# unique property
def active_unique_property?(event)
previous_event = events.where.not(id: event.id)
.where('events.properties @> ?', { aggregation_property => event.properties[aggregation_property] }.to_json)
.where('events.timestamp < ?', event.timestamp)
.reorder(timestamp: :desc)
.first

previous_event && (
previous_event.properties['operation_type'].nil? ||
previous_event.properties['operation_type'] == 'add'
)
end

def unique_count
query = Events::Stores::Postgres::UniqueCountQuery.new(store: self)
sql = ActiveRecord::Base.sanitize_sql_for_conditions([query.query])
Expand Down
2 changes: 1 addition & 1 deletion app/services/utils/timezone_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def self.date_in_customer_timezone_sql(customer, value)
end
sanitized_timezone = ActiveRecord::Base.sanitize_sql_for_conditions(customer.applicable_timezone)

"#{sanitized_field_name}::timestamptz AT TIME ZONE '#{sanitized_timezone}'"
"(#{sanitized_field_name})::timestamptz AT TIME ZONE '#{sanitized_timezone}'"
end

def self.at_time_zone
Expand Down
76 changes: 76 additions & 0 deletions spec/services/events/stores/clickhouse_store_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,82 @@
end
end

describe '#active_unique_property?' do
before { event_store.aggregation_property = billable_metric.field_name }

it 'returns false when no previous events exist' do
event = ::Clickhouse::EventsRaw.create!(
organization_id: organization.id,
external_subscription_id: subscription.external_id,
external_customer_id: customer.external_id,
code:,
timestamp: (boundaries[:from_datetime] + 2.days).end_of_day,
properties: {
billable_metric.field_name => SecureRandom.uuid,
},
)

expect(event_store).not_to be_active_unique_property(event)
end

context 'when event is already active' do
it 'returns true if the event property is active' do
::Clickhouse::EventsRaw.create!(
organization_id: organization.id,
external_subscription_id: subscription.external_id,
external_customer_id: customer.external_id,
code:,
timestamp: (boundaries[:from_datetime] + 2.days).end_of_day,
properties: {
billable_metric.field_name => 2,
},
)

event = ::Clickhouse::EventsRaw.create!(
organization_id: organization.id,
external_subscription_id: subscription.external_id,
external_customer_id: customer.external_id,
code:,
timestamp: (boundaries[:from_datetime] + 3.days).end_of_day,
properties: {
billable_metric.field_name => 2,
},
)

expect(event_store).to be_active_unique_property(event)
end
end

context 'with a previous removed event' do
it 'returns false' do
::Clickhouse::EventsRaw.create!(
organization_id: organization.id,
external_subscription_id: subscription.external_id,
external_customer_id: customer.external_id,
code:,
timestamp: (boundaries[:from_datetime] + 2.days).end_of_day,
properties: {
billable_metric.field_name => 2,
operation_type: 'remove',
},
)

event = ::Clickhouse::EventsRaw.create!(
organization_id: organization.id,
external_subscription_id: subscription.external_id,
external_customer_id: customer.external_id,
code:,
timestamp: (boundaries[:from_datetime] + 3.days).end_of_day,
properties: {
billable_metric.field_name => 2,
},
)

expect(event_store).not_to be_active_unique_property(event)
end
end
end

describe '#unique_count' do
it 'returns the number of unique active event properties' do
Clickhouse::EventsRaw.create!(
Expand Down
81 changes: 81 additions & 0 deletions spec/services/events/stores/postgres_store_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,87 @@
end
end

describe '#active_unique_property?' do
before { event_store.aggregation_property = billable_metric.field_name }

it 'returns false when no previous events exist' do
event = create(
:event,
organization_id: organization.id,
external_subscription_id: subscription.external_id,
external_customer_id: customer.external_id,
code:,
timestamp: (boundaries[:from_datetime] + 2.days).end_of_day,
properties: {
billable_metric.field_name => SecureRandom.uuid,
},
)

expect(event_store).not_to be_active_unique_property(event)
end

context 'when event is already active' do
it 'returns true if the event property is active' do
create(
:event,
organization_id: organization.id,
external_subscription_id: subscription.external_id,
external_customer_id: customer.external_id,
code:,
timestamp: (boundaries[:from_datetime] + 2.days).end_of_day,
properties: {
billable_metric.field_name => 2,
},
)

event = create(
:event,
organization_id: organization.id,
external_subscription_id: subscription.external_id,
external_customer_id: customer.external_id,
code:,
timestamp: (boundaries[:from_datetime] + 3.days).end_of_day,
properties: {
billable_metric.field_name => 2,
},
)

expect(event_store).to be_active_unique_property(event)
end
end

context 'with a previous removed event' do
it 'returns false' do
create(
:event,
organization_id: organization.id,
external_subscription_id: subscription.external_id,
external_customer_id: customer.external_id,
code:,
timestamp: (boundaries[:from_datetime] + 2.days).end_of_day,
properties: {
billable_metric.field_name => 2,
operation_type: 'remove',
},
)

event = create(
:event,
organization_id: organization.id,
external_subscription_id: subscription.external_id,
external_customer_id: customer.external_id,
code:,
timestamp: (boundaries[:from_datetime] + 3.days).end_of_day,
properties: {
billable_metric.field_name => 2,
},
)

expect(event_store).not_to be_active_unique_property(event)
end
end
end

describe '#unique_count' do
it 'returns the number of unique active event properties' do
create(
Expand Down
Loading