From c596b116ca80d7f1c2cc3c9e9eef9a9471361928 Mon Sep 17 00:00:00 2001 From: nunocruz Date: Tue, 13 Oct 2026 11:35:43 +0100 Subject: [PATCH 1/6] Support optional people properties in Kotlin generator AnalyticsProperty.innerValue is now Any? and optional special properties are read with value?.key instead of value.key, matching how optional event properties are already handled. Co-Authored-By: Claude Opus 5.5 --- changelog.md | 2 ++ lib/evva/kotlin_generator.rb | 12 +++++++++++- lib/evva/templates/kotlin/people_properties.kt | 4 ++-- spec/lib/evva/kotlin_generator_spec.rb | 18 +++++++++++++++++- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/changelog.md b/changelog.md index 34e2976..75d53a2 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,8 @@ ## [Unreleased] +- Support optional people properties in Kotlin: `innerValue` is now `Any?` and optional special properties use `value?.key` + ## [0.9.0] - 2026-09-04 - Add `Platform` column support in the events sheet and `filter_by_platforms` configuration to filter generated events by platform diff --git a/lib/evva/kotlin_generator.rb b/lib/evva/kotlin_generator.rb index cfbaab3..aec8e25 100644 --- a/lib/evva/kotlin_generator.rb +++ b/lib/evva/kotlin_generator.rb @@ -84,11 +84,21 @@ def people_properties(people_bundle, file_name, enums_file_name, destinations_fi properties = people_bundle.map do |property| type = native_type(property.type) + value_fetcher = "value" + + if special_property?(type) + if type.end_with?("?") + # optional value, we need ? to access a parameter + value_fetcher += "?" + end + value_fetcher += ".key" + end + { class_name: camelize(property.property_name), property_name: constantize(property.property_name), type: type, - is_special_property: special_property?(property.type), + value_fetcher: value_fetcher, destinations: property.destinations.map { |p| constantize(p) }, } end diff --git a/lib/evva/templates/kotlin/people_properties.kt b/lib/evva/templates/kotlin/people_properties.kt index 4d3dd85..a47a8a2 100644 --- a/lib/evva/templates/kotlin/people_properties.kt +++ b/lib/evva/templates/kotlin/people_properties.kt @@ -1,6 +1,6 @@ sealed class <%= class_name %>( property: <%= enums_class_name %>, - val innerValue: Any, + val innerValue: Any?, val destinations: Array<<%= destinations_class_name %>> = emptyArray(), ) { val name = property.key @@ -10,7 +10,7 @@ sealed class <%= class_name %>( val value: <%= property[:type] %>, ) : <%= class_name %>( property = <%= enums_class_name %>.<%= property[:property_name] %>, - innerValue = value<% if property[:is_special_property] %>.key<% end %>, + innerValue = <%= property[:value_fetcher] %>, <%- if property[:destinations].count > 0 -%> destinations = arrayOf( <%- property[:destinations].each_with_index do |d, index| -%> diff --git a/spec/lib/evva/kotlin_generator_spec.rb b/spec/lib/evva/kotlin_generator_spec.rb index 2ff6dad..5d6c236 100644 --- a/spec/lib/evva/kotlin_generator_spec.rb +++ b/spec/lib/evva/kotlin_generator_spec.rb @@ -159,6 +159,8 @@ Evva::AnalyticsProperty.new("rounds_with_wear", "String", []), Evva::AnalyticsProperty.new("last_active_at", "Date", []), Evva::AnalyticsProperty.new("wear_platform", "WearableAppPlatform", ["firebase", "custom destination"]), + Evva::AnalyticsProperty.new("last_round_id", "Long?", []), + Evva::AnalyticsProperty.new("previous_wear_platform", "WearableAppPlatform?", []), ] } let(:expected) { <<-Kotlin @@ -170,7 +172,7 @@ sealed class AnalyticsProperty( property: AnalyticsProperties, - val innerValue: Any, + val innerValue: Any?, val destinations: Array = emptyArray(), ) { val name = property.key @@ -199,6 +201,20 @@ AnalyticsDestinations.CUSTOM_DESTINATION, ), ) + + data class LastRoundId( + val value: Long?, + ) : AnalyticsProperty( + property = AnalyticsProperties.LAST_ROUND_ID, + innerValue = value, + ) + + data class PreviousWearPlatform( + val value: WearableAppPlatform?, + ) : AnalyticsProperty( + property = AnalyticsProperties.PREVIOUS_WEAR_PLATFORM, + innerValue = value?.key, + ) } Kotlin } From e10365bc6044e95a901870eb564d66bde663d925 Mon Sep 17 00:00:00 2001 From: nunocruz Date: Tue, 13 Oct 2026 11:41:35 +0100 Subject: [PATCH 2/6] Simplify optional people property spec Make the existing wear_platform entry optional instead of adding new entries. Co-Authored-By: Claude Opus 5.5 --- spec/lib/evva/kotlin_generator_spec.rb | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/spec/lib/evva/kotlin_generator_spec.rb b/spec/lib/evva/kotlin_generator_spec.rb index 5d6c236..d250c2f 100644 --- a/spec/lib/evva/kotlin_generator_spec.rb +++ b/spec/lib/evva/kotlin_generator_spec.rb @@ -158,9 +158,7 @@ let(:people_bundle) { [ Evva::AnalyticsProperty.new("rounds_with_wear", "String", []), Evva::AnalyticsProperty.new("last_active_at", "Date", []), - Evva::AnalyticsProperty.new("wear_platform", "WearableAppPlatform", ["firebase", "custom destination"]), - Evva::AnalyticsProperty.new("last_round_id", "Long?", []), - Evva::AnalyticsProperty.new("previous_wear_platform", "WearableAppPlatform?", []), + Evva::AnalyticsProperty.new("wear_platform", "WearableAppPlatform?", ["firebase", "custom destination"]), ] } let(:expected) { <<-Kotlin @@ -192,29 +190,15 @@ ) data class WearPlatform( - val value: WearableAppPlatform, + val value: WearableAppPlatform?, ) : AnalyticsProperty( property = AnalyticsProperties.WEAR_PLATFORM, - innerValue = value.key, + innerValue = value?.key, destinations = arrayOf( AnalyticsDestinations.FIREBASE, AnalyticsDestinations.CUSTOM_DESTINATION, ), ) - - data class LastRoundId( - val value: Long?, - ) : AnalyticsProperty( - property = AnalyticsProperties.LAST_ROUND_ID, - innerValue = value, - ) - - data class PreviousWearPlatform( - val value: WearableAppPlatform?, - ) : AnalyticsProperty( - property = AnalyticsProperties.PREVIOUS_WEAR_PLATFORM, - innerValue = value?.key, - ) } Kotlin } From c9c4f1da0a503594e956be71ef5a8c4a164cdd25 Mon Sep 17 00:00:00 2001 From: nunocruz Date: Tue, 13 Oct 2026 11:51:15 +0100 Subject: [PATCH 3/6] Bump version to 0.10.0 Also syncs the csv dependency in Gemfile.lock with the gemspec. Co-Authored-By: Claude Opus 5.5 --- Gemfile.lock | 4 ++-- changelog.md | 2 ++ lib/evva/version.rb | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9b14156..420ed24 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,9 @@ PATH remote: . specs: - evva (0.9.0) + evva (0.10.0) colorize (~> 1.0) - csv (>= 3.0) + csv (~> 3.0) safe_yaml (~> 1.0) GEM diff --git a/changelog.md b/changelog.md index 75d53a2..8585d38 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,8 @@ ## [Unreleased] +## [0.10.0] - 2026-10-13 + - Support optional people properties in Kotlin: `innerValue` is now `Any?` and optional special properties use `value?.key` ## [0.9.0] - 2026-09-04 diff --git a/lib/evva/version.rb b/lib/evva/version.rb index e2b1fdf..3c3059f 100644 --- a/lib/evva/version.rb +++ b/lib/evva/version.rb @@ -1,4 +1,4 @@ module Evva - VERSION = "0.9.0".freeze - VERSION_UPDATED_AT = "2026-09-04".freeze + VERSION = "0.10.0".freeze + VERSION_UPDATED_AT = "2026-10-13".freeze end From c4aa8ab01bcbdee3523e2c924dd48b1f038f8888 Mon Sep 17 00:00:00 2001 From: nunocruz Date: Tue, 13 Oct 2026 12:18:07 +0100 Subject: [PATCH 4/6] Revert version bump to 0.10.0 Releases are cut outside of feature PRs. The changelog entry stays under Unreleased, and Gemfile.lock keeps the csv (~> 3.0) sync with the gemspec. Co-Authored-By: Claude Opus 5.5 --- Gemfile.lock | 2 +- changelog.md | 2 -- lib/evva/version.rb | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 420ed24..fd5a943 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - evva (0.10.0) + evva (0.9.0) colorize (~> 1.0) csv (~> 3.0) safe_yaml (~> 1.0) diff --git a/changelog.md b/changelog.md index 8585d38..75d53a2 100644 --- a/changelog.md +++ b/changelog.md @@ -2,8 +2,6 @@ ## [Unreleased] -## [0.10.0] - 2026-10-13 - - Support optional people properties in Kotlin: `innerValue` is now `Any?` and optional special properties use `value?.key` ## [0.9.0] - 2026-09-04 diff --git a/lib/evva/version.rb b/lib/evva/version.rb index 3c3059f..e2b1fdf 100644 --- a/lib/evva/version.rb +++ b/lib/evva/version.rb @@ -1,4 +1,4 @@ module Evva - VERSION = "0.10.0".freeze - VERSION_UPDATED_AT = "2026-10-13".freeze + VERSION = "0.9.0".freeze + VERSION_UPDATED_AT = "2026-09-04".freeze end From a9357720b55e2d132dfed0d61711cd548736cf96 Mon Sep 17 00:00:00 2001 From: nunocruz Date: Tue, 13 Oct 2026 12:20:55 +0100 Subject: [PATCH 5/6] Cover non-null special and nullable native people properties Adds a non-null enum property (value.key) and makes rounds_with_wear a nullable native type (value), so every value-fetching path in the people properties generator is exercised. Co-Authored-By: Claude Opus 5.5 --- spec/lib/evva/kotlin_generator_spec.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/spec/lib/evva/kotlin_generator_spec.rb b/spec/lib/evva/kotlin_generator_spec.rb index d250c2f..d620459 100644 --- a/spec/lib/evva/kotlin_generator_spec.rb +++ b/spec/lib/evva/kotlin_generator_spec.rb @@ -156,8 +156,9 @@ describe "#people_properties" do subject { generator.people_properties(people_bundle, "AnalyticsProperty", "AnalyticsProperties", "AnalyticsDestinations") } let(:people_bundle) { [ - Evva::AnalyticsProperty.new("rounds_with_wear", "String", []), + Evva::AnalyticsProperty.new("rounds_with_wear", "String?", []), Evva::AnalyticsProperty.new("last_active_at", "Date", []), + Evva::AnalyticsProperty.new("previous_wear_platform", "WearableAppPlatform", []), Evva::AnalyticsProperty.new("wear_platform", "WearableAppPlatform?", ["firebase", "custom destination"]), ] } let(:expected) { @@ -176,7 +177,7 @@ val name = property.key data class RoundsWithWear( - val value: String, + val value: String?, ) : AnalyticsProperty( property = AnalyticsProperties.ROUNDS_WITH_WEAR, innerValue = value, @@ -189,6 +190,13 @@ innerValue = value, ) + data class PreviousWearPlatform( + val value: WearableAppPlatform, + ) : AnalyticsProperty( + property = AnalyticsProperties.PREVIOUS_WEAR_PLATFORM, + innerValue = value.key, + ) + data class WearPlatform( val value: WearableAppPlatform?, ) : AnalyticsProperty( From 3e02bc92bdc7e1013c9101adb6afc0eb1d798b81 Mon Sep 17 00:00:00 2001 From: nunocruz Date: Tue, 13 Oct 2026 12:23:59 +0100 Subject: [PATCH 6/6] Use upgrade_source as the non-null enum people property example Co-Authored-By: Claude Opus 5.5 --- spec/lib/evva/kotlin_generator_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/lib/evva/kotlin_generator_spec.rb b/spec/lib/evva/kotlin_generator_spec.rb index d620459..a6f6974 100644 --- a/spec/lib/evva/kotlin_generator_spec.rb +++ b/spec/lib/evva/kotlin_generator_spec.rb @@ -158,7 +158,7 @@ let(:people_bundle) { [ Evva::AnalyticsProperty.new("rounds_with_wear", "String?", []), Evva::AnalyticsProperty.new("last_active_at", "Date", []), - Evva::AnalyticsProperty.new("previous_wear_platform", "WearableAppPlatform", []), + Evva::AnalyticsProperty.new("upgrade_source", "PremiumFrom", []), Evva::AnalyticsProperty.new("wear_platform", "WearableAppPlatform?", ["firebase", "custom destination"]), ] } let(:expected) { @@ -190,10 +190,10 @@ innerValue = value, ) - data class PreviousWearPlatform( - val value: WearableAppPlatform, + data class UpgradeSource( + val value: PremiumFrom, ) : AnalyticsProperty( - property = AnalyticsProperties.PREVIOUS_WEAR_PLATFORM, + property = AnalyticsProperties.UPGRADE_SOURCE, innerValue = value.key, )