diff --git a/TopsortAnalytics/src/main/java/com/topsort/analytics/banners/BannerConfig.kt b/TopsortAnalytics/src/main/java/com/topsort/analytics/banners/BannerConfig.kt new file mode 100644 index 0000000..1feb79e --- /dev/null +++ b/TopsortAnalytics/src/main/java/com/topsort/analytics/banners/BannerConfig.kt @@ -0,0 +1,82 @@ +package com.topsort.analytics.banners + +/** + * Class that handles different type of Banner configurations + */ +sealed class BannerConfig private constructor() { + + /** + * Banner configuration for landing page banners + * + * @property slotId id of the banner slot + * @property ids ids of the entities that are competing for the banner + * @property device can be "desktop" or "mobile" + * @property geoTargeting optional location for geo-targeted banners + */ + data class LandingPage( + val slotId: String, + val ids: List, + val device: String? = null, + val geoTargeting: String? = null + ) : BannerConfig() + + /** + * Banner configuration for single category banners + * + * @property slotId id of the banner slot + * @property category category for the banner + * @property device can be "desktop" or "mobile" + * @property geoTargeting optional location for geo-targeted banners + */ + data class CategorySingle( + val slotId: String, + val category: String, + val device: String? = null, + val geoTargeting: String? = null + ) : BannerConfig() + + /** + * Banner config for multiple category banners + * + * @property slotId id of the banner slot + * @property categories list of categories for the competing banners + * @property device can be "desktop" or "mobile" + * @property geoTargeting optional location for geo-targeted banners + */ + data class CategoryMultiple( + val slotId: String, + val categories: List, + val device: String? = null, + val geoTargeting: String? = null, + ) : BannerConfig() + + /** + * Banner configuration for category disjunctions banners + * + * @property slotId id of the banner slot + * @property disjunctions category disjunctions for the competing banners + * @property device can be "desktop" or "mobile" + * @property geoTargeting optional location for geo-targeted banners + */ + data class CategoryDisjunctions( + val slotId: String, + val disjunctions: List>, + val device: String? = null, + val geoTargeting: String? = null, + ) : BannerConfig() + + /** + * Banner configuration for keyword banners + * + * @property slotId id of the banner slot + * @property keyword keyword for the competing banners + * @property device can be "desktop" or "mobile" + * @property geoTargeting optional location for geo-targeted banners + */ + data class Keyword( + val slotId: String, + val keyword: String, + val device: String? = null, + val geoTargeting: String? = null, + ) : BannerConfig() +} \ No newline at end of file diff --git a/TopsortAnalytics/src/main/java/com/topsort/analytics/banners/BannerResponse.kt b/TopsortAnalytics/src/main/java/com/topsort/analytics/banners/BannerResponse.kt new file mode 100644 index 0000000..096c020 --- /dev/null +++ b/TopsortAnalytics/src/main/java/com/topsort/analytics/banners/BannerResponse.kt @@ -0,0 +1,18 @@ +package com.topsort.analytics.banners + +import com.topsort.analytics.model.auctions.EntityType + +/** + * Response for a single slot banner auction + * + * @property id id of the winning entity + * @property type type of the winning entity + * @property url url of the banner to show + * @property resolvedBidId id for tracking the auction result on events + */ +data class BannerResponse( + val id: String, + val type: EntityType, + val url: String, + val resolvedBidId: String, +) {} \ No newline at end of file diff --git a/TopsortAnalytics/src/main/java/com/topsort/analytics/banners/run.kt b/TopsortAnalytics/src/main/java/com/topsort/analytics/banners/run.kt new file mode 100644 index 0000000..8570923 --- /dev/null +++ b/TopsortAnalytics/src/main/java/com/topsort/analytics/banners/run.kt @@ -0,0 +1,87 @@ +package com.topsort.analytics.banners + +import com.topsort.analytics.model.auctions.Auction +import com.topsort.analytics.model.auctions.AuctionRequest +import com.topsort.analytics.service.TopsortAuctionsHttpService + +/** + * Run a banner auction with a single slot + * + * @param config the banner configuration that specifies which kind of banner auction to run + * @return A BannerResponse if the auction successfully returned a winner or null if not. + */ +fun runBannerAuction(config: BannerConfig): BannerResponse? { + val auction = buildBannerAuction(config) + val request = AuctionRequest(listOf(auction)) + val response = TopsortAuctionsHttpService.runAuctions(request) + if ((response?.results?.isNotEmpty() == true)) { + if (response.results[0].winners.isNotEmpty()) { + val winner = response.results[0].winners[0] + return BannerResponse( + id = winner.id, + url = winner.asset!!.url, + type = winner.type, + resolvedBidId = winner.resolvedBidId + ) + } + } + return null +} + +/** + * Builds a low-leve Auction object to be run with TopsortAuctionHttpService. + * + * Generally, you shouldn't be calling this function yourself and you should use runBannerAuction instead. + * + * @param config the banner configuration that specifies which kind of banner auction to run + * @return an Auction object + */ +fun buildBannerAuction(config: BannerConfig): Auction { + when (config) { + is BannerConfig.LandingPage -> { + return Auction.Factory.buildBannerAuctionLandingPage( + 1, + config.slotId, + config.ids, + config.device, + config.geoTargeting + ) + } + + is BannerConfig.CategorySingle -> { + return Auction.Factory.buildBannerAuctionCategorySingle( + 1, + config.slotId, + config.category, + config.device, + config.geoTargeting + ) + } + + is BannerConfig.CategoryMultiple -> { + return Auction.Factory.buildBannerAuctionCategoryMultiple( + 1, + config.slotId, + config.categories, + config.device, + config.geoTargeting + ) + } + + is BannerConfig.CategoryDisjunctions -> { + return Auction.Factory.buildBannerAuctionCategoryDisjunctions( + 1, config.slotId, config.disjunctions, config.device, config.geoTargeting + ) + } + + is BannerConfig.Keyword -> { + return Auction.Factory.buildBannerAuctionKeywords( + 1, + config.slotId, + config.keyword, + config.device, + config.geoTargeting + ) + } + } +} \ No newline at end of file diff --git a/TopsortAnalytics/src/main/java/com/topsort/analytics/model/auctions/AuctionResponse.kt b/TopsortAnalytics/src/main/java/com/topsort/analytics/model/auctions/AuctionResponse.kt index 3f34d01..fafbe9e 100644 --- a/TopsortAnalytics/src/main/java/com/topsort/analytics/model/auctions/AuctionResponse.kt +++ b/TopsortAnalytics/src/main/java/com/topsort/analytics/model/auctions/AuctionResponse.kt @@ -3,11 +3,11 @@ package com.topsort.analytics.model.auctions import org.json.JSONObject data class AuctionResponse private constructor( - val results : List? = null, + val results: List, ) { companion object { fun fromJson(json: String?): AuctionResponse? { - if(json == null) return null + if (json == null) return null val array = JSONObject(json).getJSONArray("results") val results = (0 until array.length()).map { AuctionResponseItem.fromJsonObject(array.getJSONObject(it)) @@ -20,11 +20,11 @@ data class AuctionResponse private constructor( } data class AuctionResponseItem( - val resultType : String, - val winners : List? = null, + val resultType: String, + val winners: List, val error: Boolean, ) { - companion object{ + companion object { fun fromJsonObject(json: JSONObject): AuctionResponseItem { val array = json.getJSONArray("winners") val winners = (0 until array.length()).map { @@ -40,20 +40,49 @@ data class AuctionResponse private constructor( } data class AuctionWinnerItem( - val rank : Int, - val type : String, - val id : String, + val rank: Int, + val type: EntityType, + val id: String, val resolvedBidId: String, - ){ - companion object{ + val asset: Asset? = null, + ) { + companion object { fun fromJsonObject(json: JSONObject): AuctionWinnerItem { return AuctionWinnerItem( - rank = json.getInt("rank"), - type = json.getString("type"), - id = json.getString("id"), - resolvedBidId = json.getString("resolvedBidId"), + rank = json.getInt("rank"), + type = EntityType.fromValue(json.getString("type")), + id = json.getString("id"), + resolvedBidId = json.getString("resolvedBidId"), + asset = Asset.fromJsonObject(json), ) } } } + + data class Asset(val url: String) { + companion object { + fun fromJsonObject(json: JSONObject): Asset? { + val asset = json.optJSONObject("asset") ?: return null + val url = asset.getString("url") + return Asset(url = url) + } + } + } +} + +enum class EntityType { + PRODUCT, + VENDOR, + BRAND, + URL; + + companion object { + fun fromValue(value: String): EntityType = when (value) { + "product" -> PRODUCT + "vendor" -> VENDOR + "brand" -> BRAND + "url" -> URL + else -> throw IllegalArgumentException() + } + } }