├── docs ├── .nojekyll ├── CNAME ├── .DS_Store ├── public │ └── .DS_Store ├── src │ ├── env.d.ts │ ├── assets │ │ └── ascend-logo.png │ ├── content │ │ └── docs │ │ │ ├── howto │ │ │ ├── assets │ │ │ │ ├── upload-csv.png │ │ │ │ ├── create-audience.png │ │ │ │ ├── variant-config.png │ │ │ │ ├── experiment-targeting.png │ │ │ │ └── create-first-experiment.png │ │ │ ├── create-first-experiment.mdx │ │ │ └── how-to-contribute.mdx │ │ │ ├── sdks │ │ │ ├── kotlin │ │ │ │ ├── installation.mdx │ │ │ │ ├── quick-start.mdx │ │ │ │ └── api.mdx │ │ │ ├── react-native │ │ │ │ ├── installation.mdx │ │ │ │ ├── quick-start.mdx │ │ │ │ └── api.mdx │ │ │ └── ios │ │ │ │ ├── installation.mdx │ │ │ │ ├── quick-start.mdx │ │ │ │ └── api.mdx │ │ │ ├── introduction │ │ │ ├── overview.mdx │ │ │ ├── getting-started.mdx │ │ │ └── why-use-ascend.mdx │ │ │ └── concepts │ │ │ └── overview.mdx │ ├── content.config.ts │ ├── pages │ │ └── index.astro │ └── styles │ │ ├── command-flag-table.css │ │ └── custom.css ├── assets │ └── ascend-logo.png ├── tsconfig.json ├── package.json ├── astro.config.mjs └── README.md ├── .gitignore ├── .github └── workflows │ └── deploy-gh-pages.yml ├── LICENSE ├── README.md ├── remove.sh └── startup.sh /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | ascend.dreamhorizon.org -------------------------------------------------------------------------------- /docs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dream-horizon-org/ascend/HEAD/docs/.DS_Store -------------------------------------------------------------------------------- /docs/public/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dream-horizon-org/ascend/HEAD/docs/public/.DS_Store -------------------------------------------------------------------------------- /docs/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /docs/assets/ascend-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dream-horizon-org/ascend/HEAD/docs/assets/ascend-logo.png -------------------------------------------------------------------------------- /docs/src/assets/ascend-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dream-horizon-org/ascend/HEAD/docs/src/assets/ascend-logo.png -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "baseUrl": "ascend.dreamhorizon.org" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/src/content/docs/howto/assets/upload-csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dream-horizon-org/ascend/HEAD/docs/src/content/docs/howto/assets/upload-csv.png -------------------------------------------------------------------------------- /docs/src/content/docs/howto/assets/create-audience.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dream-horizon-org/ascend/HEAD/docs/src/content/docs/howto/assets/create-audience.png -------------------------------------------------------------------------------- /docs/src/content/docs/howto/assets/variant-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dream-horizon-org/ascend/HEAD/docs/src/content/docs/howto/assets/variant-config.png -------------------------------------------------------------------------------- /docs/src/content/docs/howto/assets/experiment-targeting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dream-horizon-org/ascend/HEAD/docs/src/content/docs/howto/assets/experiment-targeting.png -------------------------------------------------------------------------------- /docs/src/content/docs/howto/assets/create-first-experiment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dream-horizon-org/ascend/HEAD/docs/src/content/docs/howto/assets/create-first-experiment.png -------------------------------------------------------------------------------- /docs/src/content.config.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection } from "astro:content"; 2 | import { docsSchema } from "@astrojs/starlight/schema"; 3 | 4 | export const collections = { 5 | docs: defineCollection({ schema: docsSchema() }), 6 | }; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.astro/* 2 | **/.vscode/* 3 | **/.idea/* 4 | **/.env* 5 | 6 | # dependencies 7 | **/node_modules/* 8 | **/dist/* 9 | **/build/* 10 | **/target/* 11 | 12 | # logs 13 | **/out/* 14 | **/logs/* 15 | 16 | # temp 17 | **/tmp/* 18 | **/temp/* 19 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ascend-docs", 3 | "private": true, 4 | "type": "module", 5 | "version": "1.0.0", 6 | "description": "Documentation for Ascend - Experimentation Platform", 7 | "scripts": { 8 | "dev": "astro dev", 9 | "build": "astro build", 10 | "preview": "astro preview" 11 | }, 12 | "dependencies": { 13 | "@astrojs/mdx": "^4.3.7", 14 | "@astrojs/starlight": "^0.36.1", 15 | "astro": "^5.14.8", 16 | "sharp": "^0.34.4" 17 | }, 18 | "devDependencies": { 19 | "typescript": "^5.5.4" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/src/content/docs/howto/create-first-experiment.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Create Your First Experiment 3 | description: This guide walks you through creating your first experiment with Ascend, from installation to verification and SDK configuration. 4 | --- 5 | 6 | 1. Create audience 7 | 8 |  9 | 10 |  11 | 12 | 2. Create experiment 13 | 14 |  15 | 16 |  17 | 18 |  19 | -------------------------------------------------------------------------------- /.github/workflows/deploy-gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths: 7 | - 'docs/**' 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | permissions: 15 | contents: read 16 | pages: write 17 | id-token: write 18 | 19 | jobs: 20 | build: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout your repository using git 24 | uses: actions/checkout@v4 25 | 26 | - name: Install, build, and upload your site 27 | uses: withastro/action@v3 28 | with: 29 | path: docs 30 | node-version: 20 31 | package-manager: npm 32 | 33 | deploy: 34 | needs: build 35 | runs-on: ubuntu-latest 36 | environment: 37 | name: github-pages 38 | url: ${{ steps.deployment.outputs.page_url }} 39 | steps: 40 | - name: Deploy to GitHub Pages 41 | id: deployment 42 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Sporta Technologies PVT LTD 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/src/content/docs/sdks/kotlin/installation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | description: Installation Guide 4 | --- 5 | 6 | ## Prerequisites 7 | 8 | Before integrating the Ascend Android SDK, ensure your project meets the following requirements: 9 | 10 | - **Android API Level**: Minimum SDK 24 (Android 7.0) 11 | - **Target SDK**: 36 (Android 14) 12 | - **Java Version**: 11 or higher 13 | - **Kotlin**: 2.0.21 or higher 14 | - **Android Gradle Plugin**: 8.13.0 or higher 15 | 16 | --- 17 | 18 | ## Gradle Setup 19 | 20 | ### 1. Add Repository 21 | 22 | Add Maven Central to your project's `settings.gradle.kts` (usually already included by default): 23 | 24 | ```kotlin 25 | dependencyResolutionManagement { 26 | repositories { 27 | google() 28 | mavenCentral() // Maven Central is usually included by default 29 | } 30 | } 31 | ``` 32 | 33 | --- 34 | 35 | ### 2. Add Dependency 36 | 37 | Add the SDK dependency to your app module's `build.gradle.kts`: 38 | ref: [mavenCentral](https://central.sonatype.com/artifact/org.dreamhorizon/ascend-android-sdk/overview) 39 | 40 | ```kotlin 41 | dependencies { 42 | implementation("org.dreamhorizon:ascend-android-sdk:x.y.z") /* replace x.y.z with latest version */ 43 | } 44 | ``` -------------------------------------------------------------------------------- /docs/src/content/docs/sdks/react-native/installation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | description: Installation Guide 4 | --- 5 | 6 | ## Prerequisites 7 | 8 | Before integrating the Ascend React Native SDK, ensure your project meets the following requirements: 9 | 10 | - **React Native**: 0.60.0 or higher 11 | - **Node.js**: 14.0.0 or higher 12 | - **TypeScript**: 4.0.0 or higher (recommended) 13 | - **iOS**: iOS 11.0 or higher 14 | - **Android**: API Level 21 (Android 5.0) or higher 15 | 16 | --- 17 | 18 | ## Package Installation 19 | 20 | ### Using Yarn 21 | 22 | ```shell 23 | yarn add @d11/ascend-react-native@latest // core 24 | yarn add @d11/ascend-experiments-react-native@latest // experiments plugin 25 | ``` 26 | 27 | ### Using NPM 28 | 29 | ```shell 30 | npm install @d11/ascend-react-native@latest // core 31 | npm install @d11/ascend-experiments-react-native@latest // experiments plugin 32 | ``` 33 | 34 | --- 35 | 36 | ## Platform Setup 37 | 38 | ### iOS Setup 39 | 40 | No additional setup required for iOS. The SDK will work out of the box. 41 | 42 | ### Android Setup 43 | 44 | No additional setup required for Android. The SDK will work out of the box. 45 | 46 | --- 47 | 48 | ## TypeScript Support 49 | 50 | The SDK includes full TypeScript definitions. If you're using TypeScript, you'll get full type safety and IntelliSense support. -------------------------------------------------------------------------------- /docs/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro"; 3 | import { Card, CardGrid } from "@astrojs/starlight/components"; 4 | import logo from "../assets/ascend-logo.png"; 5 | --- 6 | 7 | 34 | 35 | 36 | Built on vertx for high performance. 37 | 38 | 39 | Rule based targeting with support of conditional logic. 40 | 41 | 42 | Multiple assignment strategies. 43 | 44 | 45 | Native SDKs quick integration. 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASCEND 2 | 3 | Production-ready Experimentation platform for your product growth. 4 | 5 | --- 6 | 7 | ## What is Ascend? 8 | 9 | An open source experimentation platform to enable individuals or companies to test features and measure impact in an easy and customizable fashion faster, safer, and more consistent. 10 | 11 | ### Architecture : 12 | 13 | 14 | --- 15 | 16 | ## Core Concepts 17 | 18 | Learn about the fundamental concepts and architecture of Ascend in our [documentation](https://ascend.dreamhorizon.org/). 19 | 20 | --- 21 | 22 | ## Installation 23 | 24 | Ready to get started? Follow our comprehensive installation guide in our [Getting Started Guide](https://ascend.dreamhorizon.org/introduction/getting-started/). 25 | 26 | --- 27 | 28 | ## 📚 References and links 29 | 30 | ### Official Resources 31 | 32 | #### Repositories 33 | 34 | - [Ascend](https://github.com/dream-horizon-org/ascend) - Main repository with docs and installation scripts 35 | - [testlab Experiment Backend](https://github.com/dream-horizon-org/testlab) - Backend experiment server 36 | - [flockr Audience Backend](https://github.com/dream-horizon-org/flockr) - Audience management server 37 | - [ascend-panel](https://github.com/dream-horizon-org/ascend-panel) - Admin panel 38 | - [ascend-android-sdk](https://github.com/dream-horizon-org/ascend-android) - Android SDK 39 | - [ascend-ios-sdk](https://github.com/dream-horizon-org/ascend-ios) - IOS SDK 40 | 41 | #### License 42 | 43 | [MIT License](./LICENSE) 44 | 45 | --- 46 | -------------------------------------------------------------------------------- /docs/src/content/docs/sdks/kotlin/quick-start.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Quick Start 3 | description: Quick Start for Android SDK Integration 4 | --- 5 | 6 | Get started with the Ascend Android SDK in just a few steps. 7 | 8 | --- 9 | 10 | ## Initialize the SDK 11 | 12 | ```kotlin 13 | import android.app.Application 14 | import android.util.Log 15 | import com.application.ascend_android.* 16 | import com.google.gson.JsonObject 17 | 18 | class MyApplication : Application() { 19 | override fun onCreate() { 20 | super.onCreate() 21 | 22 | val httpConfig = HttpConfig( 23 | apiBaseUrl = "http://localhost:8100", // Replace with your actual API endpoint 24 | ) 25 | 26 | val experimentConfig = ExperimentConfig.Builder(object : IExperimentCallback { 27 | override fun onSuccess() { 28 | // Called when experiments are successfully fetched 29 | } 30 | override fun onFailure(throwable: Throwable) { 31 | // Called when experiment fetch fails 32 | } 33 | }) 34 | .defaultValues(hashMapOf("button_color" to JsonObject().apply { addProperty("color", "blue") })) 35 | .shouldFetchOnInit(true) // Fetch experiments immediately on initialization 36 | .httpConfig(httpConfig) 37 | .build() 38 | 39 | val experimentPluginConfig = PluginConfig(::DRSPlugin, Plugins.EXPERIMENTS.pluginName, experimentConfig) 40 | 41 | val ascendConfig = AscendConfig(httpConfig, arrayListOf(experimentPluginConfig), ClientConfig("your-key")) // Replace with your actual API key 42 | 43 | Ascend.init(ascendConfig, this) 44 | 45 | Ascend.user.setUser("user123") // Replace with your actual user ID 46 | } 47 | } 48 | ``` 49 | 50 | --- 51 | 52 | ## Use Experiments 53 | 54 | ```kotlin 55 | val experimentPlugin = Ascend.getPlugin(Plugins.EXPERIMENTS) 56 | 57 | val buttonColor = experimentPlugin.getExperimentService() 58 | .getStringFlag("button_color", "color") 59 | 60 | button.setBackgroundColor(Color.parseColor(buttonColor)) 61 | ``` 62 | 63 | --- 64 | 65 | ## Use Events 66 | 67 | Coming soon... -------------------------------------------------------------------------------- /docs/src/styles/command-flag-table.css: -------------------------------------------------------------------------------- 1 | /* Scoped styles for the command flags tables across CLI docs */ 2 | .cli-flags-table table { 3 | width: 100%; 4 | border-collapse: separate; 5 | border-spacing: 0; 6 | margin: 2rem 0; 7 | font-size: 0.95rem; 8 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 9 | border-radius: 8px; 10 | overflow: hidden; /* ensures header color fills to rounded edges */ 11 | table-layout: auto; 12 | line-height: 1.6; 13 | } 14 | 15 | .cli-flags-table thead, 16 | .cli-flags-table thead tr { 17 | background: var(--sl-color-accent); 18 | } 19 | 20 | .cli-flags-table thead th { 21 | padding: 1.5rem 2rem; 22 | text-align: left; 23 | border: none; 24 | color: #ffffff !important; 25 | font-weight: 600; 26 | text-transform: uppercase; 27 | font-size: 0.85rem; 28 | letter-spacing: 0.05em; 29 | line-height: 1.6; 30 | } 31 | 32 | .cli-flags-table tbody tr { 33 | border-bottom: 1px solid var(--sl-color-gray-6); 34 | transition: background-color 0.2s ease; 35 | min-height: 4rem; 36 | } 37 | 38 | .cli-flags-table tbody tr:hover { 39 | background-color: var(--sl-color-gray-7); 40 | } 41 | 42 | .cli-flags-table tbody tr:last-child { 43 | border-bottom: none; 44 | } 45 | 46 | .cli-flags-table tbody td { 47 | padding: 1.5rem 2rem; 48 | border: none; 49 | word-wrap: break-word; 50 | overflow-wrap: break-word; 51 | vertical-align: middle; 52 | line-height: 1.8; 53 | } 54 | 55 | .cli-flags-table tbody td:first-child { 56 | font-weight: 500; 57 | color: var(--sl-color-accent-high); 58 | } 59 | 60 | .cli-flags-table tbody td code { 61 | background: var(--sl-color-gray-6); 62 | padding: 0.2rem 0.4rem; 63 | border-radius: 4px; 64 | font-size: 0.85em; 65 | white-space: normal; /* allow wrapping inside code to avoid column growth */ 66 | word-break: break-word; 67 | } 68 | 69 | /* Equal-width helpers for markdown tables wrapped with cli-flags-table */ 70 | .cli-flags-table.equal-5 thead th, 71 | .cli-flags-table.equal-5 tbody td { 72 | width: 20% !important; 73 | } 74 | 75 | .cli-flags-table.equal-4 thead th, 76 | .cli-flags-table.equal-4 tbody td { 77 | width: 25% !important; 78 | } 79 | 80 | @media (max-width: 768px) { 81 | .cli-flags-table table { 82 | font-size: 0.85rem; 83 | } 84 | 85 | .cli-flags-table thead th, 86 | .cli-flags-table tbody td { 87 | padding: 1.25rem 1.5rem; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /docs/src/content/docs/sdks/ios/installation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | description: Installation Guide 4 | --- 5 | 6 | ## Prerequisites 7 | 8 | Before integrating the Ascend iOS SDK, ensure your project meets the following requirements: 9 | 10 | - **iOS Version**: Minimum iOS 13.0 11 | - **Swift Version**: 5.0 or higher 12 | - **Xcode**: 12.0 or higher 13 | - **Deployment Target**: iOS 13.0 or later 14 | 15 | --- 16 | 17 | ## Swift Package Manager 18 | 19 | ### 1. Add Package Dependency 20 | 21 | In Xcode: 22 | 23 | 1. Go to **File > Add Packages…** 24 | 2. Enter the package repository URL: 25 | ``` 26 | https://github.com/dream-horizon-org/ascend-ios 27 | ``` 28 | 3. Select the **Ascend** package 29 | 4. Choose the version you want to use (preferably use a specific tag like `1.0.0`) 30 | 5. Click **Add Package** 31 | 32 | Alternatively, add the dependency directly in your `Package.swift`: 33 | 34 | ```swift 35 | dependencies: [ 36 | .package(url: "https://github.com/dream-horizon-org/ascend-ios", from: "1.0.0") 37 | ] 38 | ``` 39 | 40 | --- 41 | 42 | ## CocoaPods 43 | 44 | ### 1. Add to Podfile 45 | 46 | Add the following to your `Podfile`: 47 | 48 | ```ruby 49 | platform :ios, '11.0' 50 | 51 | target 'YourApp' do 52 | use_frameworks! 53 | 54 | pod 'Ascend', :git => 'https://github.com/dream-horizon-org/ascend-ios.git', :tag => '1.0.0' 55 | end 56 | ``` 57 | 58 | ### 2. Install Dependencies 59 | 60 | Run the following command in your terminal: 61 | 62 | ```bash 63 | pod install 64 | ``` 65 | 66 | --- 67 | 68 | ## Manual Installation 69 | 70 | 1. Clone the repository: 71 | ```bash 72 | git clone https://github.com/dream-horizon-org/ascend-ios.git 73 | ``` 74 | 75 | 2. Drag the `Sources` folder into your Xcode project 76 | 77 | 3. Make sure to add the following frameworks to your project: 78 | - Foundation 79 | - UIKit 80 | - SystemConfiguration 81 | - CoreLocation 82 | 83 | --- 84 | 85 | ## Import Statements 86 | 87 | After installation, import the SDK in your Swift files: 88 | 89 | ```swift 90 | import Ascend 91 | ``` 92 | 93 | --- 94 | 95 | ## Verify Installation 96 | 97 | After installation, verify that you can import the SDK: 98 | 99 | ```swift 100 | import Ascend 101 | ``` 102 | 103 | If the import succeeds, the SDK is properly installed. 104 | 105 | -------------------------------------------------------------------------------- /docs/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | import starlight from '@astrojs/starlight'; 3 | 4 | // https://astro.build/config 5 | export default defineConfig({ 6 | site: 'https://ascend.dreamhorizon.org', 7 | base: '/', 8 | integrations: [ 9 | starlight({ 10 | title: 'Ascend', 11 | description: 'Ascend - Product Growth Experimentation & Targeting Platform', 12 | favicon: '/ascend-logo.png', 13 | logo: { 14 | src: './src/assets/ascend-logo.png', 15 | }, 16 | social: [ 17 | { 18 | icon: 'github', 19 | label: 'GitHub', 20 | href: 'https://github.com/dream-horizon-org/ascend', 21 | }, 22 | ], 23 | sidebar: [ 24 | { 25 | label: 'Introduction', 26 | items: [ 27 | 'introduction/overview', 28 | 'introduction/getting-started', 29 | 'introduction/why-use-ascend', 30 | ], 31 | }, 32 | { 33 | label: 'Key Concepts', 34 | items: [ 35 | 'concepts/overview' 36 | ], 37 | }, 38 | { 39 | label: 'SDKs', 40 | items: [ 41 | { 42 | label: 'Android', 43 | items: [ 44 | 'sdks/kotlin/installation', 45 | 'sdks/kotlin/quick-start', 46 | 'sdks/kotlin/api' 47 | 48 | 49 | ], 50 | }, 51 | { 52 | label: 'iOS', 53 | items: [ 54 | 'sdks/ios/installation', 55 | 'sdks/ios/quick-start', 56 | 'sdks/ios/api' 57 | 58 | ], 59 | }, 60 | { 61 | label: 'React Native', 62 | items: [ 63 | 'sdks/react-native/installation', 64 | 'sdks/react-native/quick-start', 65 | 'sdks/react-native/api' 66 | 67 | ], 68 | }, 69 | 70 | ], 71 | }, 72 | { 73 | label: 'How-To Guides', 74 | items: [ 75 | 'howto/how-to-contribute', 76 | 'howto/create-first-experiment' 77 | ], 78 | }, 79 | ], 80 | customCss: ['./src/styles/custom.css'], 81 | tableOfContents: { 82 | minHeadingLevel: 2, 83 | maxHeadingLevel: 4, 84 | }, 85 | pagination: true, 86 | }), 87 | ], 88 | server: { 89 | port: 4321, 90 | host: true, 91 | }, 92 | }); 93 | -------------------------------------------------------------------------------- /docs/src/content/docs/introduction/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overview 3 | description: Ascend - Experimentation Platform for Product Growth 4 | --- 5 | 6 | ## What is Ascend? 7 | 8 | **Ascend** is an open-source A/B testing Experimentation platform that empowers teams to make data-driven decisions through controlled experiments. It provides a complete solution for running experiments across different mobile applications with minimal integration effort. 9 | 10 | It also provides support to create and manage audience(cohorts) using static(ID-based) . 11 | 12 | 13 | :::tip 14 | 15 | - Got questions? 16 | - We would love to help you get started with Ascend. You can [join us on Discord](https://discord.gg/Mh5BdzGQ) . 17 | ::: 18 | 19 | ## Quick Links 20 | 21 | 22 | 23 | 24 | ⚙️ 25 | 26 | How it works 27 | Learn about what Ascend does and how it works 28 | 29 | 30 | 31 | 32 | ⚡ 33 | 34 | Quick Start 35 | Get Ascend running in 10 minutes 36 | 37 | 38 | 39 | 40 | 41 | --- 42 | 43 | ## Key Features 44 | 45 | 🚀 **High Performance**: Built on Vert.x for reactive, non-blocking operations, Aerospike integration for ultra-fast allocation. 46 | 47 | 🎯 **Advanced Targeting**: Rule-based targeting with custom attributes, Support for complex conditional logic. 48 | 49 | 📊 **Flexible Assignment**: Random and round-robin distribution strategies, Custom variant weights per cohort. 50 | 51 | 🔌 **Easy Integration**: Ascend SDKs with type-safe APIs, Automatic caching and retry logic. 52 | 53 | 🛡️ **Production-Ready**: Docker and Docker Compose support, Configurable circuit breakers and rate limiting. 54 | 55 | --- 56 | 57 | ## Use Cases 58 | 59 | #### 1) Full Experimentation Platform 60 | 61 | Run comprehensive A/B tests and multivariate experiments to validate features and measure product impact. 62 | 63 | #### 2) Audience Creation 64 | 65 | Create and manage targeted audience segments for precise experiment allocation and feature rollouts. 66 | 67 | #### 3) Experiment Analysis (Coming soon) 68 | 69 | Analyze experiment results with statistical significance testing and detailed metrics. 70 | 71 | --- 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Ascend Documentation 2 | 3 | This directory contains the Astro-based documentation site for Ascend Platform. 4 | 5 | --- 6 | 7 | ## 🚀 Quick Start 8 | 9 | ### Development 10 | 11 | ```bash 12 | npm install 13 | npm run dev 14 | ``` 15 | 16 | The site will be available at `http://localhost:4321/` 17 | 18 | ### Build 19 | 20 | ```bash 21 | npm run build 22 | ``` 23 | 24 | The built site will be in the `dist/` directory. 25 | 26 | --- 27 | 28 | ### Preview Build 29 | 30 | ```bash 31 | npm run preview 32 | ``` 33 | 34 | --- 35 | 36 | ## 📝 Documentation Structure 37 | 38 | ### GitHub Pages Setup 39 | - **Site URL**: `https://ascend.dreamhorizon.org/` 40 | - **Base Path**: `/` 41 | - **Repository**: `https://dream-horizon-org.github.io/ascend` 42 | 43 | ### Documentation Sections 44 | 45 | The documentation includes: 46 | 47 | 1. **Introduction** 48 | - Overview: What is Ascend and key features 49 | - Getting Started: Installation and create your first experiment 50 | 51 | 2. **Key Concepts** 52 | - Overview: Introduction to core concepts 53 | - Assignment Strategies 54 | - Experiment Lifecycle 55 | - Architecture Overview 56 | - Use cases 57 | 58 | 3. **How-To Guides** 59 | - Deploy Your First Service 60 | - Dev to QA Iteration 61 | - Additional guides for common tasks 62 | 63 | --- 64 | 65 | ## 🎨 Customization 66 | 67 | ### Logo 68 | The site uses Ascend logo from `src/assets/ascend-logo.png` 69 | 70 | ### Theme 71 | Custom styles can be modified in `src/styles/custom.css` 72 | 73 | --- 74 | 75 | ## 📦 Dependencies 76 | 77 | The site uses: 78 | - Astro with Starlight theme 79 | - Node.js and npm 80 | - TypeScript 81 | 82 | --- 83 | 84 | ## 🚀 Deployment 85 | 86 | This site is configured for GitHub Pages deployment: 87 | 1. GitHub Pages enabled in repository settings 88 | 2. Build workflow deploys to the `gh-pages` branch 89 | 3. Site published from the `gh-pages` branch 90 | 91 | The site will be accessible at: `https://ascend.dreamhorizon.org/` 92 | 93 | --- 94 | 95 | ## 📚 Content Guidelines 96 | 97 | When adding new documentation: 98 | 99 | 1. **Use MDX format** for all content files 100 | 2. **Include frontmatter** with title and description 101 | 3. **Add to sidebar** in `astro.config.mjs` 102 | 4. **Use code examples** liberally 103 | 5. **Link between pages** using relative paths 104 | 6. **Follow the existing structure**: 105 | - Concepts: Explain what something is 106 | - How-To: Step-by-step instructions 107 | - Reference: Complete technical details 108 | 109 | --- 110 | 111 | ## 📚 Additional Resources 112 | 113 | - [Astro Documentation](https://astro.build/) 114 | - [Starlight Documentation](https://starlight.astro.build/) 115 | - [Testlab Experiment Backend]() 116 | - [Flockr Audience Backend]() 117 | - [Licence] MIT License 118 | 119 | -------------------------------------------------------------------------------- /docs/src/content/docs/sdks/ios/quick-start.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Quick Start 3 | description: Quick Start for iOS SDK Integration 4 | --- 5 | 6 | Get started with the Ascend iOS SDK in just a few steps. 7 | 8 | --- 9 | 10 | ## Initialize the SDK 11 | 12 | ```swift 13 | import SwiftUI 14 | import Ascend 15 | 16 | @main 17 | struct MyApp: App { 18 | init() { 19 | setupAscendSDK() 20 | } 21 | 22 | var body: some Scene { 23 | WindowGroup { 24 | ContentView() 25 | } 26 | } 27 | 28 | private func setupAscendSDK() { 29 | // 1. Configure Core Settings 30 | let coreConfig = AscendCoreConfig( 31 | apiKey: "your-api-key", 32 | environment: "development", 33 | enableDebugLogging: true 34 | ) 35 | 36 | // 2. Configure HTTP Settings 37 | let httpConfig = HTTPConfig( 38 | apiBaseUrl: "https://api.ascend.com", 39 | timeout: 30.0, 40 | shouldRetry: true, 41 | maxRetries: 3, 42 | defaultHeaders: ["api-key": "your-api-key"] 43 | ) 44 | 45 | // 3. Configure Experiments Plugin 46 | let experimentsConfig = AscendExperimentsConfiguration.development( 47 | apiBaseUrl: "https://api.ascend.com", 48 | apiEndpoint: "/v1/allocations", 49 | defaultValues: [ 50 | "button_color": .dictionary([ 51 | "color": .string("blue") 52 | ]) 53 | ], 54 | headers: ["api-key": "your-api-key"] 55 | ) 56 | 57 | // 4. Create SDK Configuration 58 | let config = AscendConfig( 59 | plugins: [AscendPlugin(type: .experiments, config: experimentsConfig)], 60 | httpConfig: httpConfig, 61 | coreConfig: coreConfig 62 | ) 63 | 64 | // 5. Initialize the SDK 65 | do { 66 | try Ascend.initialize(with: config) 67 | print("✅ Ascend SDK initialized successfully") 68 | 69 | // Set a user (required for experiments) 70 | Ascend.user.setUser(userId: "user-123") 71 | } catch { 72 | print("❌ Failed to initialize Ascend SDK: \(error)") 73 | } 74 | } 75 | } 76 | ``` 77 | 78 | --- 79 | 80 | ## Use Experiments 81 | 82 | ```swift 83 | // Get the experiments plugin 84 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 85 | 86 | // Get string value 87 | let buttonColor = experiments.getStringValue(for: "button_color", with: "color") 88 | print("Button color: \(buttonColor)") 89 | 90 | // Get boolean value 91 | let isEnabled = experiments.getBoolValue(for: "feature_toggle", with: "enabled") 92 | print("Feature enabled: \(isEnabled)") 93 | 94 | // Get integer value 95 | let maxRetries = experiments.getIntValue(for: "retry_config", with: "max_attempts") 96 | print("Max retries: \(maxRetries)") 97 | ``` 98 | 99 | --- 100 | 101 | ## Fetch Experiments from API 102 | 103 | ```swift 104 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 105 | 106 | experiments.fetchExperiments(for: [ 107 | "button_color": .dictionary([ 108 | "color": .string("blue") 109 | ]) 110 | ]) { response, error in 111 | if let error = error { 112 | print("Error: \(error)") 113 | } else if let response = response { 114 | print("Fetched \(response.data?.count ?? 0) experiments") 115 | } 116 | } 117 | ``` 118 | 119 | --- 120 | 121 | ## Use Events 122 | 123 | Coming soon... 124 | 125 | -------------------------------------------------------------------------------- /docs/src/content/docs/introduction/getting-started.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting Started 3 | description: Install Ascend platform in under 10 minutes 4 | --- 5 | 6 | Get up and running with Ascend on a docker. This guide will walk you through installation, configuration, and creating your first experiment. 7 | 8 | --- 9 | 10 | ## Prerequisites 11 | 12 | --- 13 | 14 | ### Hardware Requirements 15 | | Resource | Minimum | 16 | |----------|---------| 17 | | RAM | 8 GB | 18 | | CPU| 4 cores | 19 | | Disk Space | 20 GB free | 20 | | OS | macOS 11+, Linux (Ubuntu 20.04+) | 21 | 22 | 23 | ### Software Requirements 24 | 25 | The following tools must be installed on your system: 26 | 27 | | Tool | Minimum Version | Installation | 28 | |------|----------------|--------------| 29 | | Docker | v20.10+ | [Install Guide](https://docs.docker.com/get-docker/) | 30 | | curl | v7.68+ | Pre-installed on most systems | 31 | 32 | :::note 33 | The installation script will automatically check for these prerequisites and guide you through installing any missing tools. 34 | ::: 35 | 36 | --- 37 | 38 | ## Quick Start: One-Command Installation 39 | 40 | Get Ascend running on a docker in under 10 minutes: 41 | 42 | ```bash 43 | bash <(curl -fsSL https://raw.githubusercontent.com/dream-horizon-org/ascend/main/startup.sh) 44 | ``` 45 | 46 | ### What This Does 47 | 48 | The installation script automatically: 49 | 50 | 1. ✅ Checks and installs prerequisites (Docker etc.) 51 | 2. ✅ Clones the Ascend repository 52 | 3. ✅ Deploys Ascend with all components 53 | 4. ✅ Open http://localhost:9000 in browser 54 | 55 | 56 | **Expected Duration**: 7-10 minutes (depending on internet speed and hardware) 57 | 58 | --- 59 | 60 | ### Installation Output 61 | 62 | You'll see output similar to: 63 | 64 | ```bash 65 | # bash startup.sh 66 | 🚀 Installing Ascend platform to /Users/mac-user/.ascend... 67 | 68 | 📦 Installing requirements... 69 | 70 | 📁 Setting up installation directory... 71 | 72 | 📥 Checking out repositories... 73 | 74 | 🐳 Starting dependent services... 75 | Starting services for testlab... 76 | ✅ Started testlab services 77 | Starting services for flockr... 78 | ✅ Started flockr services 79 | Starting services for ascend-panel... 80 | ✅ Started ascend-panel services 81 | 82 | ✅ Ascend platform installed successfully in /Users/mac-user/.ascend 83 | 🌐 Ascend started at http://localhost:9000 84 | 85 | ``` 86 | 87 | Now that Ascend is installed and configured, you can create your first experiment: 88 | 89 | For a complete walkthrough, see: 90 | - [Create Your First Experiment](/howto/create-first-experiment) - Step-by-step tutorial 91 | - [Key Concepts](/concepts/overview) - Understand experiment, assignments, variant, cohorts etc. 92 | 93 | --- 94 | 95 | ### Uninstallation 96 | 97 | To remove Ascend from your cluster: 98 | 99 | ```bash 100 | # using the Uninstallation script 101 | bash <(curl -fsSL https://raw.githubusercontent.com/dream-horizon-org/ascend/main/remove.sh) 102 | ``` 103 | 104 | --- 105 | 106 | ## Next Steps 107 | 108 | :::tip[🎉 Success!] 109 | Congratulations! You've successfully installed Ascend and [create your first experiment](/howto/create-first-experiment). 110 | 111 | **Next steps:** 112 | - [Configure SDK](/sdks/kotlin/installation) - Complete walkthrough 113 | - [Key Concepts](/concepts/overview) - Understand key concepts experiments, audience, assignment etc. 114 | - [How-To Contribute](/howto/how-to-contribute) - How to Contribute to Ascend 115 | ::: 116 | 117 | --- 118 | 119 | ## Additional Resources 120 | 121 | - **GitHub Repository**: https://github.com/dream-horizon-org/ascend 122 | - **Issue Tracker**: https://github.com/dream-horizon-org/ascend/issues 123 | - **License**: [MIT License](https://github.com/dream-horizon-org/ascend/blob/main/LICENSE) 124 | -------------------------------------------------------------------------------- /remove.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ASCEND_DEFAULT_DIR="${HOME}/.ascend" 4 | 5 | REPO_LIST=("testlab" "flockr" "ascend-panel" "ascend-astra") 6 | 7 | # stop and remove containers 8 | stop_containers () { 9 | local base_dir=$1 10 | local remove_volumes=${2:-true} 11 | 12 | if [ ! -d "$base_dir" ]; then 13 | echo "Directory ${base_dir} does not exist. Skipping container removal." 14 | return 0 15 | fi 16 | 17 | cd "$base_dir" || exit 1 18 | 19 | for repo in "${REPO_LIST[@]}"; do 20 | local repo_dir="${base_dir}/${repo}" 21 | if [ -d "${repo_dir}" ]; then 22 | echo "🛑 Stopping containers for ${repo}..." 23 | cd "${repo_dir}" || continue 24 | if [ "$remove_volumes" = true ]; then 25 | docker compose down -v 2>/dev/null || echo "⚠️ Warning: Failed to stop containers for ${repo}" 26 | else 27 | docker compose down 2>/dev/null || echo "⚠️ Warning: Failed to stop containers for ${repo}" 28 | fi 29 | cd "$base_dir" || exit 1 30 | else 31 | echo "⚠️ Skipping ${repo} - directory does not exist" 32 | fi 33 | done 34 | } 35 | 36 | # prune unused docker resources 37 | prune_docker () { 38 | echo "🧹 Pruning unused Docker resources..." 39 | docker system prune -f 2>/dev/null || echo "⚠️ Warning: Failed to prune Docker resources" 40 | } 41 | 42 | # remove base directory 43 | remove_base_dir () { 44 | local base_dir=$1 45 | 46 | if [ ! -d "$base_dir" ]; then 47 | echo "Directory ${base_dir} does not exist. Nothing to remove." 48 | return 0 49 | fi 50 | 51 | echo "🗑️ Removing ${base_dir}..." 52 | rm -rf "${base_dir}" 53 | } 54 | 55 | # confirm action with user 56 | confirm_removal () { 57 | local base_dir=$1 58 | local containers_only=$2 59 | 60 | if [ "$containers_only" = true ]; then 61 | echo "⚠️ WARNING: This will stop all Ascend containers" 62 | else 63 | echo "⚠️ WARNING: This will remove all Ascend services and data from ${base_dir}" 64 | echo "This action cannot be undone." 65 | fi 66 | read -p "Are you sure you want to continue? (y/N): " -r response 67 | 68 | case "$response" in 69 | [yY][eE][sS]|[yY]) 70 | return 0 71 | ;; 72 | *) 73 | echo "Operation cancelled." 74 | exit 0 75 | ;; 76 | esac 77 | } 78 | 79 | print_usage () { 80 | echo "Usage: $0 [OPTIONS] [BASE_DIR]" 81 | echo "" 82 | echo "Options:" 83 | echo " --stop-only, -s Stop containers only (keep directories and images)" 84 | echo " --yes, -y Skip confirmation prompt" 85 | echo " --help, -h Show this help message" 86 | echo "" 87 | echo "Arguments:" 88 | echo " BASE_DIR Installation directory (default: ${ASCEND_DEFAULT_DIR})" 89 | } 90 | 91 | main () { 92 | local base_dir="${ASCEND_DEFAULT_DIR}" 93 | local skip_confirm=false 94 | local stop_only=false 95 | 96 | # Parse arguments 97 | while [[ $# -gt 0 ]]; do 98 | case "$1" in 99 | --stop-only|-s) 100 | stop_only=true 101 | shift 102 | ;; 103 | --yes|-y) 104 | skip_confirm=true 105 | shift 106 | ;; 107 | --help|-h) 108 | print_usage 109 | exit 0 110 | ;; 111 | *) 112 | base_dir="$1" 113 | shift 114 | ;; 115 | esac 116 | done 117 | 118 | # Confirm with user 119 | if [ "$skip_confirm" = false ]; then 120 | confirm_removal "${base_dir}" "${stop_only}" 121 | fi 122 | 123 | if [ "$stop_only" = true ]; then 124 | echo "🛑 Stopping Ascend services in ${base_dir}..." 125 | echo "" 126 | stop_containers "${base_dir}" false 127 | echo "" 128 | echo "✅ Ascend services stopped successfully." 129 | echo " Directories and images preserved." 130 | else 131 | echo "🗑️ Removing Ascend services from ${base_dir}..." 132 | echo "" 133 | stop_containers "${base_dir}" true 134 | remove_base_dir "${base_dir}" 135 | prune_docker 136 | echo "" 137 | echo "✅ Ascend services removed successfully." 138 | fi 139 | } 140 | 141 | main "$@" 142 | -------------------------------------------------------------------------------- /docs/src/content/docs/sdks/react-native/quick-start.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Quick Start 3 | description: Quick Start for Android SDK Integration 4 | --- 5 | 6 | Get started with the Ascend React Native SDK in just a few steps. 7 | 8 | --- 9 | 10 | ## Initialize the SDK 11 | 12 | 1. Create a file `initAscend.ts` under src folder next to your app's index file. 13 | 2. Import this file to your index file and call the `initAscend` method to initialise Ascend. 14 | 3. Read following section to understand how to import and configure Ascend. 15 | 16 | ### Initialise Ascend 17 | 18 | ```typescript 19 | import Ascend from "@d11/ascend-react-native"; 20 | import AscendExperiments from "@d11/ascend-experiments-react-native"; 21 | 22 | const headers: any = { // put all headers which are required for api.dream11.com 23 | 'Host': 'api.dream11.com', 24 | 'locale': 'en-US', 25 | // Add other required headers 26 | }; 27 | 28 | const httpConfig: THttpConfig = { 29 | headers, 30 | shouldRetry: true, 31 | apiBaseUrl: 'https://api.dream11.com' 32 | }; 33 | 34 | const defaultValues = { 35 | DUMMY_API_PATH_HERE_1: { 36 | value: true, 37 | }, 38 | DUMMY_API_PATH_HERE_2: { 39 | randomBool: false, 40 | randomString: 'random string', 41 | randomInt: -97, 42 | sports: ['cricket', 'football'], 43 | }, 44 | }; 45 | 46 | /* 47 | * ascend-experiments config. 48 | * It should have at least following three attributes 49 | */ 50 | const ascendExperimentsPlugin = { 51 | name: PLUGIN_LIST.EXPERIMENTS, 52 | config: { 53 | defaultValues, 54 | }, 55 | exec: AscendExperiments 56 | } 57 | 58 | /* 59 | * ascend's initialization with basic configuration and ascend-experiments plugin 60 | */ 61 | export const initAscend = async () => { 62 | return await Ascend.init({ 63 | httpConfig, 64 | plugins: [ascendExperimentsPlugin, ...otherPlugins], 65 | }); 66 | }; 67 | ``` 68 | 69 | --- 70 | 71 | ## Use Experiments 72 | 73 | ### Fetch experiment values 74 | 75 | Use following code snippet to initialise Experiment Plugin once. This operation is async. So it depends upon the consumer whether they want to wait for this operation to complete or proceed further. 76 | 77 | NOTE: By default, on initialisation of the experiment plugin, it fetches the values of API Paths configured with the plugin. 78 | 79 | ```typescript 80 | /* 81 | * Get the experiment plugin instance 82 | */ 83 | const experimentPlugin = await Ascend.getPlugin(PLUGIN_LIST.EXPERIMENTS); 84 | 85 | // Get boolean flag 86 | const boolVal = await experimentPlugin.getBooleanFlag('API_PATH', 'VARIABLE_NAME'); 87 | 88 | // Get string flag 89 | const stringVal = await experimentPlugin.getStringFlag('API_PATH', 'VARIABLE_NAME'); 90 | 91 | // Get integer flag 92 | const intVal = await experimentPlugin.getIntFlag('API_PATH', 'VARIABLE_NAME'); 93 | 94 | // Get double flag 95 | const doubleVal = await experimentPlugin.getDoubleFlag('API_PATH', 'VARIABLE_NAME'); 96 | 97 | // Get all variables for an experiment 98 | const allVars = await experimentPlugin.getAllVariables('API_PATH'); 99 | ``` 100 | 101 | --- 102 | 103 | ## Use Events 104 | 105 | Coming soon... 106 | 107 | --- 108 | 109 | ## Example Usage in React Component 110 | 111 | ```typescript 112 | import React, { useEffect, useState } from 'react'; 113 | import { View, Text, Button } from 'react-native'; 114 | import { initAscend } from './initAscend'; 115 | 116 | const MyComponent = () => { 117 | const [experimentValue, setExperimentValue] = useState(false); 118 | 119 | useEffect(() => { 120 | const initializeSDK = async () => { 121 | try { 122 | await initAscend(); 123 | console.log('Ascend initialized successfully'); 124 | } catch (error) { 125 | console.error('Failed to initialize Ascend:', error); 126 | } 127 | }; 128 | 129 | initializeSDK(); 130 | }, []); 131 | 132 | const getExperimentValue = async () => { 133 | try { 134 | const experimentPlugin = await Ascend.getPlugin(PLUGIN_LIST.EXPERIMENTS); 135 | const value = await experimentPlugin.getBooleanFlag('DUMMY_API_PATH_HERE_1', 'value'); 136 | setExperimentValue(value); 137 | } catch (error) { 138 | console.error('Failed to get experiment value:', error); 139 | } 140 | }; 141 | 142 | return ( 143 | 144 | Experiment Value: {experimentValue.toString()} 145 | 146 | 147 | ); 148 | }; 149 | 150 | export default MyComponent; 151 | ``` 152 | -------------------------------------------------------------------------------- /docs/src/content/docs/introduction/why-use-ascend.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Why Use Ascend? 3 | description: Discover why Ascend is the ideal experimentation platform for your product growth 4 | --- 5 | 6 | ## The Challenge of Modern Experimentation 7 | 8 | In today's fast-paced product development landscape, teams face significant challenges when trying to validate features and make data-driven decisions: 9 | 10 | - **Complex infrastructure requirements** — Setting up an experimentation platform requires expertise in backend services, databases, caching layers, and SDK development 11 | - **Vendor lock-in concerns** — Commercial solutions often come with steep pricing, limited customization, and data ownership issues 12 | - **Fragmented mobile support** — Many solutions don't offer first-class support across Android, iOS, and React Native 13 | - **Scalability limitations** — As experiment volume grows, performance becomes a bottleneck 14 | 15 | **Ascend solves all of these problems.** 16 | 17 | --- 18 | 19 | ## Why Choose Ascend? 20 | 21 | ### 🆓 Truly Open Source 22 | 23 | Ascend is built with ❤️ by the open-source community under the MIT License. This means: 24 | 25 | - **No vendor lock-in** — You own your data and infrastructure 26 | - **Full transparency** — Audit the code, understand exactly how experiments work 27 | - **Community-driven** — Feature requests and contributions shape the roadmap 28 | - **Cost-effective** — No per-seat licensing or usage-based pricing surprises 29 | 30 | ### 🚀 Production-Ready Performance 31 | 32 | Built on battle-tested technologies, Ascend is engineered for scale: 33 | 34 | | Component | Technology | Benefit | 35 | |-----------|------------|---------| 36 | | Backend Framework | Vert.x 4.4.9 | Reactive, non-blocking I/O for high throughput | 37 | | Database | PostgreSQL, Aerospike 6.2.0 | Reliable, partitioned storage for experiment data, Sub-millisecond variant assignments at scale | 38 | | Resilience | Resilience4j 2.2.0 | Circuit breakers, rate limiting, fault tolerance | 39 | 40 | Ascend handles millions of assignment requests while maintaining consistent low-latency responses. 41 | 42 | --- 43 | 44 | ### 📱 Native Cross-Platform SDKs 45 | 46 | Unlike solutions that treat mobile as an afterthought, Ascend provides first-class SDKs for all major platforms: 47 | 48 | SDK features: 49 | - **Type-safe APIs** — Catch errors at compile time, not runtime 50 | - **Automatic caching** — Graceful fallbacks when offline 51 | - **Lifecycle integration** — Refresh experiments on app foreground 52 | - **Minimal footprint** — Lightweight with no bloated dependencies 53 | 54 | --- 55 | 56 | ## Ascend vs. Alternatives 57 | 58 | ### vs. LaunchDarkly / Split.io / Optimizely 59 | 60 | | Feature | Ascend | Commercial Solutions | 61 | |---------|--------|---------------------| 62 | | Pricing | Free, forever | $$ - $$$$ per seat/month | 63 | | Data Ownership | 100% yours | Stored on vendor servers | 64 | | Customization | Full source access | Limited APIs | 65 | | Self-hosted | ✅ Docker/K8s | ❌ SaaS only (mostly) | 66 | | Mobile SDKs | Native, type-safe | Often wrapper-based | 67 | 68 | --- 69 | 70 | ### vs. Building In-House 71 | 72 | | Consideration | Ascend | In-House Solution | 73 | |---------------|--------|-------------------| 74 | | Time to production | < 10 minutes | Months of development | 75 | | Maintenance burden | Community-supported | 100% on your team | 76 | | Best practices | Built-in | Must research & implement | 77 | | Cross-platform SDKs | Ready to use | Build from scratch | 78 | 79 | --- 80 | 81 | ### vs. Firebase Remote Config / AWS AppConfig 82 | 83 | | Feature | Ascend | Config Services | 84 | |---------|--------|-----------------| 85 | | A/B testing | Full support | Limited/manual | 86 | | Statistical analysis | Coming soon | Not included | 87 | | Audience segmentation | Advanced rules | Basic | 88 | | Multi-variate testing | ✅ | ❌ | 89 | 90 | --- 91 | 92 | ## Getting Started is Easy 93 | 94 | - **One-Command Installation** - Get up and running in under 10 minutes 95 | - **Minimal SDK Integration** - Simple, intuitive APIs for all platforms 96 | 97 | --- 98 | 99 | ## What Makes Ascend Different 100 | 101 | #### 🏗️ Built for Scale 102 | 103 | Vert.x's reactive architecture handles high throughput with minimal resource consumption. Aerospike caching ensures sub-millisecond assignment lookups. 104 | 105 | #### 🛡️ Battle Tested at Dream11 Scale 106 | 107 | 10M+ RPM - Proven at scale with millions of requests per minute. 108 | 109 | #### 📖 Developer Experience First 110 | 111 | Type-safe APIs, comprehensive documentation, and sensible defaults mean you spend time shipping features, not debugging the experimentation layer. 112 | 113 | #### 🌐 Community Backed 114 | 115 | Active development, transparent roadmap, and responsive community support. 116 | 117 | --- 118 | -------------------------------------------------------------------------------- /docs/src/content/docs/concepts/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Key Concepts 3 | description: Understanding Ascend's core concepts and terminology 4 | --- 5 | 6 | ## What is Ascend? 7 | 8 | Ascend is a comprehensive experimentation platform that consists of: 9 | 10 | - **Backend Experiment Server (TestLab)**: A high-performance Java-based server built on Vert.x that manages experiments, handles variant assignments, and processes experiment data. 11 | - **Backend Audience Server (Flockr)**: A high-performance Java-based server built on Vert.x that manages audience creation , rule addition and querying over multiple data source. 12 | - **Admin Panel**: Manage your experiment's lifecycle, audience's lifecycle , SDK's API Key. 13 | - **Cross-Platform SDKs**: SDKs for Android, iOS, and React Native that seamlessly integrate with your applications. 14 | - **Flexible Architecture**: Plugin-based architecture that allows extensibility and customization. 15 | 16 | --- 17 | 18 | ## Core Concepts 19 | 20 | ### Experiments 21 | 22 | An **experiment** in Ascend is a controlled test where you compare different variants (versions) of a feature to determine which performs better. Each experiment includes: 23 | 24 | - **Variants**: Different versions of your feature to test (e.g., control, treatment). 25 | - **Allocation Strategy**: How users are assigned to variants (random(default), round-robin). 26 | - **Target Rules**: Conditions that determine which users are eligible for the experiment. 27 | - **Audience**: User segments for targeted experimentation. Create csv based audience. 28 | 29 | --- 30 | 31 | ### Allocation Strategies 32 | 33 | Ascend supports multiple allocation strategies: 34 | 35 | - **Stratified Allocation**: Users are assigned based on their audience segments. 36 | - **Audience-Based Allocation**: Users are assigned based on audience membership with custom variant weights per audience. 37 | 38 | --- 39 | 40 | ### Experiment Lifecycle 41 | 42 | 1. **Draft**: Experiment is being configured (Coming soon) 43 | 2. **Live**: Experiment is actively running 44 | 3. **Paused**: Experiment is temporarily stopped 45 | 4. **Concluded**: Experiment has finished and winning variant declared 46 | 5. **Terminated**: Experiment will no longer being served 47 | 48 | --- 49 | 50 | ## Architecture Overview 51 | 52 | ``` 53 | ┌─────────────────────────────────────────────────────────┐ 54 | │ Client Applications │ 55 | │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ 56 | │ │ Android │ │ iOS │ │ React Native │ │ 57 | │ │ SDK │ │ SDK │ │ SDK │ │ 58 | │ └────┬─────┘ └────┬─────┘ └────────┬─────────┘ │ 59 | └───────┼───────────────┼───────────────────┼─────────────┘ 60 | │ │ │ 61 | └───────────────┼───────────────────┘ 62 | │ REST API 63 | ┌───────────────┴────────────────────────┐ 64 | │ Backend Server(testlab+flockr) │ 65 | │ (Vert.x + PostgreSQL + │ 66 | │ Aerospike) │ 67 | └────────────────────────────────────────┘ 68 | ``` 69 | 70 | --- 71 | 72 | ## Core Capabilities 73 | 74 | - **Feature Flags** : 75 | Enable or disable specific product features for selected app attributes without deploying new code. 76 | 77 | - **A/B Testing** : 78 | Compare multiple variants of a feature to evaluate performance and identify the best-performing option. 79 | 80 | - **Audience Targeting** : 81 | Apply allocations based on user attributes and the audience segments the user belongs to, ensuring precise and personalized feature delivery. 82 | 83 | - **Traffic Control** : 84 | Regulate what percentage of incoming requests are assigned to experiments to manage system load or sample traffic intelligently. 85 | 86 | - **Multivariate Testing** : 87 | Test multiple variables at once to determine the optimal combination of feature variations and interactions. 88 | 89 | --- 90 | 91 | ## Quick Example 92 | 93 | ### 1. Server Side: Define an Experiment 94 | 95 | ```json 96 | { 97 | "experiment_key": "button-color-test", 98 | "name": "Button Color Experiment", 99 | "status": "LIVE", 100 | "type": "A/B", 101 | "variants": [ 102 | "control": { 103 | "display_name": "Control Group", 104 | "variables": [ 105 | { 106 | "key": "button_color", 107 | "value": "blue", 108 | "data_type": "STRING" 109 | } 110 | ] 111 | }, 112 | "variant1": { 113 | "display_name": "Test Variant 1", 114 | "variables": [ 115 | { 116 | "key": "button_color", 117 | "value": "green", 118 | "data_type": "STRING" 119 | } 120 | ] 121 | } 122 | ], 123 | "variant_weights": { 124 | "control": 50, 125 | "treatment": 50 126 | }, 127 | "distribution_strategy": "RANDOM", 128 | "exposure": 100 129 | } 130 | ``` 131 | 132 | ### 2. Client Side: Get Assignment 133 | 134 | **Android:** 135 | ```kotlin 136 | val experimentPlugin = Ascend.getPlugin(Plugins.EXPERIMENTS) 137 | val buttonColor = experimentPlugin.getExperimentService() 138 | .getStringFlag("button_color", "color") 139 | ``` 140 | 141 | **iOS:** 142 | ```swift 143 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 144 | let buttonColor = experiments.getStringValue(for: "button_color", with: "color") 145 | ``` 146 | 147 | **React Native:** 148 | ```javascript 149 | import { Ascend } from 'react-native-ascend-react-native-sdk'; 150 | const buttonColor = await Ascend.getStringValue('button_color', 'color'); 151 | ``` 152 | 153 | --- 154 | 155 | ## What's Next? 156 | 157 | - Learn [Why Use Ascend](../../introduction/why-use-ascend) over other solutions 158 | - Follow the [Getting Started Guide](../../introduction/getting-started/) to set up your first experiment 159 | - Understand the [Development Setup](../../howto/how-to-contribute) to contribute 160 | - Review the [License Information](https://github.com/dream-horizon-org/ascend/blob/main/LICENSE) for legal details 161 | 162 | --- 163 | 164 | ## Community and Support 165 | 166 | Ascend is built with ❤️ by the open-source community. We welcome contributions, bug reports, and feature requests. 167 | 168 | - **GitHub**: [Ascend Project](https://github.com/dream-horizon-org/ascend) 169 | - **Issues**: [Report bugs and request features](https://github.com/dream-horizon-org/ascend/issues) 170 | - **Discussions**: Use Github discussion 171 | - **Contributing**: See our Contribution Guidelines. 172 | 173 | --- 174 | 175 | *Making experimentation accessible to everyone, everywhere.* -------------------------------------------------------------------------------- /startup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PROJECT_KEY_DEFAULT="my-project" 4 | PROJECT_NAME_DEFAULT="My Project" 5 | ASCEND_DEFAULT_DIR="${HOME}/.ascend" 6 | GIT_ORG="dream-horizon-org" 7 | 8 | REPO_LIST=("testlab" "flockr" "ascend-panel") 9 | BRANCH_LIST=("main" "main" "main") 10 | 11 | # Detect user's preferred git clone protocol (SSH or HTTPS) 12 | get_git_clone_prefix () { 13 | local use_ssh=false 14 | 15 | # Check if user has configured git to use SSH instead of HTTPS 16 | if git config --get url."git@github.com:".insteadOf 2>/dev/null | grep -q "https://github.com/"; then 17 | use_ssh=true 18 | # Check if user has SSH configured in their git config 19 | elif git config --get core.sshCommand &>/dev/null; then 20 | use_ssh=true 21 | # Check if user has GitHub SSH key configured 22 | elif ssh -T git@github.com 2>&1 | grep -q "successfully authenticated"; then 23 | use_ssh=true 24 | fi 25 | 26 | if [ "$use_ssh" = true ]; then 27 | echo "git@github.com:${GIT_ORG}" 28 | else 29 | echo "https://github.com/${GIT_ORG}" 30 | fi 31 | } 32 | 33 | # Create base directory if it doesn't exist 34 | ensure_base_dir () { 35 | local base_dir=$1 36 | 37 | if [ ! -d "$base_dir" ]; then 38 | echo "Creating directory ${base_dir}..." 39 | mkdir -p "$base_dir" || { 40 | echo "❌ Failed to create directory ${base_dir}" 41 | exit 1 42 | } 43 | fi 44 | } 45 | 46 | get_package_manager () { 47 | # Check for apt (Debian/Ubuntu) 48 | if command -v apt &> /dev/null; then 49 | echo "apt" 50 | # Check for yum (RHEL/CentOS) 51 | elif command -v yum &> /dev/null; then 52 | echo "yum" 53 | # Check for dnf (Fedora) 54 | elif command -v dnf &> /dev/null; then 55 | echo "dnf" 56 | # Check for brew (macOS) 57 | elif command -v brew &> /dev/null; then 58 | echo "brew" 59 | else 60 | echo "Package manager not supported. Supported package managers are: apt, yum, dnf, brew." 61 | exit 1 62 | fi 63 | } 64 | 65 | install_git () { 66 | local package_manager=$(get_package_manager) 67 | case $package_manager in 68 | "apt") 69 | sudo apt update 70 | sudo apt install -y git 71 | ;; 72 | "yum") 73 | sudo yum install -y git 74 | ;; 75 | "dnf") 76 | sudo dnf install -y git 77 | ;; 78 | "brew") 79 | sudo brew install git 80 | ;; 81 | *) 82 | echo "Package manager not supported for git install. Please install git manually." 83 | exit 1 84 | esac 85 | echo "git installed successfully." 86 | } 87 | 88 | install_docker () { 89 | local package_manager=$(get_package_manager) 90 | case $package_manager in 91 | "apt") 92 | sudo apt update 93 | sudo apt install -y docker.io 94 | sudo systemctl enable --now docker 95 | ;; 96 | "yum") 97 | sudo yum install -y docker 98 | sudo systemctl enable --now docker 99 | ;; 100 | "dnf") 101 | sudo dnf install -y docker 102 | sudo systemctl enable --now docker 103 | ;; 104 | "brew") 105 | sudo brew install docker 106 | sudo systemctl enable --now docker 107 | ;; 108 | *) 109 | echo "Package manager not supported for docker install. Please install docker manually." 110 | exit 1 111 | esac 112 | echo "docker installed successfully." 113 | } 114 | 115 | # install requirements if not already installed 116 | install_requirements () { 117 | # git 118 | if ! command -v git &> /dev/null; then 119 | echo "git not found, installing..." 120 | install_git 121 | else 122 | echo "git is already installed." 123 | fi 124 | 125 | # docker 126 | if ! command -v docker &> /dev/null; then 127 | echo "docker not found, installing..." 128 | install_docker 129 | else 130 | echo "docker is already installed." 131 | fi 132 | } 133 | 134 | # checkout repos 135 | checkout_repos () { 136 | local base_dir=$1 137 | local force=${2:-false} 138 | local git_prefix=$(get_git_clone_prefix) 139 | 140 | for index in "${!REPO_LIST[@]}"; do 141 | local repo="${REPO_LIST[${index}]}" 142 | local branch="${BRANCH_LIST[${index}]}" 143 | 144 | local repo_url="${git_prefix}/${repo}.git" 145 | local repo_dir="${base_dir}/${repo}" 146 | 147 | # Check if repo already exists 148 | if [ -d "${repo_dir}" ]; then 149 | if [ "$force" = true ]; then 150 | echo "Removing existing ${repo_dir}..." 151 | rm -rf "${repo_dir}" 152 | else 153 | echo "⚠️ Skipping ${repo} - directory already exists at ${repo_dir}" 154 | echo " Use --force to overwrite existing repositories" 155 | continue 156 | fi 157 | fi 158 | 159 | # Clone the repository 160 | echo "Cloning repository from ${repo_url}..." 161 | git clone --branch "${branch}" --single-branch --depth 1 "${repo_url}" "${repo_dir}" 162 | 163 | if [ $? -eq 0 ]; then 164 | echo "✅ Successfully cloned ${repo} into ${repo_dir}" 165 | else 166 | echo "❌ Failed to clone ${repo}" 167 | exit 1 168 | fi 169 | done 170 | } 171 | 172 | start_services () { 173 | local base_dir=$1 174 | local project_key=$2 175 | local project_name=$3 176 | 177 | for repo in "${REPO_LIST[@]}"; do 178 | local repo_dir="${base_dir}/${repo}" 179 | 180 | if [ ! -d "${repo_dir}" ]; then 181 | echo "⚠️ Skipping ${repo} - directory does not exist" 182 | continue 183 | fi 184 | 185 | echo "Starting services for ${repo}..." 186 | cd "${repo_dir}" || continue 187 | 188 | local command="PROJECT_NAME=\"${project_name}\" PROJECT_KEY=${project_key} docker compose up -d" 189 | if eval "${command}" 2>/dev/null; then 190 | echo "✅ Started ${repo} services" 191 | else 192 | echo "❌ Failed to start services for ${repo}" 193 | fi 194 | done 195 | } 196 | 197 | print_usage () { 198 | echo "Usage: $0 [OPTIONS]" 199 | echo "" 200 | echo "Options:" 201 | echo " -d, --dir DIR Installation directory (default: ${ASCEND_DEFAULT_DIR})" 202 | echo " -p, --project-key KEY Project key for the first project (default: ${PROJECT_KEY_DEFAULT})" 203 | echo " -n, --project-name NAME Project name for the first project (default: ${PROJECT_NAME_DEFAULT})" 204 | echo " -f, --force Overwrite existing repositories" 205 | echo " -h, --help Show this help message" 206 | echo "" 207 | echo "Examples:" 208 | echo " $0 # Use all defaults" 209 | echo " $0 -d /opt/ascend # Custom install directory" 210 | echo " $0 -p my-app # Custom project key" 211 | echo " $0 -n \"My App\" # Custom project name" 212 | echo " $0 -p my-app -n \"My App\" # Custom project key and name" 213 | echo " $0 -d /opt/ascend -p my-app -n \"My App\" # All custom values" 214 | } 215 | 216 | main () { 217 | local base_dir="${ASCEND_DEFAULT_DIR}" 218 | local project_key="${PROJECT_KEY_DEFAULT}" 219 | local project_name="${PROJECT_NAME_DEFAULT}" 220 | local force=false 221 | 222 | # Parse arguments 223 | while [[ $# -gt 0 ]]; do 224 | case "$1" in 225 | -d|--dir) 226 | base_dir="$2" 227 | shift 2 228 | ;; 229 | -p|--project-key) 230 | project_key="$2" 231 | shift 2 232 | ;; 233 | -n|--project-name) 234 | project_name="$2" 235 | shift 2 236 | ;; 237 | -f|--force) 238 | force=true 239 | shift 240 | ;; 241 | -h|--help) 242 | print_usage 243 | exit 0 244 | ;; 245 | *) 246 | echo "Unknown option: $1" 247 | print_usage 248 | exit 1 249 | ;; 250 | esac 251 | done 252 | 253 | echo "🚀 Installing Ascend to ${base_dir}..." 254 | echo "" 255 | 256 | echo "📦 Installing requirements..." 257 | install_requirements 258 | 259 | echo "" 260 | echo "📁 Setting up installation directory..." 261 | ensure_base_dir "${base_dir}" 262 | 263 | echo "" 264 | echo "📥 Checking out repositories..." 265 | checkout_repos "${base_dir}" "${force}" 266 | 267 | echo "" 268 | echo "🐳 Starting dependent services..." 269 | start_services "${base_dir}" "${project_key}" "${project_name}" 270 | 271 | echo "" 272 | echo "✅ Ascend installed successfully in ${base_dir}" 273 | echo "🌐 Ascend started at http://localhost:9000" 274 | } 275 | 276 | main "$@" 277 | -------------------------------------------------------------------------------- /docs/src/styles/custom.css: -------------------------------------------------------------------------------- 1 | @import './command-flag-table.css'; 2 | 3 | /* Dark mode colors. */ 4 | :root { 5 | --sl-color-accent-low: #1b252c; 6 | --sl-color-accent: #496f86; 7 | --sl-color-accent-high: #bdcbd3; 8 | --sl-color-white: #ffffff; 9 | --sl-color-gray-1: #edeeed; 10 | --sl-color-gray-2: #c1c2c2; 11 | --sl-color-gray-3: #8a8c8b; 12 | --sl-color-gray-4: #575858; 13 | --sl-color-gray-5: #373938; 14 | --sl-color-gray-6: #252727; 15 | --sl-color-black: #181818; 16 | } 17 | /* Light mode colors. */ 18 | :root[data-theme='light'] { 19 | --sl-color-accent-low: #ced9df; 20 | --sl-color-accent: #497086; 21 | --sl-color-accent-high: #24353f; 22 | --sl-color-white: #181818; 23 | --sl-color-gray-1: #252727; 24 | --sl-color-gray-2: #373938; 25 | --sl-color-gray-3: #575858; 26 | --sl-color-gray-4: #8a8c8b; 27 | --sl-color-gray-5: #c1c2c2; 28 | --sl-color-gray-6: #edeeed; 29 | --sl-color-gray-7: #f6f6f6; 30 | --sl-color-black: #ffffff; 31 | } 32 | 33 | /* Normalize sidebar group labels - remove bold and match font size */ 34 | .sidebar details > summary, 35 | .sidebar details > summary span, 36 | .sidebar li > details > summary { 37 | font-weight: 400 !important; 38 | font-size: var(--sl-text-sm) !important; 39 | color: var(--sl-color-gray-2) !important; 40 | } 41 | 42 | .sidebar details > summary:hover { 43 | color: var(--sl-color-white) !important; 44 | } 45 | 46 | .sidebar details[open] > summary, 47 | .sidebar li > details[open] > summary { 48 | font-weight: 400 !important; 49 | font-size: var(--sl-text-sm) !important; 50 | color: var(--sl-color-gray-2) !important; 51 | } 52 | 53 | /* Specifically target nested group labels like Fields and Metadata */ 54 | .sidebar li li > details > summary { 55 | font-weight: 400 !important; 56 | font-size: var(--sl-text-sm) !important; 57 | } 58 | 59 | /* Enhanced table styling */ 60 | .sl-markdown-content table { 61 | width: 100%; 62 | border-collapse: separate; 63 | border-spacing: 0; 64 | margin: 2.0rem 0; 65 | font-size: 1rem; 66 | box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); 67 | border-radius: 8px; 68 | overflow: hidden; 69 | table-layout: auto; 70 | line-height: 1.6; 71 | display: flex; 72 | flex-direction: column; 73 | } 74 | 75 | /* Column width distribution for 2-column tables */ 76 | .sl-markdown-content table th:nth-child(1):nth-last-child(2), 77 | .sl-markdown-content table td:nth-child(1):nth-last-child(2) { 78 | width: 35%; 79 | font-weight: 600; 80 | } 81 | 82 | .sl-markdown-content table th:nth-child(2):nth-last-child(1), 83 | .sl-markdown-content table td:nth-child(2):nth-last-child(1) { 84 | width: 65%; 85 | } 86 | 87 | /* Column width distribution for 5-column tables */ 88 | .sl-markdown-content table th:nth-child(1):nth-last-child(5), 89 | .sl-markdown-content table td:nth-child(1):nth-last-child(5), 90 | .sl-markdown-content table th:nth-child(1):nth-last-child(5) ~ th, 91 | .sl-markdown-content table td:nth-child(1):nth-last-child(5) ~ td { 92 | width: auto; 93 | } 94 | 95 | .sl-markdown-content table th:nth-child(1):nth-last-child(5), 96 | .sl-markdown-content table td:nth-child(1):nth-last-child(5) { 97 | width: 16%; 98 | } 99 | 100 | .sl-markdown-content table th:nth-child(2):nth-last-child(4), 101 | .sl-markdown-content table td:nth-child(2):nth-last-child(4) { 102 | width: 10%; 103 | } 104 | 105 | .sl-markdown-content table th:nth-child(3):nth-last-child(3), 106 | .sl-markdown-content table td:nth-child(3):nth-last-child(3) { 107 | width: 36%; 108 | } 109 | 110 | .sl-markdown-content table th:nth-child(4):nth-last-child(2), 111 | .sl-markdown-content table td:nth-child(4):nth-last-child(2) { 112 | width: 18%; 113 | } 114 | 115 | .sl-markdown-content table th:nth-child(5):nth-last-child(1), 116 | .sl-markdown-content table td:nth-child(5):nth-last-child(1) { 117 | width: 20%; 118 | } 119 | 120 | /* Column width distribution for 4-column tables (built-in-functions) */ 121 | .sl-markdown-content table th:nth-child(1):nth-last-child(4), 122 | .sl-markdown-content table td:nth-child(1):nth-last-child(4) { 123 | width: 14%; 124 | } 125 | 126 | .sl-markdown-content table th:nth-child(2):nth-last-child(3), 127 | .sl-markdown-content table td:nth-child(2):nth-last-child(3) { 128 | width: 40%; 129 | } 130 | 131 | .sl-markdown-content table th:nth-child(3):nth-last-child(2), 132 | .sl-markdown-content table td:nth-child(3):nth-last-child(2) { 133 | width: 18%; 134 | } 135 | 136 | .sl-markdown-content table th:nth-child(4):nth-last-child(1), 137 | .sl-markdown-content table td:nth-child(4):nth-last-child(1) { 138 | width: 28%; 139 | } 140 | 141 | .sl-markdown-content thead { 142 | background: var(--sl-color-accent); 143 | color: #ffffff !important; 144 | font-weight: 600; 145 | text-transform: uppercase; 146 | font-size: 0.85rem; 147 | letter-spacing: 0.05em; 148 | width: 100%; 149 | display: flex; 150 | align-items: center; 151 | } 152 | 153 | .sl-markdown-content thead th { 154 | text-align: left; 155 | border: none; 156 | color: #ffffff !important; 157 | word-wrap: break-word; 158 | overflow-wrap: break-word; 159 | line-height: 1.6; 160 | font-size: 0.875rem; 161 | width: 100%; 162 | justify-content: space-between; 163 | padding: 0; 164 | margin: 0; 165 | 166 | } 167 | .sl-markdown-content thead tr { 168 | display: flex; 169 | width: 100%; 170 | align-items: center; 171 | padding: 25px 25px; 172 | } 173 | 174 | .sl-markdown-content tbody tr { 175 | border-bottom: 1px solid var(--sl-color-gray-6); 176 | transition: background-color 0.2s ease; 177 | min-height: 5rem; 178 | width: 100%; 179 | display: flex; 180 | margin-top: 0px; 181 | } 182 | 183 | 184 | .sl-markdown-content tbody tr:hover { 185 | background-color: var(--sl-color-gray-7); 186 | } 187 | 188 | .sl-markdown-content tbody tr:last-child { 189 | border-bottom: none; 190 | } 191 | 192 | .sl-markdown-content tbody td { 193 | padding: 10px 10px; 194 | border: none; 195 | word-wrap: break-word; 196 | overflow-wrap: break-word; 197 | vertical-align: middle; 198 | line-height: 1.8; 199 | font-size: 1rem; 200 | width: 100%; 201 | display: flex; 202 | padding-left: 25px; 203 | align-items: center; 204 | } 205 | .sl-markdown-content tbody{ 206 | margin-top: 0px; 207 | width: 100%; 208 | } 209 | 210 | .sl-markdown-content tbody td:first-child { 211 | font-weight: 600; 212 | color: var(--sl-color-accent-high); 213 | font-size: 0.95rem; 214 | } 215 | 216 | .sl-markdown-content tbody td code { 217 | background: var(--sl-color-gray-6); 218 | border-radius: 4px; 219 | font-size: 0.85em; 220 | } 221 | 222 | /* Responsive tables */ 223 | @media (max-width: 768px) { 224 | .sl-markdown-content table { 225 | font-size: 0.85rem; 226 | } 227 | 228 | .sl-markdown-content thead th, 229 | .sl-markdown-content tbody td { 230 | padding: 1.25rem 1.5rem; 231 | width: 100%; 232 | } 233 | } 234 | 235 | /* Optimize layout - make TOC compact and right-aligned */ 236 | .page { 237 | grid-template-columns: var(--sl-sidebar-width) minmax(0, var(--sl-content-width)) 0fr !important; 238 | gap: 1rem 2rem !important; 239 | margin-right: 0 !important; 240 | padding-right: 0 !important; 241 | } 242 | 243 | /* Make TOC compact and truly right-aligned */ 244 | .right-sidebar { 245 | position: fixed !important; 246 | right: 1rem !important; 247 | top: var(--sl-nav-height) !important; 248 | width: auto !important; 249 | max-width: 15rem !important; 250 | padding: 1rem 0 1rem 1rem !important; 251 | margin: 0 !important; 252 | height: calc(100vh - var(--sl-nav-height)) !important; 253 | overflow-y: auto !important; 254 | border-left: 1px solid var(--sl-color-gray-5) !important; 255 | } 256 | 257 | .right-sidebar-panel { 258 | width: 100% !important; 259 | padding: 0 0 0 0.75rem !important; 260 | margin: 0 !important; 261 | } 262 | 263 | .right-sidebar-panel h2 { 264 | font-size: 0.9rem !important; 265 | } 266 | 267 | .right-sidebar-panel nav { 268 | font-size: 0.85rem !important; 269 | } 270 | 271 | .right-sidebar-panel nav ul { 272 | padding-left: 0.5rem !important; 273 | } 274 | 275 | /* Increase main content max-width and reduce sidebar */ 276 | :root { 277 | --sl-content-width: 60rem; 278 | --sl-sidebar-width: 15rem; 279 | } 280 | 281 | .quick-links { 282 | display: grid; 283 | grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); 284 | gap: 1rem; 285 | margin-top: 1rem; 286 | } 287 | 288 | .quick-link { 289 | display: flex; 290 | flex-direction: row; 291 | align-items: flex-start; 292 | text-decoration: none; 293 | border: 1px solid #e0e0e0; 294 | border-radius: 8px; 295 | padding: 1.25rem; 296 | transition: all 0.2s ease; 297 | background-color: var(--ifm-background-surface-color); 298 | } 299 | 300 | .quick-links > :nth-child(3) { 301 | grid-column: 1 / -1; 302 | } 303 | 304 | .quick-link:hover { 305 | border-color: #8c8c8c; 306 | transform: translateY(-2px); 307 | } 308 | 309 | .quick-link-icon { 310 | font-size: 1.5rem; 311 | margin-right: 0.75rem; 312 | color: #2c3e50; 313 | margin-top: 12px; 314 | } 315 | 316 | .quick-link-content h4 { 317 | margin: 0; 318 | font-weight: 600; 319 | color: #1a237e; 320 | } 321 | 322 | .quick-link-content p { 323 | margin: 0.25rem 0 0; 324 | color: #37474f; 325 | font-size: 0.9rem; 326 | } 327 | -------------------------------------------------------------------------------- /docs/src/content/docs/sdks/kotlin/api.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: API 3 | description: API 4 | --- 5 | 6 | This section documents the all APIs of experiments and events. The methods are only available through Ascend. To get all the methods, use following: 7 | 8 | ```kotlin 9 | // This returns the object which contains all the methods 10 | 11 | val experimentPlugin = Ascend.getPlugin(Plugins.EXPERIMENTS) 12 | 13 | // console this object to see what are available methods here. 14 | println(experimentPlugin) 15 | ``` 16 | 17 | --- 18 | 19 | ## Experiments 20 | 21 | | Method | Return Type | 22 | |--------|-------------| 23 | | `getBooleanFlag(experiment_key: String, variable: String)` | `Boolean` | 24 | | `getIntFlag(experiment_key: String, variable: String)` | `Int` | 25 | | `getDoubleFlag(experiment_key: String, variable: String)` | `Double` | 26 | | `getLongFlag(experiment_key: String, variable: String)` | `Long` | 27 | | `getStringFlag(experiment_key: String, variable: String)` | `String` | 28 | | `getAllVariables(experiment_key: String)` | `JsonObject?` | 29 | | `getExperimentVariants()` | `HashMap` | 30 | | `getExperimentsFromStorage()` | `HashMap` | 31 | | `fetchExperiments(map: HashMap, callback: IExperimentCallback)` | `Unit` | 32 | | `refreshExperiment(callback: IExperimentCallback)` | `Unit` | 33 | | `setExperimentsToStorage(experiments: HashMap)` | `Unit` | 34 | 35 | --- 36 | 37 | ## Method Details 38 | 39 | ### 1. getBooleanFlag 40 | Retrieves a boolean value from an experiment 41 | 42 | **Parameters:** 43 | - `experiment_key`: The experiment identifier (e.g., "feature_toggle") 44 | - `variable`: Variable name within the experiment (default: "value") 45 | - `dontCache`: If true, doesn't cache the result in memory 46 | - `ignoreCache`: If true, bypasses the cache and fetches fresh 47 | 48 | **Returns:** Boolean value with fallback to false 49 | 50 | **Fallback Order:** 51 | 1. Accessed cache (if ignoreCache = false) 52 | 2. Experiment map (fetched from server) 53 | 3. Default map (provided at initialization) 54 | 4. Hard-coded false 55 | 56 | --- 57 | 58 | ### 2. getIntFlag 59 | Retrieves an integer value from an experiment 60 | 61 | **Parameters:** 62 | - `experiment_key`: The experiment identifier (e.g., "retry_config") 63 | - `variable`: Variable name within the experiment (default: "max_attempts") 64 | - `dontCache`: If true, doesn't cache the result in memory 65 | - `ignoreCache`: If true, bypasses the cache and fetches fresh 66 | 67 | **Returns:** Integer value with fallback to -1 68 | 69 | **Fallback Order:** 70 | 1. Accessed cache (if ignoreCache = false) 71 | 2. Experiment map (fetched from server) 72 | 3. Default map (provided at initialization) 73 | 4. Hard-coded -1 74 | 75 | --- 76 | 77 | ### 3. getDoubleFlag 78 | Retrieves a double value from an experiment 79 | 80 | **Parameters:** 81 | - `experiment_key`: The experiment identifier (e.g., "network_config") 82 | - `variable`: Variable name within the experiment (default: "timeout") 83 | - `dontCache`: If true, doesn't cache the result in memory 84 | - `ignoreCache`: If true, bypasses the cache and fetches fresh 85 | 86 | **Returns:** Double value with fallback to -1.0 87 | 88 | **Fallback Order:** 89 | 1. Accessed cache (if ignoreCache = false) 90 | 2. Experiment map (fetched from server) 91 | 3. Default map (provided at initialization) 92 | 4. Hard-coded -1.0 93 | 94 | --- 95 | 96 | ### 4. getLongFlag 97 | Retrieves a long value from an experiment 98 | 99 | **Parameters:** 100 | - `experiment_key`: The experiment identifier (e.g., "cache_config") 101 | - `variable`: Variable name within the experiment (default: "ttl") 102 | - `dontCache`: If true, doesn't cache the result in memory 103 | - `ignoreCache`: If true, bypasses the cache and fetches fresh 104 | 105 | **Returns:** Long value with fallback to -1L 106 | 107 | **Fallback Order:** 108 | 1. Accessed cache (if ignoreCache = false) 109 | 2. Experiment map (fetched from server) 110 | 3. Default map (provided at initialization) 111 | 4. Hard-coded -1L 112 | 113 | --- 114 | 115 | ### 5. getStringFlag 116 | Retrieves a string value from an experiment 117 | 118 | **Parameters:** 119 | - `experiment_key`: The experiment identifier (e.g., "button_exp_test") 120 | - `variable`: Variable name within the experiment (default: "color") 121 | - `dontCache`: If true, doesn't cache the result in memory 122 | - `ignoreCache`: If true, bypasses the cache and fetches fresh 123 | 124 | **Returns:** String value with fallback to "" (empty string) 125 | 126 | **Fallback Order:** 127 | 1. Accessed cache (if ignoreCache = false) 128 | 2. Experiment map (fetched from server) 129 | 3. Default map (provided at initialization) 130 | 4. Hard-coded "" 131 | 132 | --- 133 | 134 | ### 6. getAllVariables 135 | Retrieves all variables for a specific experiment as a JsonObject 136 | 137 | **Parameters:** 138 | - `experiment_key`: The experiment identifier 139 | 140 | **Returns:** JsonObject? containing all variables, or default values if not found 141 | 142 | **Fallback Order:** 143 | 1. Experiment map (fetched from server) 144 | 2. Default map (provided at initialization) 145 | 3. Hard-coded null 146 | 147 | --- 148 | 149 | ### 7. fetchExperiments 150 | Fetches experiments from the server on-demand 151 | 152 | **Parameters:** 153 | - `map`: HashMap of experiment experiment_keys with their default values 154 | - `callback`: Callback interface for success/failure handling 155 | 156 | **Behavior:** 157 | - Appends the provided default values to the default map 158 | - Triggers network requests to fetch the experiments - API CALL - getOnDemandData(newexperiment_keys, callback) -> getRemoteData(request, callback) 159 | 160 | **getRemoteData:** 161 | - If defaultMap is empty immediately call onSuccess() and return 162 | - Adds custom headers to the HTTP request like last-modified and user-id 163 | - Make the API call and if success: 164 | - update Headers if shouldRefreshDRSOnForeground is true in config 165 | - update experimentMap 166 | - remove unused experiments (not in defaultMap) 167 | - Persists experimentMap to SharedPreferences 168 | - Calls onSuccess() or onFailure() on the callback 169 | 170 | --- 171 | 172 | ### 8. refreshExperiment 173 | Fetches experiments using predefined experiment_keys in the default map 174 | 175 | **Parameters:** 176 | - `callback`: Callback interface for success/failure handling 177 | 178 | **Behavior:** 179 | - Builds a DRSExperimentRequest from the keys of the defaultMap 180 | - Triggers network request to fetch experiments 181 | - API CALL - getRemoteWithPreDefinedRequest(callback) -> getRemoteData(request, callback) 182 | 183 | **getRemoteData:** 184 | - If defaultMap is empty immediately call onSuccess() and return 185 | - Updates HTTP headers (adds guest-id/user-id/custom headers via updateHeaderMaps) 186 | - Makes the API call (RequestType.FETCH_EXPERIMENTS → IApi.getDRSExperiments 187 | - On success: 188 | - If shouldRefreshDRSOnForeground is true, updates caching headers (e.g., cache window/last modified) from the response 189 | - Parses response to ExperimentDetails list and updates experimentMap 190 | - Removes experiments not present in defaultMap 191 | - Persists experimentMap to SharedPreferences 192 | - Calls onSuccess() on the callback (Main thread) 193 | - On error or exception: 194 | - Processes error (including 304 handling) and calls onFailure() with the appropriate Throwable (Main thread) 195 | 196 | --- 197 | 198 | ### 9. getExperimentVariants 199 | Returns a copy of all currently loaded experiment variants 200 | 201 | **Parameters:** 202 | - None 203 | 204 | **Behavior:** 205 | - Returns a `HashMap` where: 206 | - Key: experiment_key (experiment identifier) 207 | - Value: ExperimentDetails object containing: 208 | - experimentId: Unique experiment identifier 209 | - experiment_key: Experiment endpoint path 210 | - variantName: Name of the assigned variant 211 | - variables: JsonObject containing all experiment variables/values 212 | - Creates a new `HashMap` copy of the mediator's experimentMap 213 | - No network calls - purely returns cached data 214 | - Data source: experimentMap populated from: 215 | - Initial load from SharedPreferences (on startup) 216 | - Updates from successful API responses (refreshExperiment/fetchExperiments) 217 | - Returns empty `HashMap` if no experiments have been loaded yet 218 | 219 | --- 220 | 221 | ### 10. setExperimentsToStorage 222 | Persists experiment data to local storage and updates the in-memory cache 223 | 224 | **Parameters:** 225 | - `experiments`: `HashMap` containing experiment data to persist 226 | 227 | **Behavior:** 228 | - Converts the provided `HashMap` to a `ConcurrentHashMap` for thread safety 229 | - Persists the experiments data to SharedPreferences via the mediator 230 | - Updates the mediator's experimentMap with the new data 231 | - No network calls - purely local storage operation 232 | - Used for manually setting experiment data (e.g., from external sources or testing) 233 | 234 | **Return/Fallback Order:** 235 | - No return value - This is a void method 236 | - No fallback mechanism - If persistence fails, the error is logged but not propagated 237 | - Immediate effect - Data is immediately available in memory after calling this method 238 | - Storage failure handling - If SharedPreferences write fails, the in-memory map is still updated, so experiments remain available for the current session 239 | 240 | --- 241 | 242 | ### 11. getExperimentsFromStorage 243 | Retrieves experiment data from local storage (SharedPreferences) 244 | 245 | **Parameters:** 246 | - None 247 | 248 | **Behavior:** 249 | - Returns a `HashMap` containing persisted experiment data 250 | - Reads from SharedPreferences using the DRS_EXPERIMENTS_PREF_KEY 251 | - No network calls - purely local storage operation 252 | - Used to retrieve previously saved experiment data 253 | 254 | **Return/Fallback Order:** 255 | - Returns: `HashMap` - the persisted experiment data 256 | - Fallback mechanism: 257 | - If SharedPreferences is empty or corrupted, returns empty `HashMap` 258 | - If JSON deserialization fails, returns empty `HashMap` 259 | - No exception thrown - gracefully handles storage issues 260 | - Data source priority: Only reads from local storage, does not check in-memory cache 261 | 262 | --- 263 | 264 | ## Events 265 | 266 | Coming soon... -------------------------------------------------------------------------------- /docs/src/content/docs/sdks/react-native/api.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: API 3 | description: API 4 | --- 5 | 6 | This section documents the all APIs of experiments and events. The methods are only available through Ascend. To get all the methods, use following: 7 | 8 | ```kotlin 9 | // This returns the object which contains all the methods 10 | 11 | val experimentPlugin = Ascend.getPlugin(Plugins.EXPERIMENTS) 12 | 13 | // console this object to see what are available methods here. 14 | println(experimentPlugin) 15 | ``` 16 | 17 | --- 18 | 19 | ## Experiments 20 | 21 | | Method | Return Type | 22 | |--------|-------------| 23 | | `getBooleanFlag(experiment_key: String, variable: String)` | `Boolean` | 24 | | `getIntFlag(experiment_key: String, variable: String)` | `Int` | 25 | | `getDoubleFlag(experiment_key: String, variable: String)` | `Double` | 26 | | `getLongFlag(experiment_key: String, variable: String)` | `Long` | 27 | | `getStringFlag(experiment_key: String, variable: String)` | `String` | 28 | | `getAllVariables(experiment_key: String)` | `JsonObject?` | 29 | | `getExperimentVariants()` | `HashMap` | 30 | | `getExperimentsFromStorage()` | `HashMap` | 31 | | `fetchExperiments(map: HashMap, callback: IExperimentCallback)` | `Unit` | 32 | | `refreshExperiment(callback: IExperimentCallback)` | `Unit` | 33 | | `setExperimentsToStorage(experiments: HashMap)` | `Unit` | 34 | 35 | --- 36 | 37 | ## Method Details 38 | 39 | ### 1. getBooleanFlag 40 | Retrieves a boolean value from an experiment 41 | 42 | **Parameters:** 43 | - `experiment_key`: The experiment identifier (e.g., "feature_toggle") 44 | - `variable`: Variable name within the experiment (default: "value") 45 | - `dontCache`: If true, doesn't cache the result in memory 46 | - `ignoreCache`: If true, bypasses the cache and fetches fresh 47 | 48 | **Returns:** Boolean value with fallback to false 49 | 50 | **Fallback Order:** 51 | 1. Accessed cache (if ignoreCache = false) 52 | 2. Experiment map (fetched from server) 53 | 3. Default map (provided at initialization) 54 | 4. Hard-coded false 55 | 56 | --- 57 | 58 | ### 2. getIntFlag 59 | Retrieves an integer value from an experiment 60 | 61 | **Parameters:** 62 | - `experiment_key`: The experiment identifier (e.g., "retry_config") 63 | - `variable`: Variable name within the experiment (default: "max_attempts") 64 | - `dontCache`: If true, doesn't cache the result in memory 65 | - `ignoreCache`: If true, bypasses the cache and fetches fresh 66 | 67 | **Returns:** Integer value with fallback to -1 68 | 69 | **Fallback Order:** 70 | 1. Accessed cache (if ignoreCache = false) 71 | 2. Experiment map (fetched from server) 72 | 3. Default map (provided at initialization) 73 | 4. Hard-coded -1 74 | 75 | --- 76 | 77 | ### 3. getDoubleFlag 78 | Retrieves a double value from an experiment 79 | 80 | **Parameters:** 81 | - `experiment_key`: The experiment identifier (e.g., "network_config") 82 | - `variable`: Variable name within the experiment (default: "timeout") 83 | - `dontCache`: If true, doesn't cache the result in memory 84 | - `ignoreCache`: If true, bypasses the cache and fetches fresh 85 | 86 | **Returns:** Double value with fallback to -1.0 87 | 88 | **Fallback Order:** 89 | 1. Accessed cache (if ignoreCache = false) 90 | 2. Experiment map (fetched from server) 91 | 3. Default map (provided at initialization) 92 | 4. Hard-coded -1.0 93 | 94 | --- 95 | 96 | ### 4. getLongFlag 97 | Retrieves a long value from an experiment 98 | 99 | **Parameters:** 100 | - `experiment_key`: The experiment identifier (e.g., "cache_config") 101 | - `variable`: Variable name within the experiment (default: "ttl") 102 | - `dontCache`: If true, doesn't cache the result in memory 103 | - `ignoreCache`: If true, bypasses the cache and fetches fresh 104 | 105 | **Returns:** Long value with fallback to -1L 106 | 107 | **Fallback Order:** 108 | 1. Accessed cache (if ignoreCache = false) 109 | 2. Experiment map (fetched from server) 110 | 3. Default map (provided at initialization) 111 | 4. Hard-coded -1L 112 | 113 | --- 114 | 115 | ### 5. getStringFlag 116 | Retrieves a string value from an experiment 117 | 118 | **Parameters:** 119 | - `experiment_key`: The experiment identifier (e.g., "button_exp_test") 120 | - `variable`: Variable name within the experiment (default: "color") 121 | - `dontCache`: If true, doesn't cache the result in memory 122 | - `ignoreCache`: If true, bypasses the cache and fetches fresh 123 | 124 | **Returns:** String value with fallback to "" (empty string) 125 | 126 | **Fallback Order:** 127 | 1. Accessed cache (if ignoreCache = false) 128 | 2. Experiment map (fetched from server) 129 | 3. Default map (provided at initialization) 130 | 4. Hard-coded "" 131 | 132 | --- 133 | 134 | ### 6. getAllVariables 135 | Retrieves all variables for a specific experiment as a JsonObject 136 | 137 | **Parameters:** 138 | - `experiment_key`: The experiment identifier 139 | 140 | **Returns:** JsonObject? containing all variables, or default values if not found 141 | 142 | **Fallback Order:** 143 | 1. Experiment map (fetched from server) 144 | 2. Default map (provided at initialization) 145 | 3. Hard-coded null 146 | 147 | --- 148 | 149 | ### 7. fetchExperiments 150 | Fetches experiments from the server on-demand 151 | 152 | **Parameters:** 153 | - `map`: HashMap of experiment experiment_keys with their default values 154 | - `callback`: Callback interface for success/failure handling 155 | 156 | **Behavior:** 157 | - Appends the provided default values to the default map 158 | - Triggers network requests to fetch the experiments - API CALL - getOnDemandData(newexperiment_keys, callback) -> getRemoteData(request, callback) 159 | 160 | **getRemoteData:** 161 | - If defaultMap is empty immediately call onSuccess() and return 162 | - Adds custom headers to the HTTP request like last-modified and user-id 163 | - Make the API call and if success: 164 | - update Headers if shouldRefreshDRSOnForeground is true in config 165 | - update experimentMap 166 | - remove unused experiments (not in defaultMap) 167 | - Persists experimentMap to SharedPreferences 168 | - Calls onSuccess() or onFailure() on the callback 169 | 170 | --- 171 | 172 | ### 8. refreshExperiment 173 | Fetches experiments using predefined experiment_keys in the default map 174 | 175 | **Parameters:** 176 | - `callback`: Callback interface for success/failure handling 177 | 178 | **Behavior:** 179 | - Builds a DRSExperimentRequest from the keys of the defaultMap 180 | - Triggers network request to fetch experiments 181 | - API CALL - getRemoteWithPreDefinedRequest(callback) -> getRemoteData(request, callback) 182 | 183 | **getRemoteData:** 184 | - If defaultMap is empty immediately call onSuccess() and return 185 | - Updates HTTP headers (adds guest-id/user-id/custom headers via updateHeaderMaps) 186 | - Makes the API call (RequestType.FETCH_EXPERIMENTS → IApi.getDRSExperiments 187 | - On success: 188 | - If shouldRefreshDRSOnForeground is true, updates caching headers (e.g., cache window/last modified) from the response 189 | - Parses response to ExperimentDetails list and updates experimentMap 190 | - Removes experiments not present in defaultMap 191 | - Persists experimentMap to SharedPreferences 192 | - Calls onSuccess() on the callback (Main thread) 193 | - On error or exception: 194 | - Processes error (including 304 handling) and calls onFailure() with the appropriate Throwable (Main thread) 195 | 196 | --- 197 | 198 | ### 9. getExperimentVariants 199 | Returns a copy of all currently loaded experiment variants 200 | 201 | **Parameters:** 202 | - None 203 | 204 | **Behavior:** 205 | - Returns a `HashMap` where: 206 | - Key: experiment_key (experiment identifier) 207 | - Value: ExperimentDetails object containing: 208 | - experimentId: Unique experiment identifier 209 | - experiment_key: Experiment endpoint path 210 | - variantName: Name of the assigned variant 211 | - variables: JsonObject containing all experiment variables/values 212 | - Creates a new `HashMap` copy of the mediator's experimentMap 213 | - No network calls - purely returns cached data 214 | - Data source: experimentMap populated from: 215 | - Initial load from SharedPreferences (on startup) 216 | - Updates from successful API responses (refreshExperiment/fetchExperiments) 217 | - Returns empty `HashMap` if no experiments have been loaded yet 218 | 219 | --- 220 | 221 | ### 10. setExperimentsToStorage 222 | Persists experiment data to local storage and updates the in-memory cache 223 | 224 | **Parameters:** 225 | - `experiments`: `HashMap` containing experiment data to persist 226 | 227 | **Behavior:** 228 | - Converts the provided `HashMap` to a `ConcurrentHashMap` for thread safety 229 | - Persists the experiments data to SharedPreferences via the mediator 230 | - Updates the mediator's experimentMap with the new data 231 | - No network calls - purely local storage operation 232 | - Used for manually setting experiment data (e.g., from external sources or testing) 233 | 234 | **Return/Fallback Order:** 235 | - No return value - This is a void method 236 | - No fallback mechanism - If persistence fails, the error is logged but not propagated 237 | - Immediate effect - Data is immediately available in memory after calling this method 238 | - Storage failure handling - If SharedPreferences write fails, the in-memory map is still updated, so experiments remain available for the current session 239 | 240 | --- 241 | 242 | ### 11. getExperimentsFromStorage 243 | Retrieves experiment data from local storage (SharedPreferences) 244 | 245 | **Parameters:** 246 | - None 247 | 248 | **Behavior:** 249 | - Returns a `HashMap` containing persisted experiment data 250 | - Reads from SharedPreferences using the DRS_EXPERIMENTS_PREF_KEY 251 | - No network calls - purely local storage operation 252 | - Used to retrieve previously saved experiment data 253 | 254 | **Return/Fallback Order:** 255 | - Returns: `HashMap` - the persisted experiment data 256 | - Fallback mechanism: 257 | - If SharedPreferences is empty or corrupted, returns empty `HashMap` 258 | - If JSON deserialization fails, returns empty `HashMap` 259 | - No exception thrown - gracefully handles storage issues 260 | - Data source priority: Only reads from local storage, does not check in-memory cache 261 | 262 | --- 263 | 264 | ## Events 265 | 266 | Coming soon... -------------------------------------------------------------------------------- /docs/src/content/docs/sdks/ios/api.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: API 3 | description: API 4 | --- 5 | 6 | This section documents all APIs of experiments and events. The methods are only available through Ascend. To get all the methods, use following: 7 | 8 | ```swift 9 | // This returns the object which contains all the methods 10 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 11 | 12 | // Print this object to see what are available methods here 13 | print(experiments) 14 | ``` 15 | 16 | --- 17 | 18 | ## Experiments 19 | 20 | | Method | Return Type | 21 | |--------|-------------| 22 | | `getBoolValue(for:with:dontCache:ignoreCache:)` | `Bool` | 23 | | `getIntValue(for:with:dontCache:ignoreCache:)` | `Int` | 24 | | `getDoubleValue(for:with:dontCache:ignoreCache:)` | `Double` | 25 | | `getLongValue(for:with:dontCache:ignoreCache:)` | `Int64` | 26 | | `getStringValue(for:with:dontCache:ignoreCache:)` | `String` | 27 | | `getAllVariablesJSON(for:)` | `String?` | 28 | | `getExperimentVariants()` | `[String: ExperimentVariant]` | 29 | | `getExperimentVariantsJSON()` | `String?` | 30 | | `getExperimentsFromStorage()` | `[String: Experiment]?` | 31 | | `fetchExperiments(for:completion:)` | `Void` (completion handler) | 32 | | `refreshExperiments(completion:)` | `Void` (completion handler) | 33 | | `setExperimentsToStorage(_:)` | `Void` | 34 | | `getDefaultExperimentValues()` | `[String: ExperimentVariable]` | 35 | | `loadExperimentsData(completion:)` | `Void` (completion handler) | 36 | | `clearAllExperimentsData()` | `Void` | 37 | 38 | --- 39 | 40 | ## Method Details 41 | 42 | ### 1. getBoolValue 43 | 44 | Retrieves a boolean value from an experiment 45 | 46 | **Parameters:** 47 | 48 | - `for experimentKey`: The experiment identifier (e.g., "feature_toggle") 49 | - `with variable`: Variable name within the experiment (required) 50 | - `dontCache`: If true, doesn't cache the result in memory (default: false) 51 | - `ignoreCache`: If true, bypasses the cache and fetches fresh (default: false) 52 | 53 | **Returns:** Boolean value with fallback to false 54 | 55 | **Fallback Order:** 56 | 57 | 1. Accessed cache (if ignoreCache = false) 58 | 2. Experiment map (fetched from server) 59 | 3. Default map (provided at initialization) 60 | 4. Hard-coded false 61 | 62 | **Example:** 63 | 64 | ```swift 65 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 66 | let isEnabled = experiments.getBoolValue(for: "feature_toggle", with: "enabled") 67 | ``` 68 | 69 | --- 70 | 71 | ### 2. getIntValue 72 | 73 | Retrieves an integer value from an experiment 74 | 75 | **Parameters:** 76 | 77 | - `for experimentKey`: The experiment identifier (e.g., "retry_config") 78 | - `with variable`: Variable name within the experiment (required) 79 | - `dontCache`: If true, doesn't cache the result in memory (default: false) 80 | - `ignoreCache`: If true, bypasses the cache and fetches fresh (default: false) 81 | 82 | **Returns:** Integer value with fallback to -1 83 | 84 | **Fallback Order:** 85 | 86 | 1. Accessed cache (if ignoreCache = false) 87 | 2. Experiment map (fetched from server) 88 | 3. Default map (provided at initialization) 89 | 4. Hard-coded -1 90 | 91 | **Example:** 92 | 93 | ```swift 94 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 95 | let maxAttempts = experiments.getIntValue(for: "retry_config", with: "max_attempts") 96 | ``` 97 | 98 | --- 99 | 100 | ### 3. getDoubleValue 101 | 102 | Retrieves a double value from an experiment 103 | 104 | **Parameters:** 105 | 106 | - `for experimentKey`: The experiment identifier (e.g., "network_config") 107 | - `with variable`: Variable name within the experiment (required) 108 | - `dontCache`: If true, doesn't cache the result in memory (default: false) 109 | - `ignoreCache`: If true, bypasses the cache and fetches fresh (default: false) 110 | 111 | **Returns:** Double value with fallback to -1.0 112 | 113 | **Fallback Order:** 114 | 115 | 1. Accessed cache (if ignoreCache = false) 116 | 2. Experiment map (fetched from server) 117 | 3. Default map (provided at initialization) 118 | 4. Hard-coded -1.0 119 | 120 | **Example:** 121 | 122 | ```swift 123 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 124 | let timeout = experiments.getDoubleValue(for: "network_config", with: "timeout") 125 | ``` 126 | 127 | --- 128 | 129 | ### 4. getLongValue 130 | 131 | Retrieves a long (Int64) value from an experiment 132 | 133 | **Parameters:** 134 | 135 | - `for experimentKey`: The experiment identifier (e.g., "cache_config") 136 | - `with variable`: Variable name within the experiment (required) 137 | - `dontCache`: If true, doesn't cache the result in memory (default: false) 138 | - `ignoreCache`: If true, bypasses the cache and fetches fresh (default: false) 139 | 140 | **Returns:** Int64 value with fallback to -1 141 | 142 | **Fallback Order:** 143 | 144 | 1. Accessed cache (if ignoreCache = false) 145 | 2. Experiment map (fetched from server) 146 | 3. Default map (provided at initialization) 147 | 4. Hard-coded -1 148 | 149 | **Example:** 150 | 151 | ```swift 152 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 153 | let ttl = experiments.getLongValue(for: "cache_config", with: "ttl") 154 | ``` 155 | 156 | --- 157 | 158 | ### 5. getStringValue 159 | 160 | Retrieves a string value from an experiment 161 | 162 | **Parameters:** 163 | 164 | - `for experimentKey`: The experiment identifier (e.g., "button_exp_test") 165 | - `with variable`: Variable name within the experiment (required) 166 | - `dontCache`: If true, doesn't cache the result in memory (default: false) 167 | - `ignoreCache`: If true, bypasses the cache and fetches fresh (default: false) 168 | 169 | **Returns:** String value with fallback to "" (empty string) 170 | 171 | **Fallback Order:** 172 | 173 | 1. Accessed cache (if ignoreCache = false) 174 | 2. Experiment map (fetched from server) 175 | 3. Default map (provided at initialization) 176 | 4. Hard-coded "" (empty string) 177 | 178 | **Example:** 179 | 180 | ```swift 181 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 182 | let color = experiments.getStringValue(for: "button_exp_test", with: "color") 183 | ``` 184 | 185 | --- 186 | 187 | ### 6. getAllVariablesJSON 188 | 189 | Retrieves all variables for a specific experiment as a JSON string 190 | 191 | **Parameters:** 192 | 193 | - `for experimentKey`: The experiment identifier 194 | 195 | **Returns:** String? containing JSON representation of all variables, or nil if not found 196 | 197 | **Fallback Order:** 198 | 199 | 1. Experiment map (fetched from server) 200 | 2. Default map (provided at initialization) 201 | 3. nil 202 | 203 | **Example:** 204 | 205 | ```swift 206 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 207 | if let jsonString = experiments.getAllVariablesJSON(for: "button_exp_test") { 208 | print("Variables: \(jsonString)") 209 | } 210 | ``` 211 | 212 | --- 213 | 214 | ### 7. fetchExperiments 215 | 216 | Fetches experiments from the backend server. 217 | 218 | **Parameters:** 219 | 220 | - `for experimentKeys`: Dictionary of experiment keys with their default values (e.g., `["button_color": .dictionary(["color": .string("blue")])]`) 221 | - `completion`: Completion handler with `(ExperimentResponse?, String?)` parameters 222 | 223 | **Behavior:** 224 | 225 | - Appends the provided default values to the default map 226 | - Triggers network requests to fetch the experiments 227 | - API CALL - POST request to `/v1/allocations` endpoint (or configured `apiEndpoint`) 228 | 229 | **API Request Details:** 230 | 231 | - **Method**: POST 232 | - **Endpoint**: Configured `apiEndpoint` (default: `/v1/users/experiments`, but commonly `/v1/allocations`) 233 | - **Headers**: 234 | - `api-key`: From configuration headers 235 | - `user-id`: Current user ID (if available) 236 | - `guest-id`: Current guest ID 237 | - `content-type`: `application/json` 238 | - Custom headers from configuration 239 | - **Body**: JSON payload with `experiment_keys`, `stable_id`, `user_id`, and `attributes` 240 | 241 | **On Success:** 242 | 243 | - Updates experiment map with fetched experiments 244 | - Removes experiments not in defaultExperiments 245 | - Persists experiment map to local storage 246 | - Calls completion handler with `ExperimentResponse` containing fetched experiments 247 | 248 | **On Error:** 249 | 250 | - Calls completion handler with `nil` response and error string 251 | 252 | **Example:** 253 | 254 | ```swift 255 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 256 | 257 | experiments.fetchExperiments(for: [ 258 | "button_color": .dictionary([ 259 | "color": .string("blue") 260 | ]) 261 | ]) { response, error in 262 | if let error = error { 263 | print("Error: \(error)") 264 | } else if let response = response { 265 | print("Fetched \(response.data?.count ?? 0) experiments") 266 | } 267 | } 268 | ``` 269 | 270 | --- 271 | 272 | ### 8. refreshExperiments 273 | 274 | Fetches experiments using predefined experiment keys in the default map 275 | 276 | **Parameters:** 277 | 278 | - `completion`: Completion handler with `(ExperimentResponse?, String?)` parameters 279 | 280 | **Behavior:** 281 | 282 | - Uses existing default experiments (from configuration) to refresh 283 | - Builds request from keys of the defaultExperiments map 284 | - Triggers network request to fetch experiments 285 | - API CALL - Same as `fetchExperiments` but uses default map keys 286 | 287 | **On Success:** 288 | 289 | - If `shouldRefreshOnForeground` is true, updates caching headers (e.g., cache window/last modified) from the response 290 | - Parses response to Experiment array and updates experiment map 291 | - Removes experiments not present in defaultExperiments 292 | - Persists experiment map to local storage 293 | - Calls completion handler with `ExperimentResponse` (on main thread) 294 | 295 | **On Error or Exception:** 296 | 297 | - Processes error (including 304 Not Modified handling) and calls completion handler with `nil` response and error string (on main thread) 298 | 299 | **Example:** 300 | 301 | ```swift 302 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 303 | 304 | experiments.refreshExperiments { response, error in 305 | if let error = error { 306 | print("Error: \(error)") 307 | } else if let response = response { 308 | print("Refreshed \(response.data?.count ?? 0) experiments") 309 | } 310 | } 311 | ``` 312 | 313 | --- 314 | 315 | ### 9. getExperimentVariants 316 | 317 | Returns a copy of all currently loaded experiment variants 318 | 319 | **Parameters:** 320 | 321 | - None 322 | 323 | **Returns:** `[String: ExperimentVariant]` where: 324 | - Key: experimentKey (experiment identifier) 325 | - Value: ExperimentVariant object containing: 326 | - `experimentId`: Unique experiment identifier 327 | - `variantName`: Name of the assigned variant 328 | 329 | **Behavior:** 330 | 331 | - Creates a new dictionary copy of the current experiments 332 | - No network calls - purely returns cached data 333 | - Data source: experiment map populated from: 334 | - Initial load from local storage (on startup) 335 | - Updates from successful API responses (refreshExperiments/fetchExperiments) 336 | - Returns empty dictionary if no experiments have been loaded yet 337 | 338 | **Example:** 339 | 340 | ```swift 341 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 342 | let variants = experiments.getExperimentVariants() 343 | for (experimentKey, variant) in variants { 344 | print("\(experimentKey): \(variant.variantName)") 345 | } 346 | ``` 347 | 348 | --- 349 | 350 | ### 10. setExperimentsToStorage 351 | 352 | Persists experiment data to local storage and updates the in-memory cache 353 | 354 | **Parameters:** 355 | 356 | - `experiments`: `[String: Experiment]?` containing experiment data to persist (nil to clear all) 357 | 358 | **Behavior:** 359 | 360 | - Persists the experiments data to local storage via AscendStorage 361 | - Updates the in-memory experiment map with the new data 362 | - No network calls - purely local storage operation 363 | - Used for manually setting experiment data (e.g., from external sources or testing) 364 | 365 | **Return/Fallback Order:** 366 | 367 | - No return value - This is a void method 368 | - No fallback mechanism - If persistence fails, the error is logged but not propagated 369 | - Immediate effect - Data is immediately available in memory after calling this method 370 | - Storage failure handling - If storage write fails, the in-memory map is still updated, so experiments remain available for the current session 371 | 372 | **Example:** 373 | 374 | ```swift 375 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 376 | 377 | // Set experiments 378 | let testExperiments: [String: Experiment] = [ 379 | "test_key": Experiment( 380 | experimentId: "exp-123", 381 | userIdentifierType: "user_id", 382 | status: "active", 383 | variables: .dictionary(["color": .string("red")]), 384 | experimentKey: "test_key", 385 | variantName: "variant_a" 386 | ) 387 | ] 388 | experiments.setExperimentsToStorage(testExperiments) 389 | 390 | // Clear all experiments 391 | experiments.setExperimentsToStorage(nil) 392 | ``` 393 | 394 | --- 395 | 396 | ### 11. getExperimentsFromStorage 397 | 398 | Retrieves experiment data from local storage (disk) 399 | 400 | **Parameters:** 401 | 402 | - None 403 | 404 | **Returns:** `[String: Experiment]?` containing persisted experiment data, or nil if not found 405 | 406 | **Behavior:** 407 | 408 | - Returns a dictionary containing persisted experiment data 409 | - Reads from disk storage using the `ascend_experiments.json` file 410 | - No network calls - purely local storage operation 411 | - Used to retrieve previously saved experiment data 412 | - Supports both ExperimentSnapshot format (new) and raw dictionary format (legacy) 413 | 414 | **Return/Fallback Order:** 415 | 416 | - Returns: `[String: Experiment]?` - the persisted experiment data 417 | - Fallback mechanism: 418 | - If file doesn't exist, returns nil 419 | - If JSON deserialization fails, returns nil 420 | - No exception thrown - gracefully handles storage issues 421 | - Data source priority: Only reads from local disk storage, does not check in-memory cache 422 | 423 | **Example:** 424 | 425 | ```swift 426 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 427 | 428 | if let storedExperiments = experiments.getExperimentsFromStorage() { 429 | print("Loaded \(storedExperiments.count) experiments from storage") 430 | for (key, experiment) in storedExperiments { 431 | print("\(key): \(experiment.variantName)") 432 | } 433 | } else { 434 | print("No experiments found in storage") 435 | } 436 | ``` 437 | 438 | --- 439 | 440 | ### 12. getExperimentVariantsJSON 441 | 442 | Returns all currently loaded experiment variants as a JSON string 443 | 444 | **Parameters:** 445 | 446 | - None 447 | 448 | **Returns:** `String?` containing JSON representation of all experiment variants, or nil if encoding fails 449 | 450 | **Behavior:** 451 | 452 | - Gets all experiment variants using `getExperimentVariants()` 453 | - Encodes the variants dictionary to JSON string 454 | - Returns nil if JSON encoding fails 455 | 456 | **Example:** 457 | 458 | ```swift 459 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 460 | 461 | if let jsonString = experiments.getExperimentVariantsJSON() { 462 | print("Variants JSON: \(jsonString)") 463 | } 464 | ``` 465 | 466 | --- 467 | 468 | ### 13. getDefaultExperimentValues 469 | 470 | Returns the default experiment values that were configured at initialization 471 | 472 | **Parameters:** 473 | 474 | - None 475 | 476 | **Returns:** `[String: ExperimentVariable]` - Dictionary of default experiment values keyed by API path 477 | 478 | **Behavior:** 479 | 480 | - Returns the default experiments map that was provided during configuration 481 | - No network calls - purely returns configured defaults 482 | - Used to retrieve the default values that were set at initialization 483 | 484 | **Example:** 485 | 486 | ```swift 487 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 488 | 489 | let defaults = experiments.getDefaultExperimentValues() 490 | for (key, value) in defaults { 491 | print("Default for \(key): \(value)") 492 | } 493 | ``` 494 | 495 | --- 496 | 497 | ### 14. loadExperimentsData 498 | 499 | Loads experiments data asynchronously from disk storage with snapshot information 500 | 501 | **Parameters:** 502 | 503 | - `completion`: Completion handler with `(ExperimentSnapshot?, Error?)` parameters 504 | 505 | **Behavior:** 506 | 507 | - Returns `ExperimentSnapshot` containing: 508 | - `timestamp`: Timestamp when the snapshot was taken 509 | - `data`: Dictionary of experiments keyed by their API path 510 | - Reads from disk storage using the `ascend_experiments.json` file 511 | - Supports both ExperimentSnapshot format (new) and raw dictionary format (legacy) 512 | - For legacy format, uses file modification date as timestamp 513 | - No network calls - purely local storage operation 514 | 515 | **On Success:** 516 | 517 | - Calls completion handler with `ExperimentSnapshot` containing persisted data (on main thread) 518 | 519 | **On Error:** 520 | 521 | - Calls completion handler with `nil` snapshot and error (on main thread) 522 | 523 | **Example:** 524 | 525 | ```swift 526 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 527 | 528 | experiments.loadExperimentsData { snapshot, error in 529 | if let error = error { 530 | print("Error loading experiments: \(error)") 531 | } else if let snapshot = snapshot { 532 | print("Loaded snapshot with \(snapshot.count) experiments at timestamp: \(snapshot.timestamp)") 533 | } 534 | } 535 | ``` 536 | 537 | --- 538 | 539 | ### 15. clearAllExperimentsData 540 | 541 | Clears all experiments data from memory and storage 542 | 543 | **Parameters:** 544 | 545 | - None 546 | 547 | **Returns:** `Void` 548 | 549 | **Behavior:** 550 | 551 | - Clears currentExperiments map 552 | - Clears defaultExperiments map 553 | - Clears access map (session-scoped cache) 554 | - Persists empty state to local storage 555 | - No network calls - purely local operation 556 | - Used when user logs out or when you want to reset all experiment data 557 | 558 | **Example:** 559 | 560 | ```swift 561 | let experiments = try Ascend.getPlugin(AscendExperiments.self) 562 | 563 | experiments.clearAllExperimentsData() 564 | print("All experiments data cleared") 565 | ``` 566 | 567 | --- 568 | 569 | ## Events 570 | 571 | Coming soon... 572 | 573 | -------------------------------------------------------------------------------- /docs/src/content/docs/howto/how-to-contribute.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Development Setup & Contributing 3 | description: Development Setup & Contributing 4 | --- 5 | Welcome to the Ascend project! We're excited to have you contribute. This guide will help you set up your development environment and understand our contribution workflow. 6 | 7 | ## Table of Contents 8 | 9 | 1. [Project Structure](#project-structure) 10 | 2. [Development Environment Setup](#development-environment-setup) 11 | 3. [Building from Source](#building-from-source) 12 | 4. [Running Tests](#running-tests) 13 | 5. [Code Style and Standards](#code-style-and-standards) 14 | 6. [Contributing Guidelines](#contributing-guidelines) 15 | 7. [Pull Request Process](#pull-request-process) 16 | 8. [Community Guidelines](#community-guidelines) 17 | 18 | --- 19 | 20 | ## Project Structure 21 | 22 | Ascend is organized as a multiple micro services. You can find project structure of each service below.: 23 | 24 | 25 | ``` 26 | ascend/ 27 | ├── testlab/ # Backend Experiment server (Java/Vert.x) 28 | │ ├── src/ 29 | │ │ ├── main/ 30 | │ │ │ ├── java/ # Java source code 31 | │ │ │ │ └── com/ascend/testlab/ 32 | │ │ │ │ ├── allocation/ # Assignment logic 33 | │ │ │ │ ├── client/ # Database & cache clients 34 | │ │ │ │ ├── config/ # Configuration 35 | │ │ │ │ ├── dao/ # Data access layer 36 | │ │ │ │ ├── dto/ # Request/Response objects 37 | │ │ │ │ ├── entity/ # Domain models 38 | │ │ │ │ ├── rest/ # REST endpoints 39 | │ │ │ │ ├── service/ # Business logic 40 | │ │ │ │ └── verticle/ # Vert.x verticles 41 | │ │ │ └── resources/ 42 | │ │ │ ├── config/ # Application configs 43 | │ │ │ ├── db/ # Database schemas 44 | │ │ │ └── webroot/ # Static files & Swagger 45 | │ │ └── test/ # Unit & integration tests 46 | │ ├── pom.xml # Maven configuration 47 | │ ├── Dockerfile # Docker build 48 | │ └── docker-compose.yaml # Local dev setup 49 | │ 50 | ├── flockr/ # Backend Audience Engine (Java/Vert.x) 51 | │ ├── env.docker # Docker environment template 52 | │ ├── Makefile # Build & deployment automation 53 | │ ├── DOCKER.md # Docker documentation 54 | │ ├── README.md 55 | │ │ 56 | │ ├── flockr-admin/ # Admin service module 57 | │ │ ├── pom.xml 58 | │ │ ├── Dockerfile 59 | │ │ └── src/ 60 | │ │ ├── main/ 61 | │ │ │ ├── java/io/ascend/flockr/admin/ 62 | │ │ │ │ ├── client/ # External clients (Postgres, Flink, WebClient) 63 | │ │ │ │ ├── config/ # Configuration classes 64 | │ │ │ │ ├── constants/ # Application constants 65 | │ │ │ │ ├── domain/ # Domain models 66 | │ │ │ │ ├── exception/ # Custom exceptions 67 | │ │ │ │ ├── injection/ # Guice modules and injector 68 | │ │ │ │ ├── io/ # Request/Response DTOs 69 | │ │ │ │ ├── provider/ # JAX-RS providers 70 | │ │ │ │ ├── repository/ # Data access layer (PostgreSQL) 71 | │ │ │ │ ├── rest/ # REST controllers 72 | │ │ │ │ ├── service/ # Business logic services 73 | │ │ │ │ ├── util/ # Utilities 74 | │ │ │ │ ├── validation/ # Custom validators 75 | │ │ │ │ ├── verticle/ # Vert.x verticles 76 | │ │ │ │ └── MainLauncher.java 77 | │ │ │ └── resources/ 78 | │ │ │ ├── config/ # HOCON configs 79 | │ │ │ │ ├── application/ 80 | │ │ │ │ ├── postgres/ 81 | │ │ │ │ ├── flink/ 82 | │ │ │ │ ├── kafka-producer/ 83 | │ │ │ │ ├── http-server/ 84 | │ │ │ │ └── swagger/ 85 | │ │ │ ├── db/postgres/ # Database scripts 86 | │ │ │ │ ├── 01_schema.sql 87 | │ │ │ │ └── 02_seed.sql 88 | │ │ │ ├── logback/ # Logging config 89 | │ │ │ └── webroot/swagger/ # Swagger UI & spec 90 | │ │ └── test/ # Unit & integration tests 91 | │ │ 92 | │ ├── flockr-users/ # Users service module 93 | │ │ ├── pom.xml 94 | │ │ ├── Dockerfile 95 | │ │ └── src/ 96 | │ │ ├── main/ 97 | │ │ │ ├── java/io/ascend/flockr/users/ 98 | │ │ │ │ ├── client/ # Aerospike client 99 | │ │ │ │ ├── config/ # Config classes 100 | │ │ │ │ ├── constants/ # Application constants 101 | │ │ │ │ ├── controller/ # REST controllers 102 | │ │ │ │ ├── dto/ # DTOs 103 | │ │ │ │ ├── guice/ # Guice application context 104 | │ │ │ │ ├── module/ # Guice modules 105 | │ │ │ │ ├── service/ # Business logic 106 | │ │ │ │ ├── verticle/ # Vert.x verticles 107 | │ │ │ │ ├── AbstractMainApplication.java 108 | │ │ │ │ └── MainApplication.java 109 | │ │ │ └── resources/ 110 | │ │ │ ├── config/ # HOCON config 111 | │ │ │ │ ├── aerospike/ 112 | │ │ │ │ ├── http-server/ 113 | │ │ │ │ └── swagger/ 114 | │ │ │ ├── logback/ # Logging config 115 | │ │ │ └── webroot/swagger/ # Swagger UI & spec 116 | │ │ └── test/ 117 | │ │ 118 | │ └── docker/ 119 | │ ├── docker-start.sh # Start all services 120 | │ ├── docker-stop.sh # Stop all services 121 | │ ├── docker-status.sh # Check service status 122 | │ ├── docker-clean.sh # Clean containers/volumes 123 | │ └── config/ 124 | │ ├── flink.conf 125 | │ └── postgres.conf 126 | │ 127 | ├── ascend-astra/ # Gateway service 128 | │ ├── Dockerfile 129 | │ ├── docker-compose.yml 130 | │ ├── plugins/ 131 | │ │ ├── conditional-req-termination/ 132 | │ │ ├── cors/ 133 | │ │ ├── maintenance/ 134 | │ │ ├── rate-limiting-v2/ 135 | │ │ ├── strip-headers/ 136 | │ │ └── swap-header/ 137 | │ ├── ascend-astra/ 138 | │ │ ├── kong.rockspec 139 | │ │ └── kong.yml 140 | │ └── scripts/ 141 | │ ├── docker-start.sh 142 | │ ├── docker-stop.sh 143 | │ ├── deck-dump.sh 144 | │ ├── deck-sync.sh 145 | │ └── setup.sh 146 | │ 147 | ├── ascend-panel/ 148 | │ ├── src/ 149 | │ │ ├── store/ # Redux store 150 | │ │ │ ├── store.ts 151 | │ │ │ ├── hooks.ts 152 | │ │ │ └── slices/ 153 | │ │ │ └── counterSlice.ts 154 | │ │ ├── theme/ # MUI theme 155 | │ │ │ └── theme.ts 156 | │ │ ├── App.tsx # Root component 157 | │ │ ├── main.tsx # Entry point 158 | │ │ └── index.css # Tailwind global styles 159 | │ 160 | ├── ascend-android-sdk/ # Android SDK (Kotlin) 161 | │ ├── ascend-android/ 162 | │ │ └── src/ 163 | │ │ ├── main/java/com/ # SDK core 164 | │ │ └── test/ 165 | │ ├── app/ # Example app 166 | │ └── build.gradle.kts 167 | │ 168 | ├── ascend-ios/ # iOS SDK (Swift) 169 | │ ├── Sources/ 170 | │ │ ├── Ascend/ 171 | │ │ ├── AscendCommon/ 172 | │ │ │ ├── AscendCore/ 173 | │ │ │ ├── AscendExperiments/ 174 | │ │ │ └── PluginSystem/ 175 | │ ├── Package.swift 176 | │ └── Ascend.podspec 177 | │ 178 | ├── ascend-react-native-sdk/ # React Native SDK 179 | │ ├── src/ 180 | │ ├── android/ 181 | │ ├── ios/ 182 | │ ├── example/ 183 | │ └── package.json 184 | │ 185 | └── ascend/ # Documentation website 186 | ├── docs/ 187 | ├── src/ 188 | │ ├── assets/ 189 | │ ├── content/ 190 | │ ├── pages/ 191 | │ ├── styles/ 192 | └── astro.config.js # Astro config 193 | 194 | ``` 195 | 196 | ### Key Components 197 | 198 | #### Testlab (Experiment Backend Service) 199 | 200 | - **Allocation**: Variant assignment logic with strategies and filters 201 | - **DAO**: Database access with query builders 202 | - **Service**: Business logic layer 203 | - **REST**: HTTP endpoints with OpenAPI specs 204 | - **Client**: PostgreSQL and Aerospike integrations 205 | 206 | #### Flockr (Audience Backend Service) 207 | 208 | - **Admin**: Flockr Admin service 209 | - **DAO**: Database access with query builders 210 | - **Service**: Business logic layer 211 | - **REST**: HTTP endpoints with OpenAPI specs 212 | - **Client**: PostgreSQL and Aerospike integrations 213 | 214 | #### ascend-astra (Gateway Service) 215 | 216 | - **Plugins**: Kong plugins 217 | - **Kong-Proxy**: Main API Gateway 218 | - **Kong-Admin**: Kong Administration 219 | - **Kong-Manager**: Kong GUI dashboard 220 | - **Client**: PostgreSQL and Redis integrations 221 | 222 | #### ascend-panel (Frontend Panel Service) 223 | 224 | - **App**: Frontend Service 225 | - **Network**: HTTP client with retry logic 226 | 227 | 228 | #### Android SDK 229 | 230 | - **Plugins**: Experiments and future features 231 | - **Core**: User management, device info, configuration 232 | - **Network**: HTTP client with retry logic 233 | - **Storage**: Local caching and persistence 234 | 235 | #### iOS SDK 236 | 237 | - **Plugin Architecture**: Modular, extensible design 238 | - **AscendCore**: Configuration, user management, device info 239 | - **AscendExperiments**: A/B testing functionality 240 | - **AscendCommon**: Shared utilities (logging, networking, storage) 241 | 242 | #### React Native SDK 243 | 244 | - **TypeScript Layer**: JavaScript API 245 | - **Native Bridges**: Android and iOS native modules 246 | - **Type Definitions**: Full TypeScript support 247 | 248 | --- 249 | 250 | ## Development Environment Setup 251 | 252 | ### Prerequisites 253 | 254 | Ensure you have the following installed: 255 | 256 | #### For Backend Development (TestLab) 257 | 258 | - **Java JDK 17** ([Download](https://adoptium.net/)) 259 | ```bash 260 | java -version # Should show 17.x 261 | ``` 262 | 263 | - **Apache Maven 3.6+** ([Download](https://maven.apache.org/download.cgi)) 264 | ```bash 265 | mvn -version 266 | ``` 267 | 268 | - **PostgreSQL 12+** ([Download](https://www.postgresql.org/download/)) 269 | ```bash 270 | psql --version 271 | ``` 272 | 273 | - **Aerospike** (optional, for caching) ([Download](https://aerospike.com/download/)) 274 | 275 | - **Docker & Docker Compose** ([Download](https://www.docker.com/get-started)) 276 | ```bash 277 | docker --version 278 | docker-compose --version 279 | ``` 280 | 281 | #### For Android SDK Development 282 | 283 | - **Android Studio** (latest stable) ([Download](https://developer.android.com/studio)) 284 | - **JDK 17** 285 | - **Android SDK** (API 24+) 286 | 287 | #### For iOS SDK Development 288 | 289 | - **macOS** (required for iOS development) 290 | - **Xcode 12+** ([Download](https://developer.apple.com/xcode/)) 291 | - **Swift 5.0+** 292 | 293 | #### For React Native SDK Development 294 | 295 | - **Node.js 16+** ([Download](https://nodejs.org/)) 296 | ```bash 297 | node --version 298 | ``` 299 | 300 | - **Yarn** ([Download](https://yarnpkg.com/)) 301 | ```bash 302 | yarn --version 303 | ``` 304 | 305 | - **React Native CLI** 306 | ```bash 307 | npm install -g react-native-cli 308 | ``` 309 | 310 | --- 311 | 312 | ## Building from Source 313 | 314 | ### Backend Server (TestLab) 315 | 316 | #### 1. Clone the Repository 317 | 318 | ```bash 319 | git clone https://https://github.com/dream-horizon-org/testlab.git 320 | cd testlab 321 | ``` 322 | 323 | #### 2. Set Up PostgreSQL 324 | 325 | ```bash 326 | # Create database 327 | createdb experiment 328 | 329 | # Run schema 330 | psql -d experiment -f src/main/resources/db/postgresql/schema.sql 331 | ``` 332 | 333 | #### 3. Configure Environment 334 | 335 | Create a `.env` file or export variables: 336 | 337 | ```bash 338 | export PG_USER="postgres" 339 | export PG_PASSWORD="your_password" 340 | export ENV="local" 341 | ``` 342 | 343 | #### 4. Build the Project 344 | 345 | ```bash 346 | # Clean build 347 | mvn clean install 348 | 349 | # Skip tests (faster) 350 | mvn clean install -DskipTests 351 | 352 | # Build fat JAR 353 | mvn clean package 354 | ``` 355 | 356 | Output: `target/testlab/testlab-1.0-fat.jar` 357 | 358 | #### 5. Run Locally 359 | 360 | **Option A: Using Maven** 361 | 362 | ```bash 363 | mvn exec:java 364 | ``` 365 | 366 | **Option B: Using JAR** 367 | 368 | ```bash 369 | cd target/testlab 370 | PG_USER=postgres PG_PASSWORD=your_password \ 371 | java -Dapp.environment=local \ 372 | -Dlogback.configurationFile=./resources/logback/logback.xml \ 373 | -jar testlab-1.0-fat.jar 374 | ``` 375 | 376 | **Option C: Using IntelliJ IDEA** 377 | 378 | 1. Open `testlab` project in IntelliJ 379 | 2. Create Run Configuration: 380 | - Type: Application 381 | - Main class: `com.ascend.testlab.MainLauncher` 382 | - VM options: `-Dapp.environment=local -Dlogback.configurationFile=logback/logback-local.xml` 383 | - Program arguments: `run verticle.com.ascend.testlab.MainVerticle` 384 | - Environment variables: `ENV=local;PG_USER=postgres;PG_PASSWORD=your_password` 385 | 386 | **Option D: Using Docker Compose** 387 | 388 | ```bash 389 | docker-compose up -d 390 | ``` 391 | 392 | #### 6. Verify 393 | 394 | ```bash 395 | curl http://localhost:8080/v1/health 396 | ``` 397 | 398 | ### Android SDK 399 | 400 | #### 1. Clone Repository 401 | 402 | ```bash 403 | git clone https://github.com/ascend/ascend-android-sdk.git 404 | cd ascend-android-sdk 405 | ``` 406 | 407 | #### 2. Open in Android Studio 408 | 409 | - File → Open → Select `ascend-android-sdk` directory 410 | - Wait for Gradle sync to complete 411 | 412 | #### 3. Build 413 | 414 | ```bash 415 | # Command line 416 | ./gradlew build 417 | 418 | # Or use Android Studio: Build → Make Project 419 | ``` 420 | 421 | #### 4. Run Example App 422 | 423 | ```bash 424 | ./gradlew :app:installDebug 425 | 426 | # Or use Android Studio: Run → Run 'app' 427 | ``` 428 | 429 | ### iOS SDK 430 | 431 | #### 1. Clone Repository 432 | 433 | ```bash 434 | git clone https://github.com/ascend/ascend-ios.git 435 | cd ascend-ios 436 | ``` 437 | 438 | #### 2. Build with Swift Package Manager 439 | 440 | ```bash 441 | swift build 442 | ``` 443 | 444 | #### 3. Run Tests 445 | 446 | ```bash 447 | swift test 448 | ``` 449 | 450 | #### 4. Open in Xcode 451 | 452 | ```bash 453 | open Package.swift 454 | ``` 455 | 456 | #### 5. Build Framework (Optional) 457 | 458 | ```bash 459 | ./build-framework.sh 460 | ``` 461 | 462 | ### React Native SDK 463 | 464 | #### 1. Clone Repository 465 | 466 | ```bash 467 | git clone https://github.com/ascend/ascend-react-native-sdk.git 468 | cd ascend-react-native-sdk 469 | ``` 470 | 471 | #### 2. Install Dependencies 472 | 473 | ```bash 474 | yarn install 475 | ``` 476 | 477 | #### 3. Build TypeScript 478 | 479 | ```bash 480 | yarn prepare 481 | ``` 482 | 483 | #### 4. Run Example App 484 | 485 | **iOS:** 486 | ```bash 487 | cd example 488 | yarn install 489 | cd ios && pod install && cd .. 490 | yarn ios 491 | ``` 492 | 493 | **Android:** 494 | ```bash 495 | cd example 496 | yarn install 497 | yarn android 498 | ``` 499 | 500 | --- 501 | 502 | ## Running Tests 503 | 504 | ### Backend Tests 505 | 506 | ```bash 507 | # Run all tests 508 | mvn test 509 | 510 | # Run integration tests 511 | mvn verify 512 | 513 | # Run specific test 514 | mvn test -Dtest=AllocationServiceTest 515 | 516 | # Generate coverage report 517 | mvn jacoco:report 518 | # Report at: target/site/jacoco/index.html 519 | ``` 520 | 521 | ### Android SDK Tests 522 | 523 | ```bash 524 | # Unit tests 525 | ./gradlew test 526 | 527 | # Instrumented tests (requires emulator/device) 528 | ./gradlew connectedAndroidTest 529 | 530 | # With coverage 531 | ./gradlew jacocoTestReport 532 | ``` 533 | 534 | ### iOS SDK Tests 535 | 536 | ```bash 537 | # All tests 538 | swift test 539 | 540 | # Specific test 541 | swift test --filter AscendExperimentsTests 542 | ``` 543 | 544 | ### React Native SDK Tests 545 | 546 | ```bash 547 | # Unit tests 548 | yarn test 549 | 550 | # Type checking 551 | yarn typecheck 552 | 553 | # Linting 554 | yarn lint 555 | ``` 556 | 557 | --- 558 | 559 | ## Code Style and Standards 560 | 561 | ### Backend (Java) 562 | 563 | We use [Google Java Format](https://github.com/google/google-java-format) with Maven plugin. 564 | 565 | **Auto-format code:** 566 | 567 | ```bash 568 | mvn com.spotify.fmt:fmt-maven-plugin:format 569 | ``` 570 | 571 | **Check formatting:** 572 | 573 | ```bash 574 | mvn com.spotify.fmt:fmt-maven-plugin:check 575 | ``` 576 | 577 | **Style Guidelines:** 578 | 579 | - 100 character line limit 580 | - 2 spaces for indentation 581 | - Use `@Override` annotations 582 | - Prefer `final` for variables when possible 583 | - Use meaningful variable names 584 | 585 | ### Android (Kotlin) 586 | 587 | Follow [Kotlin Coding Conventions](https://kotlinlang.org/docs/coding-conventions.html). 588 | 589 | **Key points:** 590 | 591 | - 4 spaces for indentation 592 | - Use Kotlin idioms (data classes, extension functions) 593 | - Prefer `val` over `var` 594 | - Use meaningful names 595 | - Add KDoc comments for public APIs 596 | 597 | ### iOS (Swift) 598 | 599 | Follow [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/). 600 | 601 | **Key points:** 602 | 603 | - 4 spaces for indentation 604 | - Use clear, expressive names 605 | - Prefer value types (structs) over reference types 606 | - Use `guard` for early returns 607 | - Add documentation comments for public APIs 608 | 609 | ### React Native (TypeScript) 610 | 611 | Follow [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript). 612 | 613 | **Auto-format:** 614 | 615 | ```bash 616 | yarn lint --fix 617 | ``` 618 | 619 | **Key points:** 620 | 621 | - 2 spaces for indentation 622 | - Use TypeScript strict mode 623 | - Prefer `const` over `let` 624 | - Use arrow functions 625 | - Add JSDoc comments for public APIs 626 | 627 | --- 628 | 629 | ## Contributing Guidelines 630 | 631 | ### Before You Start 632 | 633 | 1. **Check existing issues**: Avoid duplicate work 634 | 2. **Discuss large changes**: Open an issue first for major features 635 | 3. **Read the docs**: Understand the architecture and patterns 636 | 637 | ### Contribution Workflow 638 | 639 | #### 1. Fork the Repository 640 | 641 | ```bash 642 | # Fork on GitHub, then clone your fork 643 | git clone https://github.com/YOUR_USERNAME/testlab.git 644 | cd testlab 645 | 646 | # Add upstream remote 647 | git remote add upstream https://github.com/ascend/testlab.git 648 | ``` 649 | 650 | #### 2. Create a Branch 651 | 652 | ```bash 653 | git checkout -b feature/your-feature-name 654 | # or 655 | git checkout -b fix/bug-description 656 | ``` 657 | 658 | **Branch naming conventions:** 659 | 660 | - `feature/` - New features 661 | - `fix/` - Bug fixes 662 | - `docs/` - Documentation updates 663 | - `refactor/` - Code refactoring 664 | - `test/` - Test additions/fixes 665 | 666 | #### 3. Make Your Changes 667 | 668 | - Write clear, concise code 669 | - Follow code style guidelines 670 | - Add tests for new functionality 671 | - Update documentation 672 | 673 | #### 4. Commit Your Changes 674 | 675 | Follow [Conventional Commits](https://www.conventionalcommits.org/): 676 | 677 | ```bash 678 | git commit -m "feat: add support for weighted cohorts" 679 | git commit -m "fix: resolve race condition in allocation" 680 | git commit -m "docs: update API documentation" 681 | git commit -m "test: add tests for variant selection" 682 | ``` 683 | 684 | **Commit message format:** 685 | 686 | ``` 687 | (): 688 | 689 | 690 | 691 |
Learn about what Ascend does and how it works
Get Ascend running in 10 minutes