From 1516d9c318710becac6188c9d545b9770d55c391 Mon Sep 17 00:00:00 2001 From: HARSHIT SINHA <47042785+hsinha610@users.noreply.github.com> Date: Tue, 30 Jul 2024 16:35:57 +0530 Subject: [PATCH 1/8] chore: Used Maps in place of Lists --- .../com/featurevisor/sdk/DatafileReader.kt | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/com/featurevisor/sdk/DatafileReader.kt b/src/main/kotlin/com/featurevisor/sdk/DatafileReader.kt index eef4c77..2698b76 100644 --- a/src/main/kotlin/com/featurevisor/sdk/DatafileReader.kt +++ b/src/main/kotlin/com/featurevisor/sdk/DatafileReader.kt @@ -1,22 +1,16 @@ package com.featurevisor.sdk -import com.featurevisor.types.Attribute -import com.featurevisor.types.AttributeKey -import com.featurevisor.types.DatafileContent -import com.featurevisor.types.Feature -import com.featurevisor.types.FeatureKey -import com.featurevisor.types.Segment -import com.featurevisor.types.SegmentKey - -class DatafileReader constructor( - datafileJson: DatafileContent, +import com.featurevisor.types.* + +class DatafileReader( + datafileContent: DatafileContent, ) { - private val schemaVersion: String = datafileJson.schemaVersion - private val revision: String = datafileJson.revision - private val attributes: List = datafileJson.attributes - private val segments: List = datafileJson.segments - private val features: List = datafileJson.features + private val schemaVersion: String = datafileContent.schemaVersion + private val revision: String = datafileContent.revision + private val attributes: Map = datafileContent.attributes.associateBy { it.key } + private val segments: Map = datafileContent.segments.associateBy { it.key } + private val features: Map = datafileContent.features.associateBy { it.key } fun getRevision(): String { return revision @@ -26,19 +20,19 @@ class DatafileReader constructor( return schemaVersion } - fun getAllAttributes(): List { + fun getAllAttributes(): Map { return attributes } fun getAttribute(attributeKey: AttributeKey): Attribute? { - return attributes.find { attribute -> attribute.key == attributeKey } + return attributes[attributeKey] } fun getSegment(segmentKey: SegmentKey): Segment? { - return segments.find { segment -> segment.key == segmentKey } + return segments[segmentKey] } fun getFeature(featureKey: FeatureKey): Feature? { - return features.find { feature -> feature.key == featureKey } + return features[featureKey] } } From a0cfb96dcdaf41219de7af7e61cf6f39f688f93a Mon Sep 17 00:00:00 2001 From: HARSHIT SINHA <47042785+hsinha610@users.noreply.github.com> Date: Tue, 30 Jul 2024 16:37:05 +0530 Subject: [PATCH 2/8] Update DatafileReaderTest.kt --- src/test/kotlin/com/featurevisor/sdk/DatafileReaderTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/com/featurevisor/sdk/DatafileReaderTest.kt b/src/test/kotlin/com/featurevisor/sdk/DatafileReaderTest.kt index 9a146e0..3882266 100644 --- a/src/test/kotlin/com/featurevisor/sdk/DatafileReaderTest.kt +++ b/src/test/kotlin/com/featurevisor/sdk/DatafileReaderTest.kt @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test class DatafileReaderTest { private val systemUnderTest = DatafileReader( - datafileJson = DatafileContentFactory.get() + datafileContent = DatafileContentFactory.get() ) @Test From c216636525dbe4a863b2ae1cc126af717aa55a05 Mon Sep 17 00:00:00 2001 From: HARSHIT SINHA <47042785+hsinha610@users.noreply.github.com> Date: Tue, 30 Jul 2024 16:37:47 +0530 Subject: [PATCH 3/8] Update Instance+Activation.kt --- src/main/kotlin/com/featurevisor/sdk/Instance+Activation.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/featurevisor/sdk/Instance+Activation.kt b/src/main/kotlin/com/featurevisor/sdk/Instance+Activation.kt index 3454b0b..d1336d7 100644 --- a/src/main/kotlin/com/featurevisor/sdk/Instance+Activation.kt +++ b/src/main/kotlin/com/featurevisor/sdk/Instance+Activation.kt @@ -12,7 +12,7 @@ fun FeaturevisorInstance.activate(featureKey: FeatureKey, context: Context = emp val finalContext = interceptContext?.invoke(context) ?: context val captureContext = mutableMapOf() val attributesForCapturing = datafileReader.getAllAttributes() - .filter { it.capture == true } + .filter { it.value.capture == true } attributesForCapturing.forEach { attribute -> finalContext[attribute.key]?.let { From 509ce2d9001003605f574468c30eb461e9798fa5 Mon Sep 17 00:00:00 2001 From: HARSHIT SINHA <47042785+hsinha610@users.noreply.github.com> Date: Tue, 30 Jul 2024 16:58:15 +0530 Subject: [PATCH 4/8] Update Instance.kt --- .../kotlin/com/featurevisor/sdk/Instance.kt | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/com/featurevisor/sdk/Instance.kt b/src/main/kotlin/com/featurevisor/sdk/Instance.kt index d833849..1533ff9 100644 --- a/src/main/kotlin/com/featurevisor/sdk/Instance.kt +++ b/src/main/kotlin/com/featurevisor/sdk/Instance.kt @@ -6,6 +6,8 @@ package com.featurevisor.sdk import com.featurevisor.sdk.FeaturevisorError.MissingDatafileOptions import com.featurevisor.types.* import com.featurevisor.types.EventName.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json @@ -16,7 +18,7 @@ typealias InterceptContext = (Context) -> Context typealias DatafileFetchHandler = (datafileUrl: String) -> Result var emptyDatafile = DatafileContent( - schemaVersion = "1", + schemaVersion = "1", revision = "unknown", attributes = emptyList(), segments = emptyList(), @@ -56,6 +58,8 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) { internal var configureBucketKey = options.configureBucketKey internal var configureBucketValue = options.configureBucketValue internal var refreshJob: Job? = null + private var fetchJob: Job? = null + internal val coroutineScope = CoroutineScope(Dispatchers.IO) init { with(options) { @@ -99,17 +103,26 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) { } datafileUrl != null -> { - datafileReader = DatafileReader(options.datafile?: emptyDatafile) - fetchDatafileContent(datafileUrl, handleDatafileFetch) { result -> - if (result.isSuccess) { - datafileReader = DatafileReader(result.getOrThrow()) + datafileReader = DatafileReader(options.datafile ?: emptyDatafile) + fetchJob = fetchDatafileContentJob( + url = datafileUrl, + logger = logger, + handleDatafileFetch = handleDatafileFetch, + retryCount = retryCount.coerceAtLeast(1), + retryInterval = retryInterval.coerceAtLeast(0), + coroutineScope = coroutineScope, + ) { result -> + result.onSuccess { fetchResult -> + val datafileContent = fetchResult.datafileContent + datafileReader = DatafileReader(datafileContent) statuses.ready = true - emitter.emit(READY, result.getOrThrow()) + emitter.emit(READY, datafileContent, fetchResult.responseBodyString) if (refreshInterval != null) startRefreshing() - } else { - logger?.error("Failed to fetch datafile: $result") + }.onFailure { error -> + logger?.error("Failed to fetch datafile: $error") emitter.emit(ERROR) } + cancelFetchRetry() } } @@ -118,6 +131,12 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) { } } + // Provide a mechanism to cancel the fetch job if retry count is more than one + fun cancelFetchRetry() { + fetchJob?.cancel() + fetchJob = null + } + fun setLogLevels(levels: List) { this.logger?.setLevels(levels) } @@ -126,14 +145,14 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) { val data = datafileJSON.toByteArray(Charsets.UTF_8) try { val datafileContent = Json.decodeFromString(String(data)) - datafileReader = DatafileReader(datafileJson = datafileContent) + datafileReader = DatafileReader(datafileContent = datafileContent) } catch (e: Exception) { logger?.error("could not parse datafile", mapOf("error" to e)) } } fun setDatafile(datafileContent: DatafileContent) { - datafileReader = DatafileReader(datafileJson = datafileContent) + datafileReader = DatafileReader(datafileContent = datafileContent) } fun setStickyFeatures(stickyFeatures: StickyFeatures?) { From 0e9845183c62c2e888366dc91bebc2049607323e Mon Sep 17 00:00:00 2001 From: HARSHIT SINHA <47042785+hsinha610@users.noreply.github.com> Date: Tue, 30 Jul 2024 17:00:41 +0530 Subject: [PATCH 5/8] Update Instance.kt --- .../kotlin/com/featurevisor/sdk/Instance.kt | 35 +++++-------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/com/featurevisor/sdk/Instance.kt b/src/main/kotlin/com/featurevisor/sdk/Instance.kt index 1533ff9..af7ff2a 100644 --- a/src/main/kotlin/com/featurevisor/sdk/Instance.kt +++ b/src/main/kotlin/com/featurevisor/sdk/Instance.kt @@ -6,8 +6,6 @@ package com.featurevisor.sdk import com.featurevisor.sdk.FeaturevisorError.MissingDatafileOptions import com.featurevisor.types.* import com.featurevisor.types.EventName.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json @@ -18,7 +16,7 @@ typealias InterceptContext = (Context) -> Context typealias DatafileFetchHandler = (datafileUrl: String) -> Result var emptyDatafile = DatafileContent( - schemaVersion = "1", + schemaVersion = "1", revision = "unknown", attributes = emptyList(), segments = emptyList(), @@ -58,8 +56,6 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) { internal var configureBucketKey = options.configureBucketKey internal var configureBucketValue = options.configureBucketValue internal var refreshJob: Job? = null - private var fetchJob: Job? = null - internal val coroutineScope = CoroutineScope(Dispatchers.IO) init { with(options) { @@ -103,26 +99,17 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) { } datafileUrl != null -> { - datafileReader = DatafileReader(options.datafile ?: emptyDatafile) - fetchJob = fetchDatafileContentJob( - url = datafileUrl, - logger = logger, - handleDatafileFetch = handleDatafileFetch, - retryCount = retryCount.coerceAtLeast(1), - retryInterval = retryInterval.coerceAtLeast(0), - coroutineScope = coroutineScope, - ) { result -> - result.onSuccess { fetchResult -> - val datafileContent = fetchResult.datafileContent - datafileReader = DatafileReader(datafileContent) + datafileReader = DatafileReader(options.datafile?: emptyDatafile) + fetchDatafileContent(datafileUrl, handleDatafileFetch) { result -> + if (result.isSuccess) { + datafileReader = DatafileReader(result.getOrThrow()) statuses.ready = true - emitter.emit(READY, datafileContent, fetchResult.responseBodyString) + emitter.emit(READY, result.getOrThrow()) if (refreshInterval != null) startRefreshing() - }.onFailure { error -> - logger?.error("Failed to fetch datafile: $error") + } else { + logger?.error("Failed to fetch datafile: $result") emitter.emit(ERROR) } - cancelFetchRetry() } } @@ -131,12 +118,6 @@ class FeaturevisorInstance private constructor(options: InstanceOptions) { } } - // Provide a mechanism to cancel the fetch job if retry count is more than one - fun cancelFetchRetry() { - fetchJob?.cancel() - fetchJob = null - } - fun setLogLevels(levels: List) { this.logger?.setLevels(levels) } From 9ac01753fa4e8453836e1a53cffbd3d83910537e Mon Sep 17 00:00:00 2001 From: HARSHIT SINHA <47042785+hsinha610@users.noreply.github.com> Date: Tue, 30 Jul 2024 17:02:00 +0530 Subject: [PATCH 6/8] Update Instance+Activation.kt --- src/main/kotlin/com/featurevisor/sdk/Instance+Activation.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/featurevisor/sdk/Instance+Activation.kt b/src/main/kotlin/com/featurevisor/sdk/Instance+Activation.kt index d1336d7..f2ba1da 100644 --- a/src/main/kotlin/com/featurevisor/sdk/Instance+Activation.kt +++ b/src/main/kotlin/com/featurevisor/sdk/Instance+Activation.kt @@ -14,7 +14,7 @@ fun FeaturevisorInstance.activate(featureKey: FeatureKey, context: Context = emp val attributesForCapturing = datafileReader.getAllAttributes() .filter { it.value.capture == true } - attributesForCapturing.forEach { attribute -> + attributesForCapturing.forEach { (_, attribute) -> finalContext[attribute.key]?.let { captureContext[attribute.key] = it } From 3733bf0c3b907ee74892e6ee5b0dee017224bbf3 Mon Sep 17 00:00:00 2001 From: HARSHIT SINHA <47042785+hsinha610@users.noreply.github.com> Date: Tue, 30 Jul 2024 17:07:36 +0530 Subject: [PATCH 7/8] Update DatafileReader.kt --- .../com/featurevisor/sdk/DatafileReader.kt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/featurevisor/sdk/DatafileReader.kt b/src/main/kotlin/com/featurevisor/sdk/DatafileReader.kt index 2698b76..b39d6bd 100644 --- a/src/main/kotlin/com/featurevisor/sdk/DatafileReader.kt +++ b/src/main/kotlin/com/featurevisor/sdk/DatafileReader.kt @@ -1,8 +1,14 @@ package com.featurevisor.sdk -import com.featurevisor.types.* - -class DatafileReader( +import com.featurevisor.types.Attribute +import com.featurevisor.types.AttributeKey +import com.featurevisor.types.DatafileContent +import com.featurevisor.types.Feature +import com.featurevisor.types.FeatureKey +import com.featurevisor.types.Segment +import com.featurevisor.types.SegmentKey + +class DatafileReader constructor( datafileContent: DatafileContent, ) { @@ -20,8 +26,8 @@ class DatafileReader( return schemaVersion } - fun getAllAttributes(): Map { - return attributes + fun getAllAttributes(): List { + return attributes.values.toList() } fun getAttribute(attributeKey: AttributeKey): Attribute? { From bd6e5b4333aa7cd2413f2abeb5147b29b22e979c Mon Sep 17 00:00:00 2001 From: HARSHIT SINHA <47042785+hsinha610@users.noreply.github.com> Date: Tue, 30 Jul 2024 17:08:13 +0530 Subject: [PATCH 8/8] Update Instance+Activation.kt --- src/main/kotlin/com/featurevisor/sdk/Instance+Activation.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/featurevisor/sdk/Instance+Activation.kt b/src/main/kotlin/com/featurevisor/sdk/Instance+Activation.kt index f2ba1da..3454b0b 100644 --- a/src/main/kotlin/com/featurevisor/sdk/Instance+Activation.kt +++ b/src/main/kotlin/com/featurevisor/sdk/Instance+Activation.kt @@ -12,9 +12,9 @@ fun FeaturevisorInstance.activate(featureKey: FeatureKey, context: Context = emp val finalContext = interceptContext?.invoke(context) ?: context val captureContext = mutableMapOf() val attributesForCapturing = datafileReader.getAllAttributes() - .filter { it.value.capture == true } + .filter { it.capture == true } - attributesForCapturing.forEach { (_, attribute) -> + attributesForCapturing.forEach { attribute -> finalContext[attribute.key]?.let { captureContext[attribute.key] = it }