Skip to content

Commit 5f14e5d

Browse files
runningcodeclaude
andauthored
feat(distribution): Add install_groups support (#5062)
Add installGroupsOverride parameter to UpdateCheckParams and installGroups property to UpdateInfo for the Build Distribution SDK, matching the iOS implementation. Co-authored-by: Claude Opus 4.5 <[email protected]>
1 parent b193867 commit 5f14e5d

File tree

6 files changed

+142
-4
lines changed

6 files changed

+142
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Features
66

7+
- Add `installGroupsOverride` parameter and `installGroups` property to Build Distribution SDK ([#5062](https://github.com/getsentry/sentry-java/pull/5062))
78
- Update Android targetSdk to API 36 (Android 16) ([#5016](https://github.com/getsentry/sentry-java/pull/5016))
89

910
### Internal

sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionHttpClient.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ internal class DistributionHttpClient(private val options: SentryOptions) {
2727
val versionCode: Long,
2828
val versionName: String,
2929
val buildConfiguration: String,
30+
val installGroupsOverride: List<String>? = null,
3031
)
3132

3233
/**
@@ -58,6 +59,9 @@ internal class DistributionHttpClient(private val options: SentryOptions) {
5859
append("&build_number=${URLEncoder.encode(params.versionCode.toString(), "UTF-8")}")
5960
append("&build_version=${URLEncoder.encode(params.versionName, "UTF-8")}")
6061
append("&build_configuration=${URLEncoder.encode(params.buildConfiguration, "UTF-8")}")
62+
params.installGroupsOverride?.forEach { group ->
63+
append("&install_groups=${URLEncoder.encode(group, "UTF-8")}")
64+
}
6165
}
6266
val url = URL(urlString)
6367

sentry-android-distribution/src/main/java/io/sentry/android/distribution/UpdateResponseParser.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ internal class UpdateResponseParser(private val options: SentryOptions) {
5757
val downloadUrl = json.optString("download_url", "")
5858
val appName = json.optString("app_name", "")
5959
val createdDate = json.optString("created_date", "")
60+
val installGroups = parseInstallGroups(json)
6061

6162
// Validate required fields (optString returns "null" for null values)
6263
val missingFields = mutableListOf<String>()
@@ -77,6 +78,26 @@ internal class UpdateResponseParser(private val options: SentryOptions) {
7778
)
7879
}
7980

80-
return UpdateInfo(id, buildVersion, buildNumber, downloadUrl, appName, createdDate)
81+
return UpdateInfo(
82+
id,
83+
buildVersion,
84+
buildNumber,
85+
downloadUrl,
86+
appName,
87+
createdDate,
88+
installGroups,
89+
)
90+
}
91+
92+
private fun parseInstallGroups(json: JSONObject): List<String>? {
93+
val installGroupsArray = json.optJSONArray("install_groups") ?: return null
94+
val installGroups = mutableListOf<String>()
95+
for (i in 0 until installGroupsArray.length()) {
96+
val group = installGroupsArray.optString(i)
97+
if (group.isNotEmpty() && group != "null") {
98+
installGroups.add(group)
99+
}
100+
}
101+
return if (installGroups.isEmpty()) null else installGroups
81102
}
82103
}

sentry-android-distribution/src/test/java/io/sentry/android/distribution/UpdateResponseParserTest.kt

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ class UpdateResponseParserTest {
3232
"build_number": 42,
3333
"download_url": "https://example.com/download",
3434
"app_name": "Test App",
35-
"created_date": "2023-10-01T00:00:00Z"
35+
"created_date": "2023-10-01T00:00:00Z",
36+
"install_groups": ["beta", "internal"]
3637
},
3738
"current": null
3839
}
@@ -49,6 +50,7 @@ class UpdateResponseParserTest {
4950
assertEquals("https://example.com/download", updateInfo.downloadUrl)
5051
assertEquals("Test App", updateInfo.appName)
5152
assertEquals("2023-10-01T00:00:00Z", updateInfo.createdDate)
53+
assertEquals(listOf("beta", "internal"), updateInfo.installGroups)
5254
}
5355

5456
@Test
@@ -355,4 +357,103 @@ class UpdateResponseParserTest {
355357
error.message.contains("Missing required fields in API response: id"),
356358
)
357359
}
360+
361+
@Test
362+
fun `parseResponse returns null installGroups when not present`() {
363+
val responseBody =
364+
"""
365+
{
366+
"update": {
367+
"id": "update-123",
368+
"build_version": "2.0.0",
369+
"build_number": 42,
370+
"download_url": "https://example.com/download",
371+
"app_name": "Test App",
372+
"created_date": "2023-10-01T00:00:00Z"
373+
}
374+
}
375+
"""
376+
.trimIndent()
377+
378+
val result = parser.parseResponse(200, responseBody)
379+
380+
assertTrue("Should return NewRelease", result is UpdateStatus.NewRelease)
381+
val updateInfo = (result as UpdateStatus.NewRelease).info
382+
assertEquals(null, updateInfo.installGroups)
383+
}
384+
385+
@Test
386+
fun `parseResponse returns null installGroups when array is empty`() {
387+
val responseBody =
388+
"""
389+
{
390+
"update": {
391+
"id": "update-123",
392+
"build_version": "2.0.0",
393+
"build_number": 42,
394+
"download_url": "https://example.com/download",
395+
"app_name": "Test App",
396+
"created_date": "2023-10-01T00:00:00Z",
397+
"install_groups": []
398+
}
399+
}
400+
"""
401+
.trimIndent()
402+
403+
val result = parser.parseResponse(200, responseBody)
404+
405+
assertTrue("Should return NewRelease", result is UpdateStatus.NewRelease)
406+
val updateInfo = (result as UpdateStatus.NewRelease).info
407+
assertEquals(null, updateInfo.installGroups)
408+
}
409+
410+
@Test
411+
fun `parseResponse returns null installGroups when array is null`() {
412+
val responseBody =
413+
"""
414+
{
415+
"update": {
416+
"id": "update-123",
417+
"build_version": "2.0.0",
418+
"build_number": 42,
419+
"download_url": "https://example.com/download",
420+
"app_name": "Test App",
421+
"created_date": "2023-10-01T00:00:00Z",
422+
"install_groups": null
423+
}
424+
}
425+
"""
426+
.trimIndent()
427+
428+
val result = parser.parseResponse(200, responseBody)
429+
430+
assertTrue("Should return NewRelease", result is UpdateStatus.NewRelease)
431+
val updateInfo = (result as UpdateStatus.NewRelease).info
432+
assertEquals(null, updateInfo.installGroups)
433+
}
434+
435+
@Test
436+
fun `parseResponse returns single installGroup`() {
437+
val responseBody =
438+
"""
439+
{
440+
"update": {
441+
"id": "update-123",
442+
"build_version": "2.0.0",
443+
"build_number": 42,
444+
"download_url": "https://example.com/download",
445+
"app_name": "Test App",
446+
"created_date": "2023-10-01T00:00:00Z",
447+
"install_groups": ["beta-testers"]
448+
}
449+
}
450+
"""
451+
.trimIndent()
452+
453+
val result = parser.parseResponse(200, responseBody)
454+
455+
assertTrue("Should return NewRelease", result is UpdateStatus.NewRelease)
456+
val updateInfo = (result as UpdateStatus.NewRelease).info
457+
assertEquals(listOf("beta-testers"), updateInfo.installGroups)
458+
}
358459
}

sentry/api/sentry.api

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4521,13 +4521,14 @@ public class io/sentry/UncaughtExceptionHandlerIntegration$UncaughtExceptionHint
45214521
}
45224522

45234523
public final class io/sentry/UpdateInfo {
4524-
public fun <init> (Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
4524+
public fun <init> (Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V
45254525
public fun getAppName ()Ljava/lang/String;
45264526
public fun getBuildNumber ()I
45274527
public fun getBuildVersion ()Ljava/lang/String;
45284528
public fun getCreatedDate ()Ljava/lang/String;
45294529
public fun getDownloadUrl ()Ljava/lang/String;
45304530
public fun getId ()Ljava/lang/String;
4531+
public fun getInstallGroups ()Ljava/util/List;
45314532
public fun toString ()Ljava/lang/String;
45324533
}
45334534

sentry/src/main/java/io/sentry/UpdateInfo.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry;
22

3+
import java.util.List;
34
import org.jetbrains.annotations.ApiStatus;
45
import org.jetbrains.annotations.NotNull;
56
import org.jetbrains.annotations.Nullable;
@@ -13,20 +14,23 @@ public final class UpdateInfo {
1314
private final @NotNull String downloadUrl;
1415
private final @NotNull String appName;
1516
private final @Nullable String createdDate;
17+
private final @Nullable List<String> installGroups;
1618

1719
public UpdateInfo(
1820
final @NotNull String id,
1921
final @NotNull String buildVersion,
2022
final int buildNumber,
2123
final @NotNull String downloadUrl,
2224
final @NotNull String appName,
23-
final @Nullable String createdDate) {
25+
final @Nullable String createdDate,
26+
final @Nullable List<String> installGroups) {
2427
this.id = id;
2528
this.buildVersion = buildVersion;
2629
this.buildNumber = buildNumber;
2730
this.downloadUrl = downloadUrl;
2831
this.appName = appName;
2932
this.createdDate = createdDate;
33+
this.installGroups = installGroups;
3034
}
3135

3236
public @NotNull String getId() {
@@ -53,6 +57,10 @@ public int getBuildNumber() {
5357
return createdDate;
5458
}
5559

60+
public @Nullable List<String> getInstallGroups() {
61+
return installGroups;
62+
}
63+
5664
@Override
5765
public String toString() {
5866
return "UpdateInfo{"
@@ -73,6 +81,8 @@ public String toString() {
7381
+ ", createdDate='"
7482
+ createdDate
7583
+ '\''
84+
+ ", installGroups="
85+
+ installGroups
7686
+ '}';
7787
}
7888
}

0 commit comments

Comments
 (0)