Skip to content

Commit 4f0f65a

Browse files
authored
Merge pull request #2987 from DataDog/aleksandr-gringauz/feature/app-launch-vitals-mapper
RUM-13103: Introduce vitalAppLaunchEventMapper
2 parents 875dfc8 + 9924563 commit 4f0f65a

File tree

10 files changed

+179
-30
lines changed

10 files changed

+179
-30
lines changed

features/dd-sdk-android-rum/api/apiSurface

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ data class com.datadog.android.rum.RumConfiguration
7575
fun setErrorEventMapper(com.datadog.android.event.EventMapper<com.datadog.android.rum.model.ErrorEvent>): Builder
7676
fun setLongTaskEventMapper(com.datadog.android.event.EventMapper<com.datadog.android.rum.model.LongTaskEvent>): Builder
7777
DEPRECATED fun setVitalEventMapper(com.datadog.android.event.EventMapper<com.datadog.android.rum.model.RumVitalOperationStepEvent>): Builder
78-
fun setVitalOperationStepEventMapper(com.datadog.android.event.EventMapper<com.datadog.android.rum.model.RumVitalOperationStepEvent>): Builder
78+
fun setVitalEventMapper(com.datadog.android.event.EventMapper<com.datadog.android.rum.model.RumVitalOperationStepEvent> = NoOpEventMapper(), com.datadog.android.event.EventMapper<com.datadog.android.rum.model.RumVitalAppLaunchEvent> = NoOpEventMapper()): Builder
7979
fun trackBackgroundEvents(Boolean): Builder
8080
fun trackFrustrations(Boolean): Builder
8181
fun setVitalsUpdateFrequency(com.datadog.android.rum.configuration.VitalsUpdateFrequency): Builder

features/dd-sdk-android-rum/api/dd-sdk-android-rum.api

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ public final class com/datadog/android/rum/RumConfiguration$Builder {
114114
public final fun setTelemetrySampleRate (F)Lcom/datadog/android/rum/RumConfiguration$Builder;
115115
public final fun setViewEventMapper (Lcom/datadog/android/rum/event/ViewEventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder;
116116
public final fun setVitalEventMapper (Lcom/datadog/android/event/EventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder;
117-
public final fun setVitalOperationStepEventMapper (Lcom/datadog/android/event/EventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder;
117+
public final fun setVitalEventMapper (Lcom/datadog/android/event/EventMapper;Lcom/datadog/android/event/EventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder;
118+
public static synthetic fun setVitalEventMapper$default (Lcom/datadog/android/rum/RumConfiguration$Builder;Lcom/datadog/android/event/EventMapper;Lcom/datadog/android/event/EventMapper;ILjava/lang/Object;)Lcom/datadog/android/rum/RumConfiguration$Builder;
118119
public final fun setVitalsUpdateFrequency (Lcom/datadog/android/rum/configuration/VitalsUpdateFrequency;)Lcom/datadog/android/rum/RumConfiguration$Builder;
119120
public final fun trackAnonymousUser (Z)Lcom/datadog/android/rum/RumConfiguration$Builder;
120121
public final fun trackBackgroundEvents (Z)Lcom/datadog/android/rum/RumConfiguration$Builder;

features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumConfiguration.kt

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package com.datadog.android.rum
99
import android.os.Looper
1010
import androidx.annotation.FloatRange
1111
import com.datadog.android.event.EventMapper
12+
import com.datadog.android.event.NoOpEventMapper
1213
import com.datadog.android.rum.configuration.SlowFramesConfiguration
1314
import com.datadog.android.rum.configuration.VitalsUpdateFrequency
1415
import com.datadog.android.rum.event.ViewEventMapper
@@ -22,6 +23,7 @@ import com.datadog.android.rum.model.ActionEvent
2223
import com.datadog.android.rum.model.ErrorEvent
2324
import com.datadog.android.rum.model.LongTaskEvent
2425
import com.datadog.android.rum.model.ResourceEvent
26+
import com.datadog.android.rum.model.RumVitalAppLaunchEvent
2527
import com.datadog.android.rum.model.RumVitalOperationStepEvent
2628
import com.datadog.android.rum.model.ViewEvent
2729
import com.datadog.android.rum.tracking.ActionTrackingStrategy
@@ -228,18 +230,27 @@ data class RumConfiguration internal constructor(
228230
@Deprecated(message = "Use setVitalOperationStepEventMapper instead")
229231
fun setVitalEventMapper(eventMapper: EventMapper<RumVitalOperationStepEvent>): Builder {
230232
@OptIn(ExperimentalRumApi::class)
231-
return setVitalOperationStepEventMapper(eventMapper)
233+
return setVitalEventMapper(vitalOperationStepEventMapper = eventMapper)
232234
}
233235

234236
/**
235-
* Sets the [EventMapper] for the RUM [RumVitalOperationStepEvent]. You can use this interface implementation
236-
* to modify the [RumVitalOperationStepEvent] attributes before serialisation.
237+
* Sets the [EventMapper] for the RUM [RumVitalOperationStepEvent] and the
238+
* RUM [RumVitalAppLaunchEvent]. You can use this interface implementation
239+
* to modify the [RumVitalOperationStepEvent] attributes and the [RumVitalAppLaunchEvent]
240+
* attributes before serialisation.
237241
*
238-
* @param eventMapper the [EventMapper] implementation.
242+
* @param vitalOperationStepEventMapper the [EventMapper] implementation for [RumVitalOperationStepEvent]
243+
* @param vitalAppLaunchEventMapper the [EventMapper] implementation for [RumVitalAppLaunchEvent]
239244
*/
240245
@ExperimentalRumApi
241-
fun setVitalOperationStepEventMapper(eventMapper: EventMapper<RumVitalOperationStepEvent>): Builder {
242-
rumConfig = rumConfig.copy(vitalOperationStepEventMapper = eventMapper)
246+
fun setVitalEventMapper(
247+
vitalOperationStepEventMapper: EventMapper<RumVitalOperationStepEvent> = NoOpEventMapper(),
248+
vitalAppLaunchEventMapper: EventMapper<RumVitalAppLaunchEvent> = NoOpEventMapper()
249+
): Builder {
250+
rumConfig = rumConfig.copy(
251+
vitalOperationStepEventMapper = vitalOperationStepEventMapper,
252+
vitalAppLaunchEventMapper = vitalAppLaunchEventMapper
253+
)
243254
return this
244255
}
245256

features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ import com.datadog.android.rum.model.ActionEvent
103103
import com.datadog.android.rum.model.ErrorEvent
104104
import com.datadog.android.rum.model.LongTaskEvent
105105
import com.datadog.android.rum.model.ResourceEvent
106+
import com.datadog.android.rum.model.RumVitalAppLaunchEvent
106107
import com.datadog.android.rum.model.RumVitalOperationStepEvent
107108
import com.datadog.android.rum.model.ViewEvent
108109
import com.datadog.android.rum.tracking.ActionTrackingStrategy
@@ -378,6 +379,7 @@ internal class RumFeature(
378379
actionEventMapper = configuration.actionEventMapper,
379380
longTaskEventMapper = configuration.longTaskEventMapper,
380381
vitalOperationStepEventMapper = configuration.vitalOperationStepEventMapper,
382+
vitalAppLaunchEventMapper = configuration.vitalAppLaunchEventMapper,
381383
telemetryConfigurationMapper = configuration.telemetryConfigurationMapper,
382384
internalLogger = sdkCore.internalLogger
383385
),
@@ -733,6 +735,7 @@ internal class RumFeature(
733735
val actionEventMapper: EventMapper<ActionEvent>,
734736
val longTaskEventMapper: EventMapper<LongTaskEvent>,
735737
val vitalOperationStepEventMapper: EventMapper<RumVitalOperationStepEvent>,
738+
val vitalAppLaunchEventMapper: EventMapper<RumVitalAppLaunchEvent>,
736739
val telemetryConfigurationMapper: EventMapper<TelemetryConfigurationEvent>,
737740
val backgroundEventTracking: Boolean,
738741
val trackFrustrations: Boolean,
@@ -786,6 +789,7 @@ internal class RumFeature(
786789
actionEventMapper = NoOpEventMapper(),
787790
longTaskEventMapper = NoOpEventMapper(),
788791
vitalOperationStepEventMapper = NoOpEventMapper(),
792+
vitalAppLaunchEventMapper = NoOpEventMapper(),
789793
telemetryConfigurationMapper = NoOpEventMapper(),
790794
backgroundEventTracking = false,
791795
trackFrustrations = true,

features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapper.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ internal data class RumEventMapper(
2929
val actionEventMapper: EventMapper<ActionEvent> = NoOpEventMapper(),
3030
val longTaskEventMapper: EventMapper<LongTaskEvent> = NoOpEventMapper(),
3131
val vitalOperationStepEventMapper: EventMapper<RumVitalOperationStepEvent> = NoOpEventMapper(),
32+
val vitalAppLaunchEventMapper: EventMapper<RumVitalAppLaunchEvent> = NoOpEventMapper(),
3233
val telemetryConfigurationMapper: EventMapper<TelemetryConfigurationEvent> = NoOpEventMapper(),
3334
private val internalLogger: InternalLogger
3435
) : EventMapper<Any> {
@@ -64,9 +65,8 @@ internal data class RumEventMapper(
6465
is ResourceEvent -> resourceEventMapper.map(event)
6566
is LongTaskEvent -> longTaskEventMapper.map(event)
6667
is RumVitalOperationStepEvent -> vitalOperationStepEventMapper.map(event)
68+
is RumVitalAppLaunchEvent -> vitalAppLaunchEventMapper.map(event)
6769
is TelemetryConfigurationEvent -> telemetryConfigurationMapper.map(event)
68-
// TODO RUM-13103: add vitalAppLaunchEventMapper.
69-
is RumVitalAppLaunchEvent,
7070
is TelemetryDebugEvent,
7171
is TelemetryUsageEvent,
7272
is TelemetryErrorEvent -> event

features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumConfigurationBuilderTest.kt

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import com.datadog.android.rum.model.ActionEvent
2525
import com.datadog.android.rum.model.ErrorEvent
2626
import com.datadog.android.rum.model.LongTaskEvent
2727
import com.datadog.android.rum.model.ResourceEvent
28+
import com.datadog.android.rum.model.RumVitalAppLaunchEvent
2829
import com.datadog.android.rum.model.RumVitalOperationStepEvent
2930
import com.datadog.android.rum.model.ViewEvent
3031
import com.datadog.android.rum.tracking.ActionTrackingStrategy
@@ -451,13 +452,13 @@ internal class RumConfigurationBuilderTest {
451452

452453
@OptIn(ExperimentalRumApi::class)
453454
@Test
454-
fun `M build config with RUM Vital eventMapper W setVitalOperationStepEventMapper() & build()`() {
455+
fun `M build config W setVitalEventMapper() & build() {only vitalOperationStepEventMapper}`() {
455456
// Given
456457
val eventMapper: EventMapper<RumVitalOperationStepEvent> = mock()
457458

458459
// When
459460
val rumConfiguration = testedBuilder
460-
.setVitalOperationStepEventMapper(eventMapper)
461+
.setVitalEventMapper(vitalOperationStepEventMapper = eventMapper)
461462
.build()
462463

463464
// Then
@@ -468,6 +469,49 @@ internal class RumConfigurationBuilderTest {
468469
)
469470
}
470471

472+
@OptIn(ExperimentalRumApi::class)
473+
@Test
474+
fun `M build config W setVitalEventMapper() & build() {only vitalAppLaunchEventMapper}`() {
475+
// Given
476+
val eventMapper: EventMapper<RumVitalAppLaunchEvent> = mock()
477+
478+
// When
479+
val rumConfiguration = testedBuilder
480+
.setVitalEventMapper(vitalAppLaunchEventMapper = eventMapper)
481+
.build()
482+
483+
// Then
484+
assertThat(rumConfiguration.featureConfiguration).isEqualTo(
485+
RumFeature.DEFAULT_RUM_CONFIG.copy(
486+
vitalAppLaunchEventMapper = eventMapper
487+
)
488+
)
489+
}
490+
491+
@OptIn(ExperimentalRumApi::class)
492+
@Test
493+
fun `M build config W setVitalEventMapper() & build()`() {
494+
// Given
495+
val rumVitalAppLaunchEventMapper: EventMapper<RumVitalAppLaunchEvent> = mock()
496+
val rumVitalOperationStepEventMapper: EventMapper<RumVitalOperationStepEvent> = mock()
497+
498+
// When
499+
val rumConfiguration = testedBuilder
500+
.setVitalEventMapper(
501+
vitalOperationStepEventMapper = rumVitalOperationStepEventMapper,
502+
vitalAppLaunchEventMapper = rumVitalAppLaunchEventMapper
503+
)
504+
.build()
505+
506+
// Then
507+
assertThat(rumConfiguration.featureConfiguration).isEqualTo(
508+
RumFeature.DEFAULT_RUM_CONFIG.copy(
509+
vitalOperationStepEventMapper = rumVitalOperationStepEventMapper,
510+
vitalAppLaunchEventMapper = rumVitalAppLaunchEventMapper
511+
)
512+
)
513+
}
514+
471515
@Test
472516
fun `M use the given frequency W setVitalsMonitorUpdateFrequency`(
473517
@Forgery fakeFrequency: VitalsUpdateFrequency

features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapperTest.kt

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.datadog.android.rum.model.ActionEvent
1212
import com.datadog.android.rum.model.ErrorEvent
1313
import com.datadog.android.rum.model.LongTaskEvent
1414
import com.datadog.android.rum.model.ResourceEvent
15+
import com.datadog.android.rum.model.RumVitalAppLaunchEvent
1516
import com.datadog.android.rum.model.RumVitalOperationStepEvent
1617
import com.datadog.android.rum.model.ViewEvent
1718
import com.datadog.android.rum.utils.forge.Configurator
@@ -66,7 +67,10 @@ internal class RumEventMapperTest {
6667
lateinit var mockLongTaskEventMapper: EventMapper<LongTaskEvent>
6768

6869
@Mock
69-
lateinit var mockVitalOperationStepEvent: EventMapper<RumVitalOperationStepEvent>
70+
lateinit var mockVitalOperationStepEventMapper: EventMapper<RumVitalOperationStepEvent>
71+
72+
@Mock
73+
lateinit var mockVitalAppLaunchEventMapper: EventMapper<RumVitalAppLaunchEvent>
7074

7175
@Mock
7276
lateinit var mockTelemetryConfigurationMapper: EventMapper<TelemetryConfigurationEvent>
@@ -84,7 +88,8 @@ internal class RumEventMapperTest {
8488
resourceEventMapper = mockResourceEventMapper,
8589
errorEventMapper = mockErrorEventMapper,
8690
longTaskEventMapper = mockLongTaskEventMapper,
87-
vitalOperationStepEventMapper = mockVitalOperationStepEvent,
91+
vitalOperationStepEventMapper = mockVitalOperationStepEventMapper,
92+
vitalAppLaunchEventMapper = mockVitalAppLaunchEventMapper,
8893
telemetryConfigurationMapper = mockTelemetryConfigurationMapper,
8994
internalLogger = mockInternalLogger
9095
)
@@ -178,10 +183,24 @@ internal class RumEventMapperTest {
178183
}
179184

180185
@Test
181-
fun `M map the bundled event W map { VitalEvent }`(forge: Forge) {
186+
fun `M map the bundled event W map { VitalOperationStepEvent }`(forge: Forge) {
182187
// GIVEN
183188
val fakeRumEvent = forge.getForgery<RumVitalOperationStepEvent>()
184-
whenever(mockVitalOperationStepEvent.map(fakeRumEvent)).thenReturn(fakeRumEvent)
189+
whenever(mockVitalOperationStepEventMapper.map(fakeRumEvent)).thenReturn(fakeRumEvent)
190+
191+
// WHEN
192+
val mappedRumEvent = testedRumEventMapper.map(fakeRumEvent)
193+
194+
// THEN
195+
assertThat(mappedRumEvent).isNotNull
196+
assertThat(mappedRumEvent).isEqualTo(fakeRumEvent)
197+
}
198+
199+
@Test
200+
fun `M map the bundled event W map { VitalAppLaunchEvent }`(forge: Forge) {
201+
// GIVEN
202+
val fakeRumEvent = forge.getForgery<RumVitalAppLaunchEvent>()
203+
whenever(mockVitalAppLaunchEventMapper.map(fakeRumEvent)).thenReturn(fakeRumEvent)
185204

186205
// WHEN
187206
val mappedRumEvent = testedRumEventMapper.map(fakeRumEvent)
@@ -407,10 +426,10 @@ internal class RumEventMapperTest {
407426
}
408427

409428
@Test
410-
fun `M return null event W map returns null object { VitalEvent }`(forge: Forge) {
429+
fun `M return null event W map returns null object { VitalOperationStepEvent }`(forge: Forge) {
411430
// GIVEN
412431
val fakeRumEvent = forge.getForgery<RumVitalOperationStepEvent>()
413-
whenever(mockVitalOperationStepEvent.map(fakeRumEvent))
432+
whenever(mockVitalOperationStepEventMapper.map(fakeRumEvent))
414433
.thenReturn(null)
415434

416435
// WHEN
@@ -426,6 +445,25 @@ internal class RumEventMapperTest {
426445
)
427446
}
428447

448+
@Test
449+
fun `M return null event W map returns null object { VitalAppLaunchEvent }`(forge: Forge) {
450+
// GIVEN
451+
val fakeRumEvent = forge.getForgery<RumVitalAppLaunchEvent>()
452+
whenever(mockVitalAppLaunchEventMapper.map(fakeRumEvent))
453+
.thenReturn(null)
454+
455+
// WHEN
456+
val mappedRumEvent = testedRumEventMapper.map(fakeRumEvent)
457+
458+
// THEN
459+
assertThat(mappedRumEvent).isNull()
460+
mockInternalLogger.verifyLog(
461+
InternalLogger.Level.INFO,
462+
InternalLogger.Target.USER,
463+
RumEventMapper.EVENT_NULL_WARNING_MESSAGE.format(Locale.US, fakeRumEvent)
464+
)
465+
}
466+
429467
@Test
430468
fun `M use the original event W map returns different object { ViewEvent }`(forge: Forge) {
431469
// GIVEN
@@ -531,10 +569,10 @@ internal class RumEventMapperTest {
531569
}
532570

533571
@Test
534-
fun `M return null event W map returns different object { VitalEvent }`(forge: Forge) {
572+
fun `M return null event W map returns different object { VitalOperationStepEvent }`(forge: Forge) {
535573
// GIVEN
536574
val fakeRumEvent = forge.getForgery<RumVitalOperationStepEvent>()
537-
whenever(mockVitalOperationStepEvent.map(fakeRumEvent))
575+
whenever(mockVitalOperationStepEventMapper.map(fakeRumEvent))
538576
.thenReturn(forge.getForgery())
539577

540578
// WHEN
@@ -550,6 +588,25 @@ internal class RumEventMapperTest {
550588
)
551589
}
552590

591+
@Test
592+
fun `M return null event W map returns different object { VitalAppLaunchEvent }`(forge: Forge) {
593+
// GIVEN
594+
val fakeRumEvent = forge.getForgery<RumVitalAppLaunchEvent>()
595+
whenever(mockVitalAppLaunchEventMapper.map(fakeRumEvent))
596+
.thenReturn(forge.getForgery())
597+
598+
// WHEN
599+
val mappedRumEvent = testedRumEventMapper.map(fakeRumEvent)
600+
601+
// THEN
602+
assertThat(mappedRumEvent).isNull()
603+
mockInternalLogger.verifyLog(
604+
InternalLogger.Level.WARN,
605+
InternalLogger.Target.USER,
606+
RumEventMapper.NOT_SAME_EVENT_INSTANCE_WARNING_MESSAGE.format(Locale.US, fakeRumEvent)
607+
)
608+
}
609+
553610
@Test
554611
fun `M use the original event W map returns a copy { ViewEvent }`(forge: Forge) {
555612
// GIVEN
@@ -654,10 +711,10 @@ internal class RumEventMapperTest {
654711
}
655712

656713
@Test
657-
fun `M return null event W map returns a copy { VitalEvent }`(forge: Forge) {
714+
fun `M return null event W map returns a copy { VitalOperationStepEvent }`(forge: Forge) {
658715
// GIVEN
659716
val fakeRumEvent = forge.getForgery<RumVitalOperationStepEvent>()
660-
whenever(mockVitalOperationStepEvent.map(fakeRumEvent))
717+
whenever(mockVitalOperationStepEventMapper.map(fakeRumEvent))
661718
.thenReturn(fakeRumEvent.copy())
662719

663720
// WHEN
@@ -672,4 +729,23 @@ internal class RumEventMapperTest {
672729

673730
)
674731
}
732+
733+
@Test
734+
fun `M return null event W map returns a copy { VitalAppLaunchEvent }`(forge: Forge) {
735+
// GIVEN
736+
val fakeRumEvent = forge.getForgery<RumVitalAppLaunchEvent>()
737+
whenever(mockVitalAppLaunchEventMapper.map(fakeRumEvent))
738+
.thenReturn(fakeRumEvent.copy())
739+
740+
// WHEN
741+
val mappedRumEvent = testedRumEventMapper.map(fakeRumEvent)
742+
743+
// THEN
744+
assertThat(mappedRumEvent).isNull()
745+
mockInternalLogger.verifyLog(
746+
InternalLogger.Level.WARN,
747+
InternalLogger.Target.USER,
748+
RumEventMapper.NOT_SAME_EVENT_INSTANCE_WARNING_MESSAGE.format(Locale.US, fakeRumEvent)
749+
)
750+
}
675751
}

features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/ConfigurationRumForgeryFactory.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ internal class ConfigurationRumForgeryFactory :
4646
errorEventMapper = mock(),
4747
longTaskEventMapper = mock(),
4848
vitalOperationStepEventMapper = mock(),
49+
vitalAppLaunchEventMapper = mock(),
4950
telemetryConfigurationMapper = mock(),
5051
longTaskTrackingStrategy = mock(),
5152
backgroundEventTracking = forge.aBool(),

sample/benchmark/src/main/java/com/datadog/benchmark/sample/DatadogFeaturesInitializer.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,16 @@ internal class DatadogFeaturesInitializer @Inject constructor(
135135
event.context?.additionalProperties?.put(ATTR_IS_MAPPED, true)
136136
event
137137
}
138-
setVitalOperationStepEventMapper { event ->
139-
event.context?.additionalProperties?.put(ATTR_IS_MAPPED, true)
140-
event
141-
}
138+
setVitalEventMapper(
139+
vitalOperationStepEventMapper = { event ->
140+
event.context?.additionalProperties?.put(ATTR_IS_MAPPED, true)
141+
event
142+
},
143+
vitalAppLaunchEventMapper = { event ->
144+
event.context?.additionalProperties?.put(ATTR_IS_MAPPED, true)
145+
event
146+
}
147+
)
142148
enableComposeActionTracking()
143149
}.build()
144150
}

0 commit comments

Comments
 (0)