diff --git a/.github/workflows/azure-static-web-apps-purple-bush-012fe8010.yml b/.github/workflows/azure-static-web-apps-purple-bush-012fe8010.yml new file mode 100644 index 0000000..1518d01 --- /dev/null +++ b/.github/workflows/azure-static-web-apps-purple-bush-012fe8010.yml @@ -0,0 +1,60 @@ +name: Azure Static Web Apps CI/CD + +on: + push: + branches: + - feature/zensible + pull_request: + types: [opened, synchronize, reopened, closed] + branches: + - feature/zensible + +jobs: + build_and_deploy_job: + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') + runs-on: ubuntu-latest + name: Build and Deploy Job + steps: + - uses: actions/checkout@v3 + with: + submodules: true + lfs: false + - name: Replace TelemetryDeck App ID + env: + TELEMETRYDECK_APP_ID: ${{ secrets.TELEMETRYDECK_APP_ID }} + run: | + sed -i "s|#TELEMETRYDECK_APP_ID#|${TELEMETRYDECK_APP_ID}|g" overrides/partials/integrations/analytics/telemetrydeck.html + - uses: actions/setup-python@v6 + with: + python-version: 3.x + - run: pip install zensical + - run: zensical build --clean + - name: Generate llms.txt and ship raw markdown + run: python3 scripts/build_llms.py + - name: Build And Deploy + id: builddeploy + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_PURPLE_BUSH_012FE8010 }} + repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) + action: "upload" + ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### + # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig + app_location: "site" + api_location: "" + skip_app_build: true + skip_api_build: true + output_location: "" + ###### End of Repository/Build Configurations ###### + + close_pull_request_job: + if: github.event_name == 'pull_request' && github.event.action == 'closed' + runs-on: ubuntu-latest + name: Close Pull Request Job + steps: + - name: Close Pull Request + id: closepullrequest + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_PURPLE_BUSH_012FE8010 }} + action: "close" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..70ed450 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,31 @@ +name: Documentation +on: + push: + branches: + - master + - main +permissions: + contents: read + pages: write + id-token: write +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - uses: actions/configure-pages@v6 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: 3.x + - run: pip install zensical + - run: zensical build --clean + - name: Generate llms.txt and ship raw markdown + run: python3 scripts/build_llms.py + - uses: actions/upload-pages-artifact@v5 + with: + path: site + - uses: actions/deploy-pages@v5 + id: deployment diff --git a/.gitignore b/.gitignore index 1614a82..b032abc 100644 --- a/.gitignore +++ b/.gitignore @@ -139,3 +139,4 @@ site/ Context.md Context/ CLAUDE.md +docs/.vscode/settings.json diff --git a/README.md b/README.md index 292768f..6de2adb 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,20 @@ Welcome to the public documentation for TelemetryDeck. The output of these pages We're incredibly grateful for pull requests or issues that improve things or let us know about errata and missing or unclear bits of information! <3 Check out `articles/documentation.md` for an overview of how documentation pages are formatted. + + +## Local Development + +We use Python `pip`. You can follow the setups at https://zensical.org/docs/get-started/#installation for the initial setup. + +Note: Make sure the Python version is recent e.g. 3.14 or later (`python3 --version`). + +## LLM and agent friendly output + +Every page is also published as raw Markdown alongside the rendered HTML. After `zensical build`, run `python3 scripts/build_llms.py` to: + +- copy each source `.md` from `docs/` into the corresponding location in `site/` (so `https://docs.telemetrydeck.com/articles/insights.md` returns the source for `/articles/insights/`), +- write `site/llms.txt` following the [llms.txt](https://llmstxt.org) convention, +- write `site/llms-full.txt` containing every page concatenated for one-shot ingestion. + +Both deploy workflows run this step automatically after the Zensical build. diff --git a/articles/documentation.md b/articles/documentation.md deleted file mode 100644 index 8428599..0000000 --- a/articles/documentation.md +++ /dev/null @@ -1,300 +0,0 @@ ---- -meta_title: "How To: TelemetryDeck Markup" -title: How to write this Documentation -tags: documentation -lead: The TelemetryDeck documentation lives in a public Github repository. Here's how to contribute. -description: Here's how to contribute to the public TelemetryDeck documentation. -searchEngineDescription: The TelemetryDeck documentation lives in a public GitHub repository and is maintained by the TelemetryDeck team. Feel free to contribute & share your knowledge. -order: 999999999 ---- - -## Document Metadata - -All metadata for a documentation page is specified in the YAML header at the top of its markdown file (also called the `frontmatter` of the document). Most of the metadata is optional, but the `title` and `lead` are required: - -```yaml ---- -title: How to write this Documentation -lead: The TelemetryDeck documentation lives in a public GitHub repository. Here's how to contribute. ---- -``` - -Here are examples for all possible metadata values: - -```yaml ---- -title: Setting up your application in SwiftUI -tags: - - Setup - - Quickstart - - Code -testedOn: Xcode 12.2 & Swift 5.3 -featured: true -description: How to configure TelemetryClient in SwiftUI based applications -lead: In Scene-based SwiftUI applications, this is how you configure TelemetryClient -meta_title: Setting up your SwiftUI application with TelemetryDeck -meta_description: How to configure TelemetryDeck SDK in SwiftUI based applications -order: 1337 ---- -``` - -### Title, Lead and Description - -The `title` string is how the page is titled in the left sidebar, at the top of the documentation page and in the "Getting Started" page, should it appear there. - -The `lead` string is shown on the documentation page, right underneath the title. It is a short description of the documentation page. - -The `description` is used to generate the short descriptive text shown in the "Getting Started" page for `featured` pages. If not specified, the `lead` string is used. - -### Search Engine Title and Description - -The `meta_title` string is used to generate the title of the search engine result for the doc page. If not specified, it defaults to the `title` string. - -The `meta_description` string is used to generate the description of the search engine result for the documentation page. If not specified, it defaults to the `description` string. - -### Tags - -Tags are used to organize documentation pages in the sidebar and to link related pages. - -One tag: - -```yaml -tags: Swift -``` - -Multiple tags: - -```yaml -tags: - - Setup - - Quickstart - - Code - - Swift -``` - -Organizational tags like `docs` and `guides` are automatically applied. Use the `tags` metadata to add additional tags that link articles together, such as - -- The type of page (`setup`, `code`) -- The software stack or language (`swiftui`, `android`, `kotlin` etc.) -- The experience level of the reader (`beginner`, `intermediate`, `advanced`) -- The type of query (`filter`, `cohorts`, etc.) - -{% noteinfo "You don't need to add 'docs' and 'articles' as tags" %} - -All markdown files in the `docs` repository automatically get the `docs` tag applied to them (by `docs.11tydata.js`). In addition, the respective directories apply their own tags as well: - -- Files in the `intro` directory get the `intro` tag applied. -- Files in the `guides` directory get the `guides` tag applied. -- Files in the `articles` directory get the `articles` tag applied. -- Files in the `api` directory get the `api` tag applied. - -{% endnoteinfo %} - -### Order - -You can specify an `order` metadata value to tell the documentation system in which order to display pages. Pages are sorted within their category by `order` value ascending. - -The order value affects the display in the left sidebar as well as the "previous" and "next" links at the bottom of a page. - -```yaml -order: 42 -``` - -### Featured - -Documentation pages can be marked as `featured` to be displayed in the "Getting Started" page. - -```yaml -featured: true -``` - -### Tested On - -If the documentation page deals with a specific version of an API or SDK, or something else that might change in the future, it is common courtesy to tell the reader what version of the software or SDK the documentation page or code examples were last tested on. If the value is older, readers can at least infer that they might have to update the code to make it work. - -```yaml -testedOn: Xcode 12.2 & Swift 5.3 -``` - -The string value of the `testedOn` field is displayed in the right sidebar. - -### Compatibility and Contribution - -The right sidebar contains a list of compatibility information for the documentation page. If `testedOn` is set, it will display the `testedOn` value. In addition, it will show the date when the markdown file for the documentation page was last updated. This gives the reader additional hints as to how outdated or current the page is. - -The right sidebar will also display a list of contributors who have written an git commit that touches the specific markdown file. The documentation system will try and retrieve the GitHub avatar images for the contributors by - -1. Retrieving the committer email from the git commit -1. Searching for that email in the GitHub API -1. Downloading the avatar image from the GitHub API - -This will fail if the email is not found in the GitHub API, or if the email is not set as "public email" in the contributor's GitHub profile. - -## Best practices - -### Capitalization -We capitalize our Headings in **Sentence Case** (e.g., "This is a heading of our docs"). - -### Date and Time Format -For date formats we use either: -- International/European date format: `DD/MM/YYYY` (e.g., 01.04.2026) -- ISO 8601: `YYYY-MM-DD` (e.g., 2026-04-01) - -For times we use either: -- 24-Hour: `HH:mm:ss` (e.g., 14:30:05) -- 12-Hour: `hh:mm:ss tt` (e.g., 02:30:05 PM). `AM/PM` is capitalized. - -## Tables - -Tables are supported in markdown. Here is an example: - -| Name | Description | -| ------- | ------------------------------------------------------------------------------------------------------------------ | -| `title` | The title of the page. This is used in the left sidebar, at the top of the page and in the "Getting Started" page. | - -Here is the markdown code for the previous table: - -```markdown -| Name | Description | -| ------- | ------------------------------------------------------------------------------------------------------------------ | -| `title` | The title of the page. This is used in the left sidebar, at the top of the page and in the "Getting Started" page. | -``` - -## Markdown - -Documentation is written in [Markdown](https://www.markdownguide.org). This means you can write documentation in plain text, and it will be converted to HTML. All standard Markdown elements are supported, such as **bold text**, _italic text_, `inline code`, and [links](https://www.markdownguide.org/basic-syntax/#link). - -- Unordered lists -- are just dashes - -1. And ordered -1. lists -1. are just numbers - -Here is the markdown code for the previous paragraphs: - -```markdown -All standard Markdown elements are supported, -such as **bold text**, _italic text_, `inline code`, -and [links](https://www.markdownguide.org/basic-syntax/#link). - -- Unordered lists -- are just dashes - -1. And ordered -1. lists -1. are just numbers -``` - -## Images - -To display an image in a docs article, add it to the `images` directory. You can then link it using regular markdown image syntax, adding `/docs/images/` before the images' name. - -Example: You just added the file `privacy-overview.png` to the `images` folder. You can now display that image like so: - -```markdown -![A screenshot of Apple's Privacy Overview](/docs/images/privacy-overview.png) -``` - -The first part is the image's alt text. The second part is the path (`/docs/images/`) and the image file name `privacy-overview.png`. - -Here's what it looks like: - -![A screenshot of Apple's Privacy Overview](/docs/images/privacy-overview.png) - -{% notewarning "Image File Locations" %} - -Image files need to live in the `images` directory at the root of the `docs` repository. Image files that are elsewhere in the file hierarchy will be ignored. - -You need to prefix `/docs/images/` to the path when linking to the image, since the Docs repository lives inside a `/docs` folder on deployment. -{% endnotewarning %} - -## Code Blocks - -Code blocks begin with three backticks (\`) and end with three backticks (\`). On the same line as the opening backticks, you **must** specify the programming language of the code block. - -````text -```swift -print("Hello, world!") -``` -```` - -Results in: - -```swift -print("Hello, world!") -``` - -If you can't specify a language, please use `text` instead. - -````text -```text -this is - just - plain text -``` -```` - -## Custom Shortcodes - -In addition to the standard shortcodes, you can also use custom shortcodes. These allow you to display info boxes and other custom elements. - -### Info Box - -Use the `noteinfo` shortcode to display a box giving the reader additional information . - -```markdown -{% raw %}{% noteinfo "Your pizza is ready πŸ•" %} -It is time. You can now take your pizza out of the oven. -{% endnoteinfo %}{% endraw %} -``` - -Here is how this looks like: - -{% noteinfo "Your pizza is ready πŸ•" %} -It is time. You can now take your pizza out of the oven. -{% endnoteinfo %} - -### Warning Box - -Just as with `noteinfo`, you can also use `notewarning` to warn the readers of dangerous actions or consequences: - -```markdown -{% raw %}{% notewarning "Take care of your pizza!" %} -When removing your pizza out of the oven, please make sure to not burn your fingers. Also make sure your cat won't eat it while you look at this document. -{% endnotewarning %}{% endraw %} -``` - -This is how the `notewarning` output looks like: - -{% notewarning "Take care of your pizza!" %} -When removing your pizza out of the oven, please make sure to not burn your fingers. Also make sure your cat won't eat it while you look at this document. -{% endnotewarning %} - -### Buttons - -You can use button links to link to especially important pages or resources. Make a button by supplying - -- a label -- an URL to link to -- and a Boolean to indicate wether the button is a primary button (true) or a secondary button (false) - -```markdown -{% raw %}{% bigButton "All your base" "https://en.wikipedia.org/wiki/All_your_base_are_belong_to_us" true %} -{% bigButton "Main screen turn on." "https://en.wikipedia.org/wiki/Zero_Wing" true %}{% endraw %} -``` - -{% bigButton "All your base" "https://en.wikipedia.org/wiki/All_your_base_are_belong_to_us" true %} -{% bigButton "Main screen turn on." "https://en.wikipedia.org/wiki/Zero_Wing" false %} - -### Call to Action - -In rare cases, we want to display a call to action to register for TelemetryDeck. This is done using the `callToAction` shortcode. It needs two `string` parameters for the first and second line of text respectively. - -```markdown -{% raw %}{% callToAction "Let's make your app awesome!" "Get awesome privacy-first analytics for your app" %}{% endraw %} -``` - -The shortcode will automatically link to the registration page and the docs: - -{% callToAction "Let's make your app awesome!" "Get awesome privacy-first analytics for your app" %} diff --git a/articles/duration-signals.md b/articles/duration-signals.md deleted file mode 100644 index 3cc9440..0000000 --- a/articles/duration-signals.md +++ /dev/null @@ -1,250 +0,0 @@ ---- -title: Track time-based user engagement with Duration Signals -tags: - - setup - - how-to - - beginner - - swift -description: Duration Signals allow you to measure how long users spend on specific activities in your app with millisecond precision. -lead: With Duration Signals, you can easily measure how long users spend on different activities in your app, helping you identify engagement patterns, optimize workflows, and improve user experience with precise timing data. -searchEngineTitle: Track User Engagement with Duration Signals in TelemetryDeck -searchEngineDescription: Learn how to implement and analyze time-based metrics in your app using TelemetryDeck's Duration Signals. ---- - -## What are Duration Signals? - -Duration Signals are a powerful feature of TelemetryDeck's SDKs that make it easier than ever to understand how users interact with your app over time. Whether you want to track time spent during onboarding, content consumption, checkout flows, or any other user journey, Duration Signals provide accurate, millisecond-precise timing data. - -The SDK automatically handles all the complexities of time tracking for you: -- Precise measurement down to milliseconds (3 decimal places) -- Automatic exclusion of time spent while the app is in the background -- Merging parameters from both start and stop calls -- Thread-safe implementation for accurate timing - -## Implementation - -Using Duration Signals is as simple as bracketing an activity with two function calls: - -```swift -// Start tracking when the activity begins -TelemetryDeck.startDurationSignal("activityName") - -// ... user performs the activity ... - -// Stop tracking and send the signal when the activity ends -TelemetryDeck.stopAndSendDurationSignal("activityName") -``` - -The duration is automatically calculated and included in your signal as `TelemetryDeck.Signal.durationInSeconds`. - -### View Lifecycle Integration - -Duration Signals integrate seamlessly with your view lifecycles in SwiftUI: - -```swift -struct TutorialView: View { - var body: some View { - VStack { - Text("Welcome to the Tutorial!") - } - .onAppear { - TelemetryDeck.startDurationSignal("tutorial") - } - .onDisappear { - TelemetryDeck.stopAndSendDurationSignal("tutorial") - } - } -} -``` - -Both functions also take an optional `parameters` argument where you can pass additional information just like with the `signal` function. - -## Technical Details - -### SDK Requirements - -- Swift SDK: Version 2.7.0 or later -- Kotlin SDK: Version 4.1.0 or later -- Flutter SDK: Version 2.1.0 or later - -### Edge Cases & Limitations - -- **Multiple starts**: If you call `startDurationSignal` with a name that's already being tracked, the previous tracking is discarded and a new one begins. -- **Missing stop**: If a duration signal is never stopped, it will not be sent. -- **Signal name conflicts**: Use unique signal names for different activities to avoid conflicts. -- **App restarts**: Duration signals are not stored to persistent storage, therefore they are not appropriate for tracking long-term user engagement. - -## Analyzing Duration Data - -Duration data is sent as a numerical value in the `TelemetryDeck.Signal.durationInSeconds` parameter, which opens up several analysis possibilities. - -### Using the Histogram Aggregation - -The histogram aggregation type is perfect for visualizing the distribution of duration data: - -1. Create a new insight of type "Advanced Query", then open the "JSON Editor": - - ![A screenshot of the Query Creator dialog](/docs/images/duration-signal-01.png) - -2. Copy & paste the following histogram aggregation query and adjust `` to your needs: - - ![A screenshot of the JSON Editor text field](/docs/images/duration-signal-02.png) - - ```json - { - "aggregations": [ - { - "fieldName": "TelemetryDeck.Signal.durationInSeconds", - "name": "durationSketch", - "splitPoints": [0, 0.5, 1, 1.5, 2, 2.5, 3, 4, 5, 7.5, 10, 15, 20, 30, 45, 60, 90, 120], - "type": "histogram" - } - ], - "filter": { - "type": "and", - "fields": [ - { - "type": "range", - "column": "TelemetryDeck.Signal.durationInSeconds", - "matchValueType": "DOUBLE", - "lower": "0", - "upper": "120", - "upperOpen": true - }, - { - "dimension": "type", - "type": "selector", - "value": "" - } - ] - }, - "granularity": "all", - "queryType": "timeseries" - } - ``` - -3. Set the chart type to be a bar chart in the UI via the insight's top right segmented control: - - ![A screenshot of the insight set to be a bar chart](/docs/images/duration-signal-03.png) - -4. You might also want to adjust the `splitPoints` array based on the expected duration of your activity, for example: - - **Short interactions** (button clicks): `[0, 0.05, 0.1, 0.15, 0.2, 0.3, 0.5, 1, 2, 5]` - - **Medium interactions** (form fills): `[0, 1, 2, 3, 4, 5, 7.5, 10, 15, 20, 30]` - - **Long interactions** (content consumption): `[0, 5, 15, 30, 60, 120, 300, 600, 1200]` - -## Common Use Cases - -### Onboarding Optimization - -Track time spent in each step of your onboarding flow to identify which steps take too long or where users might get stuck: - -```swift -// In first onboarding screen -TelemetryDeck.startDurationSignal("Onboarding.step1") - -// When moving to second screen -TelemetryDeck.stopAndSendDurationSignal("Onboarding.step1", parameters: ["pushAccess": "granted"]) -TelemetryDeck.startDurationSignal("Onboarding.step2") - -// etc. -``` - -Note that Duration Signals are just ordinary signals, so you can totally reuse these for creating [funnel charts](https://telemetrydeck.com/docs/articles/how-to-funnel-insights/) and more. - -### Content Engagement - -Measure how long users engage with different content types to understand what resonates with your audience: - -```swift -// When user opens an article -TelemetryDeck.startDurationSignal("Content.viewing", parameters: [ - "contentType": "article", - "contentID": article.id, - "contentCategory": article.category, -]) - -// When user leaves the article -TelemetryDeck.stopAndSendDurationSignal("Content.viewing", parameters: [ - "reachedEnd": userReachedEnd ? "true" : "false", -]) -``` - -### Feature Discovery - -Track how long users spend exploring new features to assess the effectiveness of your feature introduction: - -```swift -// When user enters new feature area -TelemetryDeck.startDurationSignal("Feature.exploration", parameters: [ - "featureName": "videoEditor", - "entryPoint": entryPoint, -]) - -// When user leaves the feature area -TelemetryDeck.stopAndSendDurationSignal("Feature.exploration", parameters: [ - "completedAction": userCreatedVideo ? "true" : "false" -]) -``` - -### Performance Monitoring - -Track real-world performance metrics by measuring operation durations: - -```swift -// Before starting an intensive operation -TelemetryDeck.startDurationSignal("Render.operation", parameters: [ - "complexity": "\(complexity)", - "inputSize": "\(inputSizeInMB)", -]) - -// After operation completes -TelemetryDeck.stopAndSendDurationSignal("Render.operation", parameters: [ - "success": success ? "true" : "false", - "outputSize": "\(outputSizeInMB)", -]) -``` - -### Network Request Timing - -Duration Signals methods are marked with `@MainActor`, which means two things: - -1. In UI contexts like SwiftUI views, no `await` is needed (as shown in the above examples) -2. When calling from background contexts like network operations, you need to use `await` - -Here's how to measure network requests that run on background threads: - -```swift -func fetchData() async throws -> Data { - // Since we're potentially on a background thread, await is needed - await TelemetryDeck.startDurationSignal("Network.fetch", parameters: [ - "endpoint": "users/profile" - ]) - - do { - // Perform your network request - let (data, response) = try await URLSession.shared.data(from: url) - let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 - - // Again, await is needed here - await TelemetryDeck.stopAndSendDurationSignal("Network.fetch", parameters: [ - "status": "\(statusCode)", - "success": "true" - ]) - - return data - } catch { - await TelemetryDeck.stopAndSendDurationSignal("Network.fetch", parameters: [ - "success": "false" - ]) - throw error - } -} -``` - -## What's Next? - -Start by identifying a few key user journeys or critical performance areas in your app that would benefit from timing data. Implement Duration Signals for these activities first, then use the histogram aggregation to visualize and analyze the results. - -Remember that Duration Signals can be combined with your existing analytics strategy - they provide an additional dimension to your user data without replacing what you already have. - -{% callToAction "Explore more analytics possibilities" "Track user engagement and make data-driven decisions" %} \ No newline at end of file diff --git a/articles/hosting-solutions.md b/articles/hosting-solutions.md deleted file mode 100644 index 65180e7..0000000 --- a/articles/hosting-solutions.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: Hosting Solutions at TelemetryDeck -tags: - - setup - - hosting - - privacy -lead: Since we started with our public beta in 2021, we have been hosting TelemetryDeck, its customers, and data in the cloud. We currently don’t offer on-premise hosting or self-hosted solutions. -searchEngineDescription: Since we started in 2021, we have been hosting TelemetryDeck's data in the cloud. We currently don’t offer on-premise hosting or self-hosted solutions. ---- - -One reason is our [Double-Hashing anonymization method](https://telemetrydeck.com/docs/articles/anonymization-how-it-works/). It requires two distinct parties to use their own private keys to make sure neither can retrieve any personally identifiable information. When an event arrives on the server, we add our own salt to the user identifier and hash it again. This ensures that neither TelemetryDeck nor you can reconstruct the original identifier, protecting the user’s privacy. - -Another reason is how we think about data protection. We handle data with one of [the strongest data protection laws in mind](https://telemetrydeck.com/blog/privacy-by-design-vs-privacy-by-paperwork/) - and this way we can always make sure our customers use TelemetryDeck with strong data protection regulations. - -TelemetryDeck is always hosted in the cloud. Additionally, we save our customers tons of infrastructure cost and work by taking care of all hosting, bandwidth, and security, all hosted within the EU. - -{% callToAction "Try TelemetryDeck today" "Track user engagement with the fastest privacy friendly setup you can find" %} diff --git a/articles/navigation-signals.md b/articles/navigation-signals.md deleted file mode 100644 index 4692b06..0000000 --- a/articles/navigation-signals.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -title: Navigation Signals -tags: setup -description: TelemetryDeck can track how users navigate through your app when you send navigation signals. Here's how these need to look like. -lead: TelemetryDeck can track how users navigate through your app when you send navigation signals. Here's how these need to look like. -testedOn: SwiftSDK 2.2.0, WebSDK 1.0.0 ---- - -{% notewarning "Upcoming Feature" %} - -This feature is still in development and will take a while to be available in all SDKs and the Dashboard UI. We encourage you to start sending navigation signals now so you'll have data to play around with once we launch the feature fully. -{% endnotewarning %} - -{% noteinfo "Web Analytics already tracks navigation" %} - -If you're using TelemetryDeck's Web SDK to track your website, you don't need to send navigation signals. The Web SDK already tracks navigation automatically. -{% endnoteinfo %} - -## Format - -A navigation signal is a regular TelemetryDeck signal of type `TelemetryDeck.Navigation.pathChanged`. It has parameters for version number, source and destination paths, and an identifier, which is the source path and destination path concatenated with `->`. Using this identifier, we can track how users navigate through your app. - -```json -{ - "appID": "", - "clientUser": "", - "type": "TelemetryDeck.Navigation.pathChanged", - "payload": { - "TelemetryDeck.Navigation.schemaVersion": "1", - "TelemetryDeck.Navigation.identifier": " -> ", - "TelemetryDeck.Navigation.sourcePath": "", - "TelemetryDeck.Navigation.destinationPath": "" - } -} -``` - -Values in angle brackets (`< >`) are placeholders and should be replaced with actual values. - -The signal type should always be `TelemetryDeck.Navigation.pathChanged` for navigation signals. - -Here's what each parameter should contain: - -| Key | Description | -| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `TelemetryDeck.Navigation.schemaVersion` | The version of the schema. Should always be `"1"`. We'll use this to account for if we ever need to update this schema. | -| `TelemetryDeck.Navigation.identifier` | The source navigation path, followed by `->`, followed by the destination navigation path. This is the most important part of the navigation schema, we'll use this to build directed graphs. | -| `TelemetryDeck.Navigation.sourcePath` | A navigation path that describes the source of the navigation. | -| `TelemetryDeck.Navigation.destinationPath` | A navigation that describes the destination of the navigation. | - -## Navigation Paths - -Navigation Paths are strings that describe a location or view in your application or website. They must be -delineated by `.` characters in apps and `/` in websites. Delineation characters at the beginning and end of the string are -ignored. Use the empty string `""` for navigation from outside the app. - -Examples: - -- `index` -- `settings.user.changePassword` -- `/blog/ios-market-share` - -## Automatic Navigation Tracking - -Since TelemetryDeck navigation signals are slightly cumbersome to create manually, we're aiming to provide convenience methods for our SDKs that will automatically track navigation signals for you. These methods will be in one of two flavors, either a method that you call with a source and destination, or a method that you call with just a destination. - -### `TelemetryDeck.navigationPathChanged(from: , to: )` - -Calling this method will automatically create a navigation signal with the given source and destination. - -### `TelemetryDeck.navigationPathChanged(to: )` - -Calling this method with just a destination will use the previously last used source as the source for the navigation signal. - -This is convenient, but might produce incorrect graphs if you don't call it from every screen in your app. -Suppose you have 3 tabs "Home", "User" and "Settings", but only set up navigation in "Home" and "Settings". If -a user taps "Home", "User" and "Settings" in that order, that'll produce an incorrect navigation signal with -source "Home" and destination "Settings", a path that the user did not take. - -#### SwiftUI Convenience - -If you're using SwiftUI, you can use the `.trackNavigation(path:)` view modifier as a convenient wrapper around `navigationPathChanged(to:)`. It will automatically send the navigation signal when the view appears: - -```swift -struct SettingsView: View { - var body: some View { - Form { - NavigationLink("Account") { - AccountSettingsView() - .trackNavigation(path: "settings.account") - } - } - .trackNavigation(path: "settings") - } -} -``` - -The same considerations about consistent application apply - make sure to use the modifier on all navigation destinations to avoid incorrect navigation paths. diff --git a/articles/preset-errors.md b/articles/preset-errors.md deleted file mode 100644 index 05c71e8..0000000 --- a/articles/preset-errors.md +++ /dev/null @@ -1,134 +0,0 @@ ---- -title: Setting Up the 'Errors' Preset to Reduce Churn -tags: - - setup - - beginner - - insights - - presets -lead: TelemetryDeck ships with a set of insights that can be useful to learn what kind of issues your users encounter most in your apps. Here's how to set them up. ---- - -## Why Track Errors? - -Success is often defined in numbers such as Monthly Active Users (MAU) or Monthly Recurring Revenue (MRR). Churn is a metric that tracks the percentage of active/paying users who stopped using/paying for your app. - -A smooth experience without bugs & issues is one of the key factors contributing to a lower churn rate. If you do a good job in continuously fixing the most common issues with your product, you're on the right track for success. That's why it's important that you detect any common issues your users get stuck at while using your app. - -{% noteinfo "What Error Handling Really Is" %} -Error Handling is any logic that detects that something unexpected happened and reacts to that information in some useful way. It's commonly interpreted as 'showing an error message to a user', but that's just the most basic form of Error Handling. Other common things you can do to improve your app are Empty States (Why is this empty?), Call-to-Actions (What can I do?), and Auto-Repair (resiliency against common unexpected user input). But always make sure to give some form of feedback instead of failing silently, which makes your app feel broken. -{% endnoteinfo %} - -## Using the TelemetryDeck Swift SDK - -If you're using the TelemetryDeck Swift SDK, tracking errors is simple. Use the convenience method we've prepared: - -```swift -TelemetryDeck.errorOccurred(id: error.localizedDescription) -``` - -### Better Error Identification - -While `error.localizedDescription` works, it's not optimal because the same error will be reported differently based on user language settings. For better error grouping, provide a consistent ID: - -```swift -do { - let object = try JSONDecoder().decode(Object.self, from: data) -} catch { - // Option 1: Using error.with(id:) - TelemetryDeck.errorOccurred( - identifiableError: error.with(id: "ImportObject.jsonDecode") - ) - - // Option 2: Using explicit parameters - TelemetryDeck.errorOccurred( - id: "ImportObject.jsonDecode", - message: error.localizedDescription - ) -} -``` - -### Custom Error Types - -For your own error types, conform to the `IdentifiableError` protocol (built into the Swift SDK): - -```swift -enum MyError: String, IdentifiableError { - case fileMissing - case invalidFormat - - var id: String { self.rawValue } -} - -// Then report directly: -TelemetryDeck.errorOccurred(identifiableError: myError) -``` - -### Error Categories - -The Swift SDK provides three built-in error categories for better organization: - -```swift -// Thrown exceptions (parsing errors, I/O errors, permission errors) -TelemetryDeck.errorOccurred( - id: "FileNotFound", - category: .thrownException, - message: error.localizedDescription -) - -// User input errors (invalid format, invalid range) -TelemetryDeck.errorOccurred( - id: "ProjectForm.hourlyRateConversionFailed", - category: .userInput, - message: "Text '\(self.textFieldInput)' could not be converted to type 'Int'." -) - -// App state errors (inconsistent navigation, invalid combinations) -TelemetryDeck.errorOccurred( - id: "NavigationState.invalidTransition", - category: .appState, - message: "Cannot navigate from login to dashboard without authentication" -) -``` - -{% noteinfo "Default Category" %} -When using `TelemetryDeck.errorOccurred(identifiableError:)`, the category defaults to `.thrownException`, but you can override it if needed. -{% endnoteinfo %} - -The `errorOccurred` function accepts the same optional arguments as the `signal` function (namely `parameters`, `floatValue`, `customUserID`) in case you want to provide additional context info. - -## Manual Signal Construction for Other Platforms - -If you're not using the TelemetryDeck Swift SDK (for example, on Android, Web, or other platforms), you'll need to manually build the error signal. - -### Signal Structure - -**Event name**: `TelemetryDeck.Error.occurred` - -**Required parameter**: - -- `TelemetryDeck.Error.id`: A consistent identifier for grouping similar errors - -**Optional parameters**: - -- `TelemetryDeck.Error.message`: The full error message or description -- `TelemetryDeck.Error.category`: One of `thrown-exception`, `user-input`, or `app-state` - -## Built-In Error Categories - -Unexpected behavior generally falls into one of three categories, each with a dedicated chart in the "Errors" tab: - -1. **Thrown Exceptions** (`thrown-exception`): Parsing errors, I/O errors, permission errors -2. **User Input** (`user-input`): Invalid text format, invalid number format, invalid date range -3. **App State** (`app-state`): Inconsistent navigation request, invalid combination of form options - -### When to Use Each Category - -- **thrown-exception**: Use in `try-catch` blocks or when handling `null`/`nil` returns from operations that might fail -- **user-input**: Use when converting or validating user input with fallback behavior -- **app-state**: Use for assertion failures or unexpected application states that shouldn't occur in normal operation - -## Effect on Privacy & App Tracking Transparency - -If you are sending dynamic values such as `error.localizedDescription` or if any of the parameter fields contain user-dynamic data such as file paths or input data, some user data might be sent to TelemetryDeck. It really depends on the nature of this data and how you plan to use it that influences what fields in App Tracking Transparency you need to add. You might need to adjust your privacy report accordingly. - -TelemetryDeck does not attempt to link collected data to the users identity, nor do we use data for tracking purposes. To protect your users privacy, we urge you to not send any data that might identify your users. diff --git a/articles/preset-purchases.md b/articles/preset-purchases.md deleted file mode 100644 index fbeb9cd..0000000 --- a/articles/preset-purchases.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -title: Setting Up the 'Purchases' Preset to Get Live Purchase Data -tags: - - setup - - beginner - - insights - - presets -lead: TelemetryDeck ships with a set of insights that can be useful to track your revenue within the last few hours with live purchase data. Here's how to set them up. -searchEngineDescription: TelemetryDeck ships with a set of insights that can be useful to track your revenue within the last few hours with live purchase data. Learn how to set them up. ---- - -## Why Track Purchases? - -If you are offering In-App Purchases in your app, you might have noticed some delay in officially reported purchase stats. For example, App Store Connect charts do not offer any purchase data for the last 3 hours. Such a delay can be annoying sometimes such as at the day of your app launch or a specific live event related to your app. On top of that, App Store Connect in particular signs you out of your account regularly, making it annoying to quickly look up purchase statistics. - -That's why you might want to set up a signal in your application to track purchases in your app through TelemetryDeck with just a couple of seconds delay, providing you with the live data you want. - -You can use these methods to include your purchase data in TelemetryDeck: - -- Use the TelemetryDeck Swift SDK directly -- If you're already using RevenueCat, you can use the RevenueCat Integration -- If you're using FreemiumKit, you can connect that to TelemetryDeck - -See the sections below for a detailed description. - -{% notewarning "Live Data vs. Correct Data" %} -We do not offer any intelligence to correct once reported purchases, such as when users make refunds, or to detect subscription renewals. Therefore, our insights focus on more recent data. For longer-term or 100% correct data, refer to official sources. -{% endnotewarning %} - -## Using the TelemetryDeck Swift SDK - -If you're using the TelemetryDeck Swift SDK, tracking purchases is incredibly simple. Just call the convenience method when you receive a StoreKit transaction: - -```swift -TelemetryDeck.purchaseCompleted(transaction: transaction) -``` - -That's it! This method automatically: - -- Extracts the price from the transaction -- Converts the currency to USD (using hard-coded exchange rates) -- Determines if it's a subscription or one-time purchase -- Includes the storefront country and currency codes -- Sends the properly formatted signal to TelemetryDeck - -{% noteinfo "Requirements" %} -The `purchaseCompleted` convenience function is only available on iOS 15 or higher. It accepts the same optional arguments as the `signal` function (namely `parameters` and `customUserID`) in case you want to provide additional context info. -{% endnoteinfo %} - -## Using TelemetryDeck with RevenueCat - -If you use [RevenueCat](https://revenuecat.com), you can use our [RevenueCat Setup Guide](/docs/integrations/revenuecat/). - -## Using FreemiumKit - -If you use [FreemiumKit](https://freemiumkit.app), just add their SDKs `.onPurchaseCompleted` view modifier to your main view. It passes the `transaction` parameter to the closure, which you can directly pass to `TelemetryDeck.purchaseCompleted(transaction: transaction)`. Read the related section in their [setup guide](https://freemiumkit.app/documentation/freemiumkit/setupguide#Direct-Access-to-StoreKit-Transactions) to learn more. - -## Manual Signal Structure for Other Platforms - -{% notewarning "Only Needed for Non-Swift Platforms" %} -The following section describes the manual signal structure only necessary if you are NOT using the TelemetryDeck Swift SDK. Swift developers should use the `purchaseCompleted` convenience method described above. -{% endnotewarning %} - -If you're reporting purchases from other platforms (Android, Web, etc.), you'll need to manually construct and send the purchase signal with the following structure: - -### Required Fields - -- **Event name**: Must be `TelemetryDeck.Purchase.completed` -- **`floatValue`**: The purchase amount in USD - -{% notewarning "Manual Currency Conversion Required" %} -When sending purchase signals manually, you MUST convert the transaction value to USD yourself before sending. You can use [an API like this](https://www.exchangerate-api.com/docs/standard-requests) which offers 1,500 requests per month free of charge to get current exchange rates. Alternatively, you could fetch & hard-code exchange rates in your app for a rough estimate if you expect more than 1,500 purchases per month. -{% endnotewarning %} - -### Optional but Recommended Payload Keys - -To get more detailed insights, include these additional parameters: - -- `TelemetryDeck.Purchase.type`: Either `subscription` or `one-time-purchase` -- `TelemetryDeck.Purchase.countryCode`: The country code of the storefront -- `TelemetryDeck.Purchase.currencyCode`: The currency code of the storefront - -### Example Manual Implementation (Swift) - -Here's what the manual implementation looks like if you need to customize it or understand what the convenience method does internally: - -```swift -// Convert price to USD first (you need to handle currency conversion) -let priceInUSD = convertToUSD(transaction.price, from: transaction.currencyCode) - -TelemetryDeck.signal( - "TelemetryDeck.Purchase.completed", - parameters: [ - "TelemetryDeck.Purchase.type": transaction.subscriptionGroupID != nil ? "subscription" : "one-time-purchase", - "TelemetryDeck.Purchase.countryCode": transaction.storefrontCountryCode, - "TelemetryDeck.Purchase.currencyCode": transaction.currencyCode ?? "???" - ], - floatValue: priceInUSD -) -``` - -## Effect on Privacy & App Tracking Transparency - -If you are using a 3rd-party service like RevenueCat, you don't need to change your privacy labels at all because you're sending way less data to TelemetryDeck than you are already to those services. So if you've followed their guides, you should be good. - -If you aren't using a 3rd-party library, you are now sending purchase history data to TelemetryDeck. So make sure to mark the checkbox for "Analytics" in the "Purchase History" entry in your App Privacy page. - -You can answer all subsequent questions with "No" because we neither link collected data to the users identity, nor do we use them for tracking purposes. - -When all is configured your "Purchases" entry in your App Privacy page should end up looking like this: - -![Purchases entry with only 'Used for Analytics' in the box](/docs/images/purchases-privacy-box.png) diff --git a/articles/telemetry-client.md b/articles/telemetry-client.md deleted file mode 100644 index e46aaea..0000000 --- a/articles/telemetry-client.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -title: TelemetryDeck Swift Client Reference -tags: - - Swift - - SDK -testedOn: Xcode 12.4 & Swift 5.3 -description: Reference documentation for the Swift Client for TelemetryDeck-using apps -lead: The TelemetryDeck Swift Client is a Swift Package to include in your app -searchEngineTitle: How to add the TelemetryDeck Swift Client -searchEngineDescription: Learn how to add the Swift Client for TelemetryDeck-using apps ---- - -## Include the Swift Client in your Xcode Project - -See the [Swift Guide](/docs/guides/swift-setup/) on how to set up your app to use the TelemetryDeck Swift Client. - -## Initialization - -Init the TelemetryDeck at app startup, so it knows your App ID (you can retrieve the App ID in the TelemetryDeck Viewer app, under App Settings) - -```swift -let config = TelemetryDeck.Config(appID: "") -TelemetryDeck.initialize(config: config) -``` - -For example, if you're building a scene based app, in the `init()` function for your `App`: - -```swift -import SwiftUI -import TelemetryDeck - -@main -struct TelemetryTestApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } - - init() { - // Note: Do not add this code to `WindowGroup.onAppear`, which will be called - // *after* your window has been initialized, and might lead to out initialization - // occurring too late. - let config = TelemetryDeck.Config(appID: "") - TelemetryDeck.initialize(config: config) - } -} -``` - -## Sending Signals - -Once you've included the TelemetryDeck Swift Client, send signals like so: - -```swift -TelemetryDeck.signal"appLaunchedRegularly") -``` - -TelemetryDeck will create a user identifier for your user that is specific to app installation and device. If you have a better user identifier available, you can use that instead: (the identifier will be hashed before sending it) - -```swift -let email = MyConfiguration.User.Email -TelemetryDeck.signal"userLoggedIn", customUserID: email) -``` - -## Payload Data - -You can also send additional parameters with each signal: - -```swift -TelemetryDeck.signal("databaseUpdated", parameters: ["numberOfDatabaseEntries": "3831"]) -``` - -TelemetryDeck will automatically send base parameters with these keys: - -- `TelemetryDeck.AppInfo.buildNumber` -- `TelemetryDeck.AppInfo.dartVersion` -- `TelemetryDeck.AppInfo.version` -- `TelemetryDeck.AppInfo.versionAndBuildNumber` -- `TelemetryDeck.Device.architecture` -- `TelemetryDeck.Device.brand` -- `TelemetryDeck.Device.modelName` -- `TelemetryDeck.Device.operatingSystem` -- `TelemetryDeck.Device.orientation` -- `TelemetryDeck.Device.platform` -- `TelemetryDeck.Device.screenResolutionWidth` -- `TelemetryDeck.Device.screenResolutionHeight` -- `TelemetryDeck.Device.systemMajorVersion` -- `TelemetryDeck.Device.systemMajorMinorVersion` -- `TelemetryDeck.Device.systemVersion` -- `TelemetryDeck.Device.timeZone` -- `TelemetryDeck.RunContext.extensionIdentifier` -- `TelemetryDeck.RunContext.isAppStore` -- `TelemetryDeck.RunContext.isDebug` -- `TelemetryDeck.RunContext.isSimulator` -- `TelemetryDeck.RunContext.isTestFlight` -- `TelemetryDeck.RunContext.language` -- `TelemetryDeck.RunContext.locale` -- `TelemetryDeck.RunContext.targetEnvironment` -- `TelemetryDeck.SDK.name` -- `TelemetryDeck.SDK.nameAndVersion` -- `TelemetryDeck.SDK.version` -- `TelemetryDeck.UserPreference.region` -- `TelemetryDeck.UserPreference.language` - -## Debug Mode - -TelemetryDeck will _not_ send any signals if you are in `DEBUG` Mode. You can override this by setting `configuration.telemetryAllowDebugBuilds = true` on your `TelemetryDeck.Configuration` instance. diff --git a/articles/test-mode.md b/articles/test-mode.md deleted file mode 100644 index 528b77b..0000000 --- a/articles/test-mode.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: Getting started with Test Mode -tags: - - setup - - testmode - - quickstart - - beginner -testedOn: Xcode 13.1 & Swift 5.5 & TelemetryDeck SDK 1.1.5 -description: Here's how to use Test Mode to get started with TelemetryDeck -lead: Test Mode helps you make sure that TelemetryDeck is set up correctly in your app and allows you to set up your insights even during development. -searchEngineTitle: How to run test signals -searchEngineDescription: With test mode, you can review your analytics setup without compromising live data. Enable Test Mode in your dashboard to send signals through debug mode. ---- - -During the development of your TelemetryDeck-enabled app - or even while you test it - your app sends signals. These signals are not from your users but rather from yourself or your development team. You might even send hundreds of signals during tests, which would mess up your insights if mixed with actual analytics data. Not cool! - -We do not recommend not doing any testing. The benefits of sending signals during test phases are enormous! If you have not considered it yet, here are some nifty reasons why you should start now: - -- You will be able to find errors in the configuration of the TelemetryDeck SDK -- Working with test signals means you will know if your app works even before releasing your app -- As well as being able to make preparations for new signal types or payload types until your app is released - -Test Mode will let you easily and quickly test new features for your app, giving you the power to release the best product possible! Let's dive right in. - -## How it works - -Each sent signal has a `isTestMode` parameter, which can either be `true` or `false`. -Navigate to your TelemetryDeck [dashboard](https://dashboard.telemetrydeck.com/), where you will find the Test Mode toggle on the top left side, just above the sidebar. -You can toggle it to show your signals either in `isTestMode == true` or `isTestMode == false`. While toggled to `true`, you will see a banner at the top displaying **Test Data** to remind you that you are currently in Test Mode, and all signals get sent in said mode. All charts will display test data only while in Test Mode. - -![Screenshot of the dashboard showing the Test Mode toggle in the upper left corner.](/docs/images/test_mode.png) - -## Sending Signals in Test Mode - -The SDKs try to infer the isTestMode parameter as best as they can. For example, if a DEBUG parameter is present in your development environment, that is used as the value for isTestMode. -You can also override the isTestMode parameter just as you would add any other payload parameter to a signal -Note: since signal payloads only support strings, the parameter needs to be either "true" or "false" - -### Manually set Test Mode in Swift SDK - -```swift -// An example variable to manually set test mode. -// Set this to `true` or `false` depending on your app's configuration -// or environment or state -let customTestModeParameter = true - -TelemetryManager.send( - "pizzaModeActivated", - for: "myUserIdentifier", - with: ["isTestMode": customTestModeParameter ? "true" : "false"] -)` -``` - -### Manually set Test Mode in JavaScript SDK - -```javascript -// Example initialisation of TelemetryDeck SDK -`td = new TelemetryDeck({ - app: ENV.APP.telemetryAppID, - user: this.user.current?.email ?? 'anonymous', -});` - -// In our example, the app has a `send` function wrapping the TelemetryDeck SDK -send(payload) { - // ENV.APP.telemetryIsDebug is an example variable that represents your app's - // configuration or environment. Replace it with an implementation that fits your - // app's needs. - if (ENV.APP.telemetryIsDebug) { - this.td.signal({...payload, isTestMode: "true"}) - return; - } - - this.td.signal(payload); -} -``` diff --git a/basics/metrics.md b/basics/metrics.md deleted file mode 100644 index f5c438a..0000000 --- a/basics/metrics.md +++ /dev/null @@ -1,175 +0,0 @@ ---- -title: Metrics Analytics -tags: - - setup - - basics - - technical-metrics - - analytics -basics: true -description: Learn how to interpret and act on TelemetryDeck's technical metrics to optimize your app's performance and compatibility. -lead: Metrics Analytics provides critical technical insights about how your app performs across different devices, platforms, and configurations. TelemetryDeck automatically collects these details to help you make informed decisions about development priorities, OS support, and accessibility needs. -searchEngineTitle: App Technical Metrics & Analytics | TelemetryDeck -searchEngineDescription: Learn how to interpret technical metrics data for your app with TelemetryDeck's automatic device, version, and error analytics. -order: 70 ---- - -## What are Technical Metrics? - -Technical metrics represent the operational aspects of your app – how it performs across devices, versions, languages, and accessibility settings. These metrics help you understand your app's compatibility landscape, identify technical issues, and prioritize development efforts. - -With TelemetryDeck's Metrics dashboard, these insights are automatically collected and visualized with no additional code required beyond the basic SDK integration. - -## Device & platform distribution - -**Questions you can answer:** -- What devices are my users using to access my app? -- Which platforms should I prioritize for development and testing? -- When can I safely drop support for older devices? - -![Device and Platform Distribution](/docs/images/metrics-devices.png) - -**How to interpret the charts:** -- **Models**: Shows specific device models being used, helping prioritize testing -- **Types**: Reveals device categories for understanding user hardware preferences -- **Platforms**: Displays the OS platform breakdown (iOS, macOS, visionOS) -- **Platform distribution changes**: Identifies shifts in platform usage over time - -**Action example:** Looking at the Models chart, we can see MacBook Air (M1 and M2) models account for 17% of devices, while new Apple Vision Pro (RealityDevice) users represent 7%. iPhone models collectively account for a significant portion of usage. With iOS accounting for 62.05% of platform usage and macOS at 34.55%, you should prioritize iOS testing while maintaining solid macOS support. The emergence of visionOS at 3.40% shows early adoption of this platform that may warrant attention for future development. - -## Version analytics - -**Questions you can answer:** -- How quickly are users adopting my latest app version? -- When do users typically update after a new release? -- Are there patterns in build adoption? -- Which SDK versions are in use across your user base? - -![Version Analytics](/docs/images/metrics-versions.png) - -**How to interpret the charts:** -- **App Versions**: Tracks adoption of your app releases over time -- **Build Numbers**: Provides more granular insight into specific build adoption -- **SDK Versions**: Reveals which versions of your SDK are in use - -**Action example:** The App Versions chart shows a clear transition starting in early March, with users moving from the previous version (green) to the latest release (blue). This transition happened relatively quickly, suggesting users are responsive to updates. The Build Numbers chart shows a similar pattern, with a diverse range of builds in use before March consolidating to newer versions. The SDK Versions chart reveals an interesting transition from version 2.2.3 (yellow) to 2.2.4 (light green) and SwiftClient 1.5.1 (green) in March, suggesting your SDK updates are being adopted alongside app updates. This data indicates that you can likely count on most users updating within 2-3 weeks of a release. - -## Error monitoring - -**Questions you can answer:** -- What are the most common errors occurring in my app? -- When did error rates spike or change? -- Which errors should I prioritize fixing? -- Are errors occurring on specific devices or platforms? - -![Error Monitoring](/docs/images/metrics-errors.png) - -**How to interpret the charts:** -- **Most Frequent Errors**: Ranks issues by occurrence count and percentage -- **Error History**: Shows error frequency over time, highlighting spikes -- **Thrown Exception Errors**: Details specific exception types -- **Error distribution**: Helps correlate errors with platforms or versions - -**Action example:** The "Most Frequent Errors" chart identifies "ProjectDetails.loadAppInfo" as your most common error (29% of total errors), followed by "AITranslation.loadAvailability" (25%). These two issues account for more than half of all errors, making them high-priority fixes. The error history graph shows a significant spike in mid-March, which may correlate with a specific release or backend change. Focusing on resolving these top issues first will have the largest impact on improving app stability for your users. - -{% noteinfo "Action Required" %} -TelemetryDeck offers built-in presets for error tracking that require some configuration in your app code. With a few simple implementation steps, you can collect and categorize errors to identify patterns and prioritize fixes. -{% endnoteinfo %} - -
-
-
- -
-

- - Implementing Error Tracking -

-

Learn how to set up TelemetryDeck's error reporting system in your app code to capture and analyze runtime issues.

-
-
-
-
- -## Localization insights - -**Questions you can answer:** -- What languages do my users speak? -- Which regions show significant app usage? -- Should I invest in additional localizations? -- Are users using my app in the same language as their device? - -![Localization Insights](/docs/images/metrics-localization.png) - -**How to interpret the charts:** -- **Preferred Language**: Shows user device language settings -- **App Language**: Indicates which language the system (or user) has selected for your app -- **Region**: Displays geographical distribution of your users -- **Layout Direction**: Shows text direction preferences (LTR vs RTL) - -**Action example:** The data shows a diverse user base with English (45.52% preferred/51.87% app) and German (30.94% preferred/31.12% app) leading language usage. Germany (25.49%), US (20.60%), and UK (13.29%) are your top markets. The strong German presence in both language and regional data suggests significant opportunity for enhanced German-language support and targeted marketing in German-speaking countries. - -## Accessibility usage - -**Questions you can answer:** -- How many of my users utilize accessibility features? -- Which accessibility settings are most common? -- Does my app need better accessibility support? -- How would design changes impact users with accessibility needs? - -![Accessibility Usage](/docs/images/metrics-accessibility.png) - -**How to interpret the charts:** -- **Preferred Content Size**: Shows text size preferences (88.43% use default size "L") -- **Bold Text Usage**: Indicates users who need stronger text contrast (3.31%) -- **Reduce Motion**: Shows users sensitive to animations (0% in this sample) -- **Reduce Transparency**: Reveals users who need less UI transparency (4.13%) - -**Action example:** While most users (88.43%) use the default content size "L", a significant portion (6.61%) use the larger "XXXL" setting, indicating they need much larger text for readability. Additionally, 4.13% of users have enabled the Reduce Transparency setting. These metrics suggest you should test your app thoroughly with larger text settings and ensure it remains functional and attractive for users who need these accessibility features. The complete absence of "Reduce Motion" usage in your current user base doesn't mean you can ignore motion considerations – it may simply reflect that users who need this setting aren't using your app yet. - -## Making data-driven decisions with Metrics - -Your technical metrics provide critical insights for development decisions. Consider these key questions as you analyze the data: - -1. **Platform strategy** - - Which device models and OS versions represent your core users? - - When can you adopt new platform features or drop legacy support? - - Does your user base justify expanding to emerging platforms? - -2. **Technical improvements** - - Which errors have the highest impact on your user experience? - - Are specific devices or configurations experiencing more issues? - - How quickly are users adopting your latest app version? - -3. **Localization priorities** - - Which languages and regions show significant adoption? - - Are users using your app in their preferred language? - - Would accessibility improvements benefit a meaningful portion of users? - -Remember that technical metrics complement your user behavior analytics. By combining device insights with acquisition and engagement data, you'll build a complete picture of how technical decisions impact real-world user experience. - -## Related guides - -
-
-
- -
-

- - Strategic OS Support Planning -

-

Learn the practical framework for balancing backward compatibility with development resources using metrics data.

-
-
-
- -
-

- - Version Migration Insights -

-

Discover patterns in how quickly users update and what this means for your release strategy.

-
-
-
-
\ No newline at end of file diff --git a/docs.11tydata.js b/docs.11tydata.js deleted file mode 100644 index e10251a..0000000 --- a/docs.11tydata.js +++ /dev/null @@ -1,12 +0,0 @@ -let data = { - layout: "docs/docpage.njk", - tags: "docs", - sitemapchangefrequency: "daily", - sitemappriority: "0.7", -}; - -// if(process.env.NODE_ENV === "production") { -data.date = "git Last Modified"; -// } - -module.exports = data; diff --git a/api/api-query-from-insight.md b/docs/api/api-query-from-insight.md similarity index 92% rename from api/api-query-from-insight.md rename to docs/api/api-query-from-insight.md index 543927d..36b58b0 100644 --- a/api/api-query-from-insight.md +++ b/docs/api/api-query-from-insight.md @@ -6,11 +6,9 @@ description: Using the TelemetryDeck API, you can retrieve the query that is use lead: Using the TelemetryDeck API, you can retrieve the query that is used in an insight --- -{% notewarning "Paid plans only" %} +!!! warning "Paid plans only" -API access β€” including retrieving an insight's query β€” is available on TelemetryDeck's paid plans. If your organization is on the free plan, upgrade your plan first and you'll be able to generate a personal access token and use the API. - -{% endnotewarning %} + API access β€” including retrieving an insight's query β€” is available on TelemetryDeck's paid plans. If your organization is on the free plan, upgrade your plan first and you'll be able to generate a personal access token and use the API. ## Authorization diff --git a/api/api-run-query.md b/docs/api/api-run-query.md similarity index 71% rename from api/api-run-query.md rename to docs/api/api-run-query.md index f7e6c04..abd9842 100644 --- a/api/api-run-query.md +++ b/docs/api/api-run-query.md @@ -7,11 +7,9 @@ lead: Using the TelemetryDeck API, you can run a query and retrieve its results order: 2 --- -{% notewarning "Paid plans only" %} +!!! warning "Paid plans only" -API access β€” including running queries β€” is available on TelemetryDeck's paid plans. If your organization is on the free plan, upgrade your plan first and you'll be able to generate a personal access token and run queries. - -{% endnotewarning %} + API access β€” including running queries β€” is available on TelemetryDeck's paid plans. If your organization is on the free plan, upgrade your plan first and you'll be able to generate a personal access token and run queries. ## Authorization @@ -87,31 +85,24 @@ Host: api.telemetrydeckapi.com Response: ```json -{ - "calculationDuration": 0.21845901012420654, - "calculationFinishedAt": "2022-07-11T14:00:44+0000", - "result": { - "rows": [ +[ + { + "result": [ { - "result": [ - { - "count": 516, - "modelName": "iPhone13,1" - }, - - // ... - - { - "count": 2, - "modelName": "iPad8,6" - } - ], - "timestamp": "2022-06-11T00:00:00+0000" + "count": 516, + "modelName": "iPhone13,1" + }, + + // ... + + { + "count": 2, + "modelName": "iPad8,6" } ], - "type": "topNResult" + "timestamp": "2022-06-11T00:00:00+0000" } -} +] ``` -That's it β€” the `result` field contains the data, and `calculationDuration` tells you how long the query took on our side. +That's it β€” the response body is the raw query result. Its exact shape depends on the query type (for example, a `topN` query returns an array of timestamped result buckets as shown above, while a `timeseries` query returns one entry per time bucket). diff --git a/api/api-token.md b/docs/api/api-token.md similarity index 90% rename from api/api-token.md rename to docs/api/api-token.md index a13152c..c062aeb 100644 --- a/api/api-token.md +++ b/docs/api/api-token.md @@ -7,11 +7,9 @@ lead: To interact with the TelemetryDeck API, you need a personal access token ( order: 1 --- -{% notewarning "Paid plans only" %} +!!! warning "Paid plans only" -Personal access tokens are available on TelemetryDeck's paid plans. If your organization is on the free plan, upgrade your plan first and the feature will become available in the dashboard. - -{% endnotewarning %} + Personal access tokens are available on TelemetryDeck's paid plans. If your organization is on the free plan, upgrade your plan first and the feature will become available in the dashboard. ## Generating a Personal Access Token diff --git a/api/api.json b/docs/api/api.json similarity index 100% rename from api/api.json rename to docs/api/api.json diff --git a/api/insights-reference.md b/docs/api/insights-reference.md similarity index 100% rename from api/insights-reference.md rename to docs/api/insights-reference.md diff --git a/api/signals-reference.md b/docs/api/signals-reference.md similarity index 100% rename from api/signals-reference.md rename to docs/api/signals-reference.md diff --git a/articles/anonymization-how-it-works.md b/docs/articles/anonymization-how-it-works.md similarity index 92% rename from articles/anonymization-how-it-works.md rename to docs/articles/anonymization-how-it-works.md index 5f74634..ab589f2 100644 --- a/articles/anonymization-how-it-works.md +++ b/docs/articles/anonymization-how-it-works.md @@ -8,7 +8,7 @@ description: TelemetryDeck anonymizes user data, by double salting and hashing I lead: We take user privacy seriously. To ensure the privacy of our users, we use various techniques to anonymize user data. One of the ways we do this is by using a double hashing technique to anonymize user identifiers. searchEngineTitle: How TelemetryDeck anonymizes user data privacy-friendly searchEngineDescription: TelemetryDeck anonymizes user data, by double hashing and salting IDs, to ensure anonymity and protect user privacy while still providing valuable insights. -headerImage: /docs/images/anonymization-display-image.jpg +headerImage: /assets/anonymization-display-image.jpg --- ## Methodology @@ -19,7 +19,7 @@ At TelemetryDeck, we even go a step further: the β€œidentification date” is no **Check out why [App Store Connect is not enough](https://www.youtube.com/watch?v=4_WqJGPzgmY) to protect user data!** -[![Compare TelemetryDeck and App Store Connect for Analytics](/docs/images/yt-app-store-connect.png)](https://www.youtube.com/watch?v=4_WqJGPzgmY) +[![Compare TelemetryDeck and App Store Connect for Analytics](/assets/yt-app-store-connect.png)](https://www.youtube.com/watch?v=4_WqJGPzgmY) ## User identifiers @@ -35,7 +35,7 @@ If you use the same (custom) salt and provide the same original identifier, you **Check out our [YouTube Channel](https://www.youtube.com/watch?v=4sQcZoi21nU) for a deeper dive into our anonymization process!** -[![The Benefits of TelemetryDeck For Privacy Focused App Analytics](/docs/images/yt-app-analytics.png)](https://www.youtube.com/watch?v=4sQcZoi21nU) +[![The Benefits of TelemetryDeck For Privacy Focused App Analytics](/assets/yt-app-analytics.png)](https://www.youtube.com/watch?v=4sQcZoi21nU) ## Conclusion diff --git a/articles/app-privacy-report.md b/docs/articles/app-privacy-report.md similarity index 100% rename from articles/app-privacy-report.md rename to docs/articles/app-privacy-report.md diff --git a/articles/app-tracking-transparency.md b/docs/articles/app-tracking-transparency.md similarity index 100% rename from articles/app-tracking-transparency.md rename to docs/articles/app-tracking-transparency.md diff --git a/articles/apple-app-privacy.md b/docs/articles/apple-app-privacy.md similarity index 85% rename from articles/apple-app-privacy.md rename to docs/articles/apple-app-privacy.md index 88c4cb1..267bfa3 100644 --- a/articles/apple-app-privacy.md +++ b/docs/articles/apple-app-privacy.md @@ -16,7 +16,7 @@ Since TelemetryDeck's analytics are private by default, you can include this inf This means your app will get an excellent privacy rating. -![A screenshot of Apple's Privacy Overview](/docs/images/privacy-overview.png) +![A screenshot of Apple's Privacy Overview](/assets/privacy-overview.png) ## How do I start? @@ -34,14 +34,12 @@ In the second screen, scroll down until you see the **Identifiers** section. In TelemetryDeck's **default mode**, with no user identifier specified, check the **Device ID** checkmark. This is what identifies individual users to TelemetryDeck. -{% noteinfo "Other Types of Identifiers" %} +!!! warning "Other Types of Identifiers" -In case you use TelemetryDeck in a more advanced way where you supply a custom user identifier, you'll need to think about this for a second: + In case you use TelemetryDeck in a more advanced way where you supply a custom user identifier, you'll need to think about this for a second: -- If you instead specify a User Identifier such as email address or username to TelemetryDeck, check instead the **User ID** checkmark. The identifier is only transmitted as a hash, but it still counts as a user identifier. -- If you instead are purposefully disabling user tracking by handing the same string to TelemetryDeck for each user, you don't need to check any of the checkboxes in the **Identifiers** section. - -{% endnoteinfo %} + - If you instead specify a User Identifier such as email address or username to TelemetryDeck, check instead the **User ID** checkmark. The identifier is only transmitted as a hash, but it still counts as a user identifier. + - If you instead are purposefully disabling user tracking by handing the same string to TelemetryDeck for each user, you don't need to check any of the checkboxes in the **Identifiers** section. ### Usage Data diff --git a/articles/articles.json b/docs/articles/articles.json similarity index 100% rename from articles/articles.json rename to docs/articles/articles.json diff --git a/articles/login-options.md b/docs/articles/authentication.md similarity index 58% rename from articles/login-options.md rename to docs/articles/authentication.md index 0ab72c4..038f5a8 100644 --- a/articles/login-options.md +++ b/docs/articles/authentication.md @@ -1,24 +1,23 @@ --- +icon: fontawesome/solid/fingerprint title: How to login to TelemetryDeck tags: - setup - quickstart - beginner - how-to -description: Learn about the ways you can login to the TelemetryDeck dashboard -lead: TelemetryDeck offers multiple ways to login to the dashboard. Just pick the one that suits your needs the most! -order: 5 --- -## Login options To login into the TelemetryDeck dashboard you can use any of these options: + * Username + password * Google * GitHub * Okta (for our enterprise customers, on request) +![Login mask](/assets/login-mask.png) -![Login mask](/docs/images/login-mask.png) +--- ## Frequently Asked Questions **Does TemeletryDeck support 2FA?** @@ -30,4 +29,6 @@ Yes, we support 2FA with Okta, GitHub login, and Google login. We can add another identity provider if you need, just let us know! -{% callToAction "Create your free TelemetryDeck account now" "Get awesome privacy-first analytics for your app" %} +--- + +[Create your free TelemetryDeck account now](https://dashboard.telemetrydeck.com/){ .md-button .md-button--primary } \ No newline at end of file diff --git a/articles/check-if-users-upgrade-to-latest-app-version.md b/docs/articles/check-if-users-upgrade-to-latest-app-version.md similarity index 100% rename from articles/check-if-users-upgrade-to-latest-app-version.md rename to docs/articles/check-if-users-upgrade-to-latest-app-version.md diff --git a/articles/making-account.md b/docs/articles/create-an-account.md similarity index 76% rename from articles/making-account.md rename to docs/articles/create-an-account.md index 904c7d8..2a98aad 100644 --- a/articles/making-account.md +++ b/docs/articles/create-an-account.md @@ -1,14 +1,11 @@ --- -title: Making an account +icon: fontawesome/solid/hat-wizard +title: Create an account tags: - setup - quickstart - beginner - how-to -description: Register an account and create an organization -lead: Let's register for an account, create an app, and set up your application so you can get started with receiving signals from your app! -searchEngineTitle: Create a TelemetryDeck account -searchEngineDescription: Register an account now and create an organization in the TelemetryDeck dashboard. --- ## Step one: Register an account @@ -30,4 +27,4 @@ In the dashboard, have a look at your left sidebar. Under the `SETTINGS`category That's it, you're now ready for the next step, in which you will learn to configure your first insight for analyzing your signals! -Next Up: Setting up your application β†’ +Next Up: Setting up your application β†’ diff --git a/articles/create-custom-dashboards.md b/docs/articles/create-custom-dashboards.md similarity index 89% rename from articles/create-custom-dashboards.md rename to docs/articles/create-custom-dashboards.md index 0aadfae..8216dc8 100644 --- a/articles/create-custom-dashboards.md +++ b/docs/articles/create-custom-dashboards.md @@ -16,7 +16,7 @@ order: To create a dashboard for your insights, go to your [TelemetryDeck dashboard](https://dashboard.telemetrydeck.com/), navigate to `Dashboards` and look at the left sidebar. Here you can find the `New Dashboard` button. Create a _New Dashboard_, give your dashboard a name and then click _Create_. You now have a new insight dashboard in your sidebar! -![Location of the "Create New Dashboard" Button](/docs/images/location-create-dashboard.png) +![Location of the "Create New Dashboard" Button](/assets/location-create-dashboard.png) ## Share your dashboards with your organization @@ -31,20 +31,20 @@ You can also share specific dashboards with the members of your organization. To Now let's **create** an insight. While in your newly created dashboard, navigate to the `Actions` drop down button on the top-right side. Click the _Create new Insight_ button and give your new insight a name. Please choose from one of our convenient templates! The insight will be automagically ✨ generated for you. If needed, you can also change the display mode, title, query type, and more - even after creating it. -![Location of the "Create New Insight" Button](/docs/images/create-new-insight.png) +![Location of the "Create New Insight" Button](/assets/create-new-insight.png) You can create a bunch of insights inside your dashboards and thus, combine thematically related charts of your insights. And you're able to resize your charts to regular-sized `Compact` cards or big `Wide` cards. To **delete** insights from a dashboard, click on the insights' name. Go to `Actions` in the top right corner, just delete the current insight by clicking on "Delete Insight". -![Location of the "Delete Insight" Button](/docs/images/location-edit-insight.png) +![Location of the "Delete Insight" Button](/assets/location-edit-insight.png) ### Rename your dashboard Not happy with the name given to your dashboard? No problem - you can change it quickly! Click on the dashboard whose name you want to change. Then hover over your dashboard name, and click to edit. Save your changes after you are done editing - your dashboard now has a new name. -![Location of the "Rename Dashboard" Button](/docs/images/edit-dashboard-name.png) +![Location of the "Rename Dashboard" Button](/assets/edit-dashboard-name.png) ### Delete your dashboard diff --git a/articles/decide-to-drop-ios-version.md b/docs/articles/decide-to-drop-ios-version.md similarity index 98% rename from articles/decide-to-drop-ios-version.md rename to docs/articles/decide-to-drop-ios-version.md index ddd9c7b..6380978 100644 --- a/articles/decide-to-drop-ios-version.md +++ b/docs/articles/decide-to-drop-ios-version.md @@ -27,7 +27,7 @@ The TelemetryDeck SDK by default tries to collect data about the user's operatin To find out which percentage of your users are using a certain OS version, use a **Breakdown Insight** (also known as a **Top N Insight**). Then you'll have multiple options for the breakdown key. TelemetryDeck provides pre-built dashboards, among others the Top N Insight just described: -![Screenshot SystemVersion](/docs/images/Screenshot_SystemVersion.png) +![Screenshot SystemVersion](/assets/Screenshot_SystemVersion.png) You can switch between `majorMinorVersion` and `majorVersion` in the chart. Below we come back to the difference with recommendations what to use for this purpose. As an orientation beyond your app we also want to point out our monthly updated [surveys](https://telemetrydeck.com/survey/). Through these we provide information about [majorMinorVersions](https://telemetrydeck.com/survey/apple/iOS/minorSystemVersions/) and [majorVersions](https://telemetrydeck.com/survey/apple/iOS/majorSystemVersions/) currently used according to our data set as well as device related information regarding [iPhone models](https://telemetrydeck.com/survey/apple/iPhone/models/) for example. diff --git a/docs/articles/documentation.md b/docs/articles/documentation.md new file mode 100644 index 0000000..441f325 --- /dev/null +++ b/docs/articles/documentation.md @@ -0,0 +1,283 @@ +--- +title: How to write this Documentation +tags: documentation +--- + +# Get Started + +TelemetryDeck uses Zensical. For full documentation visit [zensical.org](https://zensical.org/docs/). + +## Document Metadata + +All metadata for a documentation page is specified in the YAML header at the top of its markdown file (also called the `front matter` of the document). Most of the metadata is optional, but the `title` is required: + +```yaml +--- +icon: fontawesome/solid/icons +title: How to write this Documentation +tags: + - tag 1 + - ... + - tag n +status: new +--- +``` + +The `title` string is how the page is titled in the left sidebar, at the top of the documentation page and in the "Getting Started" page, should it appear there. + +Tags are used to organize documentation pages in the sidebar and to link related pages. + +__One tag:__ + + ```yaml + tags: Swift + ``` + +__Multiple tags:__ + + ```yaml + tags: + - Setup + - Quickstart + - Code + - Swift + ``` + +Use the `tags` metadata to add additional tags that link articles together, such as + +- The type of page (`setup`, `code`) +- The software stack or language (`swiftui`, `android`, `kotlin` etc.) +- The experience level of the reader (`beginner`, `intermediate`, `advanced`) +- The type of query (`filter`, `cohorts`, etc.) + +The `status` metadata is used to show the current status of a page. The following status identifiers are already defined: + +- :lucide-badge-alert: – `new`: for brand new pages that deserve to stand out +- :lucide-trash: – `deprecated`: for pages that are not up to date anymore but still relevant to keep + +### Compatibility and Contribution + +The right sidebar contains a list of compatibility information for the documentation page. If `testedOn` is set, it will display the `testedOn` value. In addition, it will show the date when the markdown file for the documentation page was last updated. This gives the reader additional hints as to how outdated or current the page is. + +The right sidebar will also display a list of contributors who have written an git commit that touches the specific markdown file. The documentation system will try and retrieve the GitHub avatar images for the contributors by + +1. Retrieving the committer email from the git commit +1. Searching for that email in the GitHub API +1. Downloading the avatar image from the GitHub API + +This will fail if the email is not found in the GitHub API, or if the email is not set as "public email" in the contributor's GitHub profile. + +## Best practices + +### Capitalization +We capitalize our Headings in **Sentence Case** (e.g., "This is a heading of our docs"). + +### Date and Time Format +For date formats we use either: + +* International/European date format: `DD/MM/YYYY` (e.g., 01.04.2026) +* ISO 8601: `YYYY-MM-DD` (e.g., 2026-04-01) + +For times we use either: + +* 24-Hour: `HH:mm:ss` (e.g., 14:30:05) +* 12-Hour: `hh:mm:ss tt` (e.g., 02:30:05 PM). `AM/PM` is capitalized. + +## Tables + +Tables are supported in markdown. Here is an example: + +| Name | Description | +| ------- | ------------------------------------------------------------------------------------------------------------------ | +| `title` | The title of the page. This is used in the left sidebar, at the top of the page and in the "Getting Started" page. | + +Here is the markdown code for the previous table: + +```markdown +| Name | Description | +| ------- | ------------------------------------------------------------------------------------------------------------------ | +| `title` | The title of the page. This is used in the left sidebar, at the top of the page and in the "Getting Started" page. | +``` + +## Formatting + +Documentation is written in [Markdown](https://www.markdownguide.org). This means you can write documentation in plain text, and it will be converted to HTML. All standard Markdown elements are supported, such as **bold text**, _italic text_, `inline code`, and [links](https://www.markdownguide.org/basic-syntax/#link). + +> Go to [documentation](https://zensical.org/docs/authoring/formatting/) + +- ==This was marked (highlight)== +- ^^This was inserted (underline)^^ +- ~~This was deleted (strikethrough)~~ +- H~2~O +- A^T^A +- ++ctrl+alt+del++ + +### Icons, Emojis + +> Go to [documentation](https://zensical.org/docs/authoring/icons-emojis/) + +* :sparkles: `:sparkles:` +* :rocket: `:rocket:` +* :tada: `:tada:` +* :memo: `:memo:` +* :eyes: `:eyes:` + +### Unordered lists + +* are just +* asteriks or dashes + +### Ordered lists + +1. are +1. just +3. numbers + +Here is the markdown code for the previous paragraphs: + +```markdown +All standard Markdown elements are supported, +such as **bold text**, _italic text_, `inline code`, +and [links](https://www.markdownguide.org/basic-syntax/#link). + +Unordered lists + +* are just +* asteriks or dashes + +and ordered lists + +1. are +1. just +3. numbers +``` + +## Images + +To display an image in a docs article, add it to the `assets` directory. You can then link it using regular markdown image syntax, adding `/assets/` before the image's name. + +Example: You just added the file `privacy-overview.png` to the `assets` folder. You can now display that image like so: + +```markdown +![A screenshot of Apple's Privacy Overview](/assets/privacy-overview.png) +``` + +The first part is the image's alt text. The second part is the path (`/assets/`) and the image file name `privacy-overview.png`. + +Here's what it looks like: + +![A screenshot of Apple's Privacy Overview](/assets/privacy-overview.png) + +!!! warning "Image File Locations" + + Image files need to live in the `assets` directory inside `docs/`. Image files elsewhere in the file hierarchy will be ignored. + + Prefix `/assets/` to the path when linking to the image β€” the path is resolved against the docs site root. + + +## Code Blocks + +> Go to [documentation](https://zensical.org/docs/authoring/code-blocks/) + +Code blocks begin with three backticks (\`) and end with three backticks (\`). On the same line as the opening backticks, you **must** specify the programming language of the code block. + +````text +```swift +print("Hello, world!") +``` +```` + +Results in: + +```swift +print("Hello, world!") +``` + +If you can't specify a language, please use `text` instead. + +````text +```text +this is + just + plain text +``` +```` + +### Content tabs + +> Go to [documentation](https://zensical.org/docs/authoring/content-tabs/) + +=== "Python" + + ``` python + print("Hello from Python!") + ``` + +=== "Rust" + + ``` rs + println!("Hello from Rust!"); + ``` + +## Admonitions + +> Go to [documentation](https://zensical.org/docs/authoring/admonitions/) + +!!! info + This is an **info** admonition. Use to provide additional information. + +!!! note + + This is a **note** admonition. Use it to provide helpful information. + +!!! warning + + This is a **warning** admonition. Be careful! + +### Details + +> Go to [documentation](https://zensical.org/docs/authoring/admonitions/#collapsible-blocks) + +??? info "Click to expand for more info" + + This content is hidden until you click to expand it. + Great for FAQs or long explanations. + +## Buttons + +You can use button links to link to especially important pages or resources. Make a button by supplying + +- a label +- an URL to link to +- and a CSS class selector to indicate wether the button is a primary button or a secondary button + +```markdown +[Subscribe to our newsletter](https://telemetrydeck.com/newsletter/){ .md-button .md-button--primary } +[Subscribe to our newsletter](https://telemetrydeck.com/newsletter/){ .md-button } +``` + +[Subscribe to our newsletter](https://telemetrydeck.com/newsletter/){ .md-button .md-button--primary } +[Subscribe to our newsletter](https://telemetrydeck.com/newsletter/){ .md-button } + +## Diagrams + +> Go to [documentation](https://zensical.org/docs/authoring/diagrams/) + +``` mermaid +graph LR + A[Start] --> B{Error?}; + B -->|Yes| C[Hmm...]; + C --> D[Debug]; + D --> B; + B ---->|No| E[Yay!]; +``` + +## Footnotes + +> Go to [documentation](https://zensical.org/docs/authoring/footnotes/) + +Here's a sentence with a footnote.[^1] + +Hover it, to see a tooltip. + +[^1]: This is the footnote. \ No newline at end of file diff --git a/docs/articles/duration-signals.md b/docs/articles/duration-signals.md new file mode 100644 index 0000000..595f18f --- /dev/null +++ b/docs/articles/duration-signals.md @@ -0,0 +1,189 @@ +--- +title: Duration Events +tags: + - setup + - how-to + - beginner + - swift +description: Duration events let you measure how long users spend on specific activities in your app. +lead: Measure how long users spend on different activities in your app β€” onboarding steps, content consumption, checkout flows, or any user journey. +searchEngineTitle: Track User Engagement with Duration Events in TelemetryDeck +searchEngineDescription: Learn how to implement and analyze time-based metrics in your app using TelemetryDeck's duration events. +testedOn: SwiftSDK 3.0.0 +--- + +## How it works + +Bracket an activity with two calls β€” start and stop. The SDK handles timing, background exclusion, and parameter merging automatically. + +```swift +await TelemetryDeck.startDurationEvent("activityName") + +// ... user performs the activity ... + +await TelemetryDeck.stopAndSendDurationEvent("activityName") +``` + +The duration is included as `TelemetryDeck.Signal.durationInSeconds` with millisecond precision (3 decimal places). + +### SwiftUI view lifecycle + +```swift +struct TutorialView: View { + var body: some View { + VStack { + Text("Welcome to the Tutorial!") + } + .onAppear { + Task { await TelemetryDeck.startDurationEvent("tutorial") } + } + .onDisappear { + Task { await TelemetryDeck.stopAndSendDurationEvent("tutorial") } + } + } +} +``` + +Both functions accept optional `parameters` for additional context. + +## SDK requirements + +- Swift SDK: 3.0.0 or later +- Kotlin SDK: 4.1.0 or later +- Flutter SDK: 2.1.0 or later + +## Edge cases + +- **Multiple starts**: Calling `startDurationEvent` with an already-tracked name discards the previous tracking and starts fresh. +- **Missing stop**: A duration event that's never stopped is never sent. +- **App restarts**: Duration events survive app restarts via persistent storage. +- **Background time**: Excluded by default. Pass `includeBackgroundTime: true` to include it: + +```swift +await TelemetryDeck.startDurationEvent( + "longRunningTask", + includeBackgroundTime: true +) +``` + +### Cancelling a duration event + +If the activity is abandoned before completion: + +```swift +await TelemetryDeck.cancelDurationEvent("activityName") +``` + +## Analyzing duration data + +Duration data is sent as a numerical value in the `TelemetryDeck.Signal.durationInSeconds` parameter. The histogram aggregation is a natural fit for visualizing distribution. + +### Histogram query + +1. Create a new insight of type "Advanced Query", then open the "JSON Editor": + + ![A screenshot of the Query Creator dialog](/assets/duration-signal-01.png) + +2. Paste this histogram aggregation query, replacing ``: + + ![A screenshot of the JSON Editor text field](/assets/duration-signal-02.png) + + ```json + { + "aggregations": [ + { + "fieldName": "TelemetryDeck.Signal.durationInSeconds", + "name": "durationSketch", + "splitPoints": [0, 0.5, 1, 1.5, 2, 2.5, 3, 4, 5, 7.5, 10, 15, 20, 30, 45, 60, 90, 120], + "type": "histogram" + } + ], + "filter": { + "type": "and", + "fields": [ + { + "type": "range", + "column": "TelemetryDeck.Signal.durationInSeconds", + "matchValueType": "DOUBLE", + "lower": "0", + "upper": "120", + "upperOpen": true + }, + { + "dimension": "type", + "type": "selector", + "value": "" + } + ] + }, + "granularity": "all", + "queryType": "timeseries" + } + ``` + +3. Set the chart type to bar chart: + + ![A screenshot of the insight set to be a bar chart](/assets/duration-signal-03.png) + +4. Adjust `splitPoints` to match your expected durations: + - **Short interactions** (button clicks): `[0, 0.05, 0.1, 0.15, 0.2, 0.3, 0.5, 1, 2, 5]` + - **Medium interactions** (form fills): `[0, 1, 2, 3, 4, 5, 7.5, 10, 15, 20, 30]` + - **Long interactions** (content consumption): `[0, 5, 15, 30, 60, 120, 300, 600, 1200]` + +## Examples + +### Onboarding steps + +```swift +await TelemetryDeck.startDurationEvent("Onboarding.step1") + +// When moving to step 2 +await TelemetryDeck.stopAndSendDurationEvent("Onboarding.step1", parameters: [ + "pushAccess": "granted" +]) +await TelemetryDeck.startDurationEvent("Onboarding.step2") +``` + +Duration events are regular events, so you can reuse them in [funnel charts](/articles/how-to-funnel-insights/). + +### Content engagement + +```swift +await TelemetryDeck.startDurationEvent("Content.viewing", parameters: [ + "contentType": "article", + "contentID": article.id, + "contentCategory": article.category, +]) + +// When leaving the article +await TelemetryDeck.stopAndSendDurationEvent("Content.viewing", parameters: [ + "reachedEnd": userReachedEnd +]) +``` + +### Network request timing + +```swift +func fetchData() async throws -> Data { + await TelemetryDeck.startDurationEvent("Network.fetch", parameters: [ + "endpoint": "users/profile" + ]) + + do { + let (data, response) = try await URLSession.shared.data(from: url) + let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 + + await TelemetryDeck.stopAndSendDurationEvent("Network.fetch", parameters: [ + "status": statusCode, + "success": true + ]) + + return data + } catch { + await TelemetryDeck.stopAndSendDurationEvent("Network.fetch", parameters: [ + "success": false + ]) + throw error + } +} +``` diff --git a/docs/articles/hosting-solutions.md b/docs/articles/hosting-solutions.md new file mode 100644 index 0000000..ca13a2d --- /dev/null +++ b/docs/articles/hosting-solutions.md @@ -0,0 +1,15 @@ +--- +title: Hosting Solutions at TelemetryDeck +tags: + - setup + - hosting + - privacy +--- + +Since we started with our public beta in 2021, we have been hosting TelemetryDeck, its customers, and data in the cloud. We currently don’t offer on-premise hosting or self-hosted solutions. + +One reason is our [Double-Hashing anonymization method](anonymization-how-it-works.md). It requires two distinct parties to use their own private keys to make sure neither can retrieve any personally identifiable information. When an event arrives on the server, we add our own salt to the user identifier and hash it again. This ensures that neither TelemetryDeck nor you can reconstruct the original identifier, protecting the user’s privacy. + +Another reason is how we think about data protection. We handle data with one of [the strongest data protection laws in mind](https://telemetrydeck.com/blog/privacy-by-design-vs-privacy-by-paperwork/) - and this way we can always make sure our customers use TelemetryDeck with strong data protection regulations. + +TelemetryDeck is always hosted in the cloud. Additionally, we save our customers tons of infrastructure cost and work by taking care of all hosting, bandwidth, and security, all hosted within the EU. \ No newline at end of file diff --git a/articles/how-to-funnel-insights.md b/docs/articles/how-to-funnel-insights.md similarity index 94% rename from articles/how-to-funnel-insights.md rename to docs/articles/how-to-funnel-insights.md index 236a704..5d6db5b 100644 --- a/articles/how-to-funnel-insights.md +++ b/docs/articles/how-to-funnel-insights.md @@ -8,6 +8,7 @@ tags: description: Funnels are here to help you understand your users. Watch how they navigate and flow through your app and find out how to improve your in-App processes with funnels! searchEngineTitle: Get your analytics flowing - the art of funneling your signals searchEngineDescription: Watch how your users navigate and flow through your app and learn how to improve your in-App processes with funnels! +status: deprecated --- ## The flow of your app @@ -16,9 +17,9 @@ Sometimes they go directly to their goal, do not pass Go, and do not collect 200 These goals are often your goals, too. As the app owner, you want them to spend time in your app, find the new feature, and order that new product. -![Screenshot of a funnel called "Log in Journey" with 4 decreasing steps, going from "user login", to "show group view", to "show insight", and finally to "widget setup"](/docs/images/funnels_example.png) +![Screenshot of a funnel called "Log in Journey" with 4 decreasing steps, going from "user login", to "show group view", to "show insight", and finally to "widget setup"](/assets/funnels_example.png) -![Screenshot of the funnel steps with set filters for "Log in Journey"](/docs/images/funnels_set_filters.jpg) +![Screenshot of the funnel steps with set filters for "Log in Journey"](/assets/funnels_set_filters.jpg) Funnels are a great way to show this kind of behavior. What funnels do at heart is display **when** a user **ends the process**. For example, it is possible to showcase each page of an app - from opening the home page to the final checkout page. If you have 100 users going through that process, and all of them finish it, all steps should show 100 users. If there happens to be some confusion during the process, and users cancel it before the checkout, you will see that the page causing that issue will leave a drop in users afterward. The checkout page will probably show fewer than 100 users at the end. @@ -28,7 +29,7 @@ Let's have a look at how to use funnels in TelemetryDeck. ## How to use funnels Imagine you want to track how many users turn into customers. You can use funnels to track this conversion process. Since a funnel filters your insights by conditions, you must set up filters in your TelemetryDeck dashboard with the **filter editor**. These filters help you track how many users complete each funnel step, leading to a great visualization of your app's conversion. To create a **funnel chart**, just set the chart type to funnel and the query type to **funnel query** - or use our handy template to create an empty funnel! -![Screenshot of the selected funnel query and chart type](/docs/images/funnel_type.PNG) +![Screenshot of the selected funnel query and chart type](/assets/funnel_type.PNG) For example, let's say your funnel has three steps: diff --git a/articles/insights-about-daily-users.md b/docs/articles/insights-about-daily-users.md similarity index 97% rename from articles/insights-about-daily-users.md rename to docs/articles/insights-about-daily-users.md index c777a80..c55a4ea 100644 --- a/articles/insights-about-daily-users.md +++ b/docs/articles/insights-about-daily-users.md @@ -19,7 +19,7 @@ Insights of daily active users are the best way to see how your app is doing. Yo We will help you set up your insight so you can track your users right away, privacy-friendly and anonymized! -![Bar chart example of daily active users](/docs/images/daily-active-users-example.png) +![Bar chart example of daily active users](/assets/daily-active-users-example.png) ## Navigate to your app diff --git a/articles/insights-about-monthly-users.md b/docs/articles/insights-about-monthly-users.md similarity index 89% rename from articles/insights-about-monthly-users.md rename to docs/articles/insights-about-monthly-users.md index 2e6bcff..94597de 100644 --- a/articles/insights-about-monthly-users.md +++ b/docs/articles/insights-about-monthly-users.md @@ -18,21 +18,21 @@ order: Follow these steps to get an insight of your monthly active users (MAU) within a couple of minutes: 1. Go to the dashboard and create a New Insight. - ![Create a new insight](/docs/images/MAU_1.png) + ![Create a new insight](/assets/MAU_1.png) 2. In the next step select "I don’t know, show me some examples" and then choose "Monthly Users". 3. Now you are already getting results! Unfortunately, you probably still do not find the chart that helpful since only data of the last 30 days is included and that contradicts a chart with granularity set to "Month". - ![Standard insight](/docs/images/MAU_2.png) + ![Standard insight](/assets/MAU_2.png) **A quick fix** is changing the included data from "Last 30 Days" to "This Year" in the respective dropdown at the top of the page. - ![Insight current year](/docs/images/MAU_3.png) + ![Insight current year](/assets/MAU_3.png) 4. An issue is that you do not want to constantly change the time interval manually every time you use TelemetryDeck for this query to work. To resolve this: - While editing the query click on the **Query Type** button and choose **Advanced Query**. This will cause that you stop editing the query, but once you click on "Edit" that same query again you can now use the Visual Editor. - In the Metadata section add an item for Relative Intervals - At beginningDate choose `month`, `-12` and `beginning` to start the time interval 12 months ago and for **endDate** choose `month`, `0` and `end` to end the time interval at the current time. - ![Set relative interval](/docs/images/MAU_4.png) + ![Set relative interval](/assets/MAU_4.png) The query is now working as required although the dropdown at the top is changed back to "Last 30 Days". -![Final insight](/docs/images/MAU_5.png) +![Final insight](/assets/MAU_5.png) diff --git a/articles/insights-about-referrers.md b/docs/articles/insights-about-referrers.md similarity index 89% rename from articles/insights-about-referrers.md rename to docs/articles/insights-about-referrers.md index f922729..30eb3f6 100644 --- a/articles/insights-about-referrers.md +++ b/docs/articles/insights-about-referrers.md @@ -16,10 +16,10 @@ order: --- To begin with, add a new insight in the dashboard of your choice and select **Advanced Query** so that you can proceed using the **Visual Editor**. -![Create a new advanced query](/docs/images/Referrers_1.png) +![Create a new advanced query](/assets/Referrers_1.png) 1. In the first section of the Visual Editor, **Metadata**, choose a `topN query` - we propose granularity `all` so that you can display the information in a donut chart in the end. - ![Metadata](/docs/images/Referrers_2.png) + ![Metadata](/assets/Referrers_2.png) 2. In the next step you can choose which **Data to Include**. To get started, add `thisApp` as well as the respective App ID. @@ -27,15 +27,15 @@ To begin with, add a new insight in the dashboard of your choice and select **Ad To achieve that add a `regex` filter with dimension `referrer` and domain as well as top-level-domain of your website separated by a `\`: `referrer~yourDomain\.com`. Please be aware that this is a rather basic regex filter as the purpose here is to show the creation of the query. More elements can be added to the filter of course. Once you have entered the information for the filter click on the field again and select `Negate (Wrap in "not" condition)` - ![Data to Include_1](/docs/images/Referrers_3a.png) + ![Data to Include_1](/assets/Referrers_3a.png) When you are finished, the filter in **Data to Include** section should look as follows: - ![Data to Include_2](/docs/images/Referrers_3b.png) + ![Data to Include_2](/assets/Referrers_3b.png) Additionally, feel free to add further information in the regex filter in case you want to exclude search engines besides your own website as referrers for example. This could be used in the `regex` filter of the Visual Editor to expand the filter: `(yourDomain\.com|google\.|bing\.|search\.brave|yandex\.|kagi\.|duckduckgo\.)`. 4. The following section are **topN Options**. In the first part regarding the dimension, select type `default` and type in `referrer` two times. Furthermore, choose a `numeric` metric and add `count`. To wrap this section up choose the number of topN results to be returned, i.e. the threshold. - ![topN Options](/docs/images/Referrers_4.png) + ![topN Options](/assets/Referrers_4.png) 5. Finally, add a `longSum`-aggregator for the **Aggregations** and type in `count` in the two respective fields. - ![Aggregations](/docs/images/Referrers_5.png) + ![Aggregations](/assets/Referrers_5.png) To wrap the creation of the insight up we recommend using a `Donut Chart` to display topN of the query results. diff --git a/articles/insights-about-system-version.md b/docs/articles/insights-about-system-version.md similarity index 97% rename from articles/insights-about-system-version.md rename to docs/articles/insights-about-system-version.md index 038fd8e..a826808 100644 --- a/articles/insights-about-system-version.md +++ b/docs/articles/insights-about-system-version.md @@ -34,7 +34,7 @@ Click on _+ Create new Insight_ and give it a name. Now you can either build you You can directly see how many users are using your app from a specific operating system. By default, the versions are grouped by minor versions (displayed as `15.x.x`). -![Donut chart example of system versions](/docs/images/system-versions-example.png) +![Donut chart example of system versions](/assets/system-versions-example.png) ## Step four: Configure your insights diff --git a/articles/insights.md b/docs/articles/insights.md similarity index 93% rename from articles/insights.md rename to docs/articles/insights.md index 4fc8c23..d049ad4 100644 --- a/articles/insights.md +++ b/docs/articles/insights.md @@ -21,7 +21,7 @@ The query is used to retrieve data from the TelemetryDeck API, and the display m **Check out our [YouTube Channel](https://www.youtube.com/watch?v=x0eZoQDnAPA) to learn how our built-in charts work!** -[![Understanding TelemetryDeck's built-in charts](/docs/images/yt-built-in-charts.png)](https://www.youtube.com/watch?v=x0eZoQDnAPA) +[![Understanding TelemetryDeck's built-in charts](/assets/yt-built-in-charts.png)](https://www.youtube.com/watch?v=x0eZoQDnAPA) ### Versatile and adjustable @@ -53,12 +53,12 @@ The title is displayed on top of any insight card. Example: "Daily Active Users" **Compact and wide mode** You can choose between "Compact" and "Wide". If "Wide" is active, the insight will be displayed in a larger frame. This allows you to see your most important insight with extra detail. -![buttons for the compact mode](/docs/images/compact_wide_mode.PNG) +![buttons for the compact mode](/assets/compact_wide_mode.PNG) **Display Mode** Here, you can choose the most suitable chart type for your insight. Learn more about the chart types in the section [Chart Types](#chart-types). -![icons for the display mode](/docs/images/display_mode.PNG) +![icons for the display mode](/assets/display_mode.PNG) **Counting** If "Count Signals" is checked, an insight will count all signals by their type. If "Count Users" is checked, each user will only get counted once. Only the newest is selected if the set of signals counted by the insight contains multiple signals from the same user. This is great for counting users, for example, "How many users are using feature x?" @@ -81,7 +81,7 @@ Whenever you choose TopN as a query type, you can configure it under _TopN Confi {% noteinfo "Hints" %} -- TopN pairs well with the [Donut Chart](#donut-chart) as a Chart Type. +- TopN pairs well with the Donut Chart as a Chart Type. - If you're interested in the number of users per - for example - system version instead of the number of signals, enable the _unique by user_ option in _Signal Type_. {% endnoteinfo %} @@ -106,7 +106,7 @@ There is no going back from here. If you later decide that there is no custom qu **Check out our [YouTube Channel](https://www.youtube.com/watch?v=PYWO3017_Ho) on how to create custom charts!** -[![Creating Custom Charts in TelemetryDeck – Conversion Tracking Tutorial](/docs/images/yt-custom-charts.png)](https://www.youtube.com/watch?v=PYWO3017_Ho) +[![Creating Custom Charts in TelemetryDeck – Conversion Tracking Tutorial](/assets/yt-custom-charts.png)](https://www.youtube.com/watch?v=PYWO3017_Ho) ## Chart Types @@ -119,7 +119,7 @@ The TelemetryDeck dashboard will show time zones of your users in **their browse --- -![An example of a table chart](/docs/images/table-chart.PNG) +![An example of a table chart](/assets/table-chart.PNG) **Table** @@ -129,7 +129,7 @@ If only one line of data is returned, that line will be displayed in large type. --- -![An example of a bar chart](/docs/images/bar-chart.PNG) +![An example of a bar chart](/assets/bar-chart.PNG) **Bar Chart** @@ -137,7 +137,7 @@ It displays the query result as a vertical bar chart, one bar per line of data. --- -![An example of a line chart](/docs/images/line-chart.PNG) +![An example of a line chart](/assets/line-chart.PNG) **Line Chart** @@ -145,7 +145,7 @@ This chart shows the query result as a line chart, one bar per line of data. Thi --- -![An example of a donut chart](/docs/images/donut-chart.PNG) +![An example of a donut chart](/assets/donut-chart.PNG) **Donut Chart** @@ -153,7 +153,7 @@ A pie/donut type chart that will sort the rows of data by number and visually sh --- -![An example of a funnel chart](/docs/images/funnels_example.png) +![An example of a funnel chart](/assets/funnels_example.png) **Funnel Chart** diff --git a/articles/invite-members-to-organization.md b/docs/articles/invite-members-to-organization.md similarity index 82% rename from articles/invite-members-to-organization.md rename to docs/articles/invite-members-to-organization.md index 53d5e05..ed33acd 100644 --- a/articles/invite-members-to-organization.md +++ b/docs/articles/invite-members-to-organization.md @@ -1,4 +1,5 @@ --- +icon: fontawesome/solid/users title: How to invite members to your organization tags: - beginner @@ -6,13 +7,7 @@ tags: - quickstart - collaboration - user-cohorts -testedOn: -featured: description: A guide on how to add members to your organization and collaborate -lead: You have more than one person working on your app and want them to have access to all the insights? Great! This guide will tell you everything you need to know about adding new members to your organization. -searchEngineTitle: -searchEngineDescription: Learn how to add new members to your organization. Share insights and important changes by inviting them easily. -order: --- In TelemetryDeck, every member is part of an **organization**. If you are not working on your own and have at least one other person joining you on the work of your app, you might be interested in adding them to your organization. With separate accounts for each member, everyone will be able to get access to the insights whenever they need them. diff --git a/articles/namespaces.md b/docs/articles/namespaces.md similarity index 98% rename from articles/namespaces.md rename to docs/articles/namespaces.md index 0521707..ed3a89a 100644 --- a/articles/namespaces.md +++ b/docs/articles/namespaces.md @@ -1,4 +1,5 @@ --- +icon: fontawesome/solid/address-card title: Namespaces tags: - setup diff --git a/docs/articles/navigation-signals.md b/docs/articles/navigation-signals.md new file mode 100644 index 0000000..6b6e182 --- /dev/null +++ b/docs/articles/navigation-signals.md @@ -0,0 +1,80 @@ +--- +title: Navigation Signals +tags: setup +description: TelemetryDeck tracks how users navigate through your app when you send navigation signals. +lead: TelemetryDeck tracks how users navigate through your app when you send navigation signals. Here's the format and convenience methods. +testedOn: SwiftSDK 3.0.0, WebSDK 1.0.0 +--- + +!!! note "Web Analytics already tracks navigation" + + If you're using TelemetryDeck's Web SDK, navigation is tracked automatically. This guide is for native app SDKs. + +## Format + +A navigation signal has the type `TelemetryDeck.Navigation.pathChanged` with these parameters: + +```json +{ + "appID": "", + "clientUser": "", + "type": "TelemetryDeck.Navigation.pathChanged", + "payload": { + "TelemetryDeck.Navigation.schemaVersion": "1", + "TelemetryDeck.Navigation.identifier": " -> ", + "TelemetryDeck.Navigation.sourcePath": "", + "TelemetryDeck.Navigation.destinationPath": "" + } +} +``` + +| Key | Description | +|---|---| +| `TelemetryDeck.Navigation.schemaVersion` | Always `"1"`. | +| `TelemetryDeck.Navigation.identifier` | ` -> `. Used to build directed navigation graphs. | +| `TelemetryDeck.Navigation.sourcePath` | Where the user came from. | +| `TelemetryDeck.Navigation.destinationPath` | Where the user went. | + +## Navigation paths + +Navigation paths are `.`-delimited strings in apps and `/`-delimited in websites. Leading and trailing delimiters are ignored. Use the empty string `""` for navigation from outside the app. + +Examples: + +- `index` +- `settings.user.changePassword` +- `/blog/ios-market-share` + +## Convenience methods + +### With source and destination + +```swift +await TelemetryDeck.navigationPathChanged(from: "home", to: "settings") +``` + +### Destination only + +```swift +await TelemetryDeck.navigationPathChanged(to: "settings") +``` + +This uses the previous destination as the source. Be careful β€” if you don't call it from every screen, the navigation graph will show paths the user didn't take. + +### SwiftUI view modifier + +```swift +struct SettingsView: View { + var body: some View { + Form { + NavigationLink("Account") { + AccountSettingsView() + .trackNavigation(path: "settings.account") + } + } + .trackNavigation(path: "settings") + } +} +``` + +The `.trackNavigation(path:)` modifier calls `navigationPathChanged(to:)` when the view appears. Apply it consistently to all navigation destinations to get accurate graphs. diff --git a/articles/notebooks.md b/docs/articles/notebooks.md similarity index 97% rename from articles/notebooks.md rename to docs/articles/notebooks.md index f193311..043d6b8 100644 --- a/articles/notebooks.md +++ b/docs/articles/notebooks.md @@ -16,7 +16,7 @@ searchEngineDescription: Learn how to effectively use TelemetryDeck Notebooks to Notebooks are your analytics lab notebook – a place where you can document your findings, share insights with your team, and maintain context throughout your investigations. Let's explore how to use them effectively. {% noteinfo "See Notebooks in action" %} -[![Notebooks Feature Demo - Learn how to combine live charts with markdown text for better analytics insights](/docs/images/notebooks-video-thumbnail.png)](https://www.youtube.com/watch?v=WAa2BRIaVGE) +[![Notebooks Feature Demo - Learn how to combine live charts with markdown text for better analytics insights](/assets/notebooks-video-thumbnail.png)](https://www.youtube.com/watch?v=WAa2BRIaVGE) *Watch the [Notebooks Walkthrough](https://www.youtube.com/watch?v=WAa2BRIaVGE) on our YouTube channel* {% endnoteinfo %} @@ -32,7 +32,7 @@ Notebooks are your analytics lab notebook – a place where you can document you Starting with a clear question or hypothesis helps focus your analysis and makes it easier to structure your notebook. It also makes it easier to share your findings with others later. {% endnoteinfo %} -![A screenshot of the Notebooks overview tab, showing a list of notebooks](/docs/images/Notebooks-Overview.png) +![A screenshot of the Notebooks overview tab, showing a list of notebooks](/assets/Notebooks-Overview.png) ### Adding charts to your notebook @@ -62,7 +62,7 @@ You can customize how your data is displayed using the `displayMode` value in yo - `raw` - Ideal for detailed breakdowns - `funnelChart` - Essential for conversion analysis -![A screenshot showing the markdown editor with TQL code, and the live chart preview in a notebook](/docs/images/Notebooks-TQL-Live-Preview.png) +![A screenshot showing the markdown editor with TQL code, and the live chart preview in a notebook](/assets/Notebooks-TQL-Live-Preview.png) ### Using Markdown effectively diff --git a/docs/articles/preset-errors.md b/docs/articles/preset-errors.md new file mode 100644 index 0000000..2f94ff0 --- /dev/null +++ b/docs/articles/preset-errors.md @@ -0,0 +1,116 @@ +--- +title: Tracking Errors +tags: + - setup + - beginner + - insights + - presets +lead: Track errors in your app to identify common issues and reduce churn. The TelemetryDeck SDK provides convenience methods for structured error reporting. +--- + +## Why track errors? + +A smooth experience directly impacts retention. Tracking errors lets you find the most common issues your users hit, prioritize fixes, and measure improvement over time. + +## Using the TelemetryDeck Swift SDK + +```swift +await TelemetryDeck.errorOccurred(id: error.localizedDescription) +``` + +### Better error identification + +`error.localizedDescription` varies by user language, making it hard to group the same error. Provide a consistent ID instead: + +```swift +do { + let object = try JSONDecoder().decode(Object.self, from: data) +} catch { + await TelemetryDeck.errorOccurred( + identifiableError: error.with(id: "ImportObject.jsonDecode") + ) + + // Or with explicit parameters: + await TelemetryDeck.errorOccurred( + id: "ImportObject.jsonDecode", + message: error.localizedDescription + ) +} +``` + +### Custom error types + +Conform your own error types to `IdentifiableError`: + +```swift +enum MyError: String, IdentifiableError { + case fileMissing + case invalidFormat + + var id: String { self.rawValue } +} + +await TelemetryDeck.errorOccurred(identifiableError: myError) +``` + +### Error categories + +Three built-in categories for organization: + +```swift +// Thrown exceptions β€” parsing errors, I/O errors, permission errors +await TelemetryDeck.errorOccurred( + id: "FileNotFound", + category: .thrownException, + message: error.localizedDescription +) + +// User input errors β€” invalid format, invalid range +await TelemetryDeck.errorOccurred( + id: "ProjectForm.hourlyRateConversionFailed", + category: .userInput, + message: "Text could not be converted to Int" +) + +// App state errors β€” inconsistent navigation, invalid combinations +await TelemetryDeck.errorOccurred( + id: "NavigationState.invalidTransition", + category: .appState, + message: "Cannot navigate from login to dashboard without authentication" +) +``` + +!!! note + + When using `errorOccurred(identifiableError:)`, the category defaults to `.thrownException`. + +The `errorOccurred` function accepts the same optional arguments as `event` (`parameters`, `floatValue`, `customUserID`). + +## Manual signal construction for other platforms + +If you're not using the Swift SDK, construct the error signal manually. + +**Event name**: `TelemetryDeck.Error.occurred` + +**Required parameter**: + +- `TelemetryDeck.Error.id`: A consistent identifier for grouping similar errors + +**Optional parameters**: + +- `TelemetryDeck.Error.message`: The full error message +- `TelemetryDeck.Error.category`: One of `thrown-exception`, `user-input`, or `app-state` + +## Built-in error categories + +| Category | Use when | +|---|---| +| `thrown-exception` | In `try-catch` blocks or when handling `nil` returns from operations that might fail | +| `user-input` | Validating or converting user input with fallback behavior | +| `app-state` | Assertion failures or unexpected application states | + +## Privacy considerations + +If you send dynamic values like `error.localizedDescription` or user-generated content in parameters, some user data reaches TelemetryDeck. Adjust your App Tracking Transparency disclosure accordingly. + +TelemetryDeck does not link collected data to user identity or use it for tracking. Don't send data that could identify your users. diff --git a/docs/articles/preset-purchases.md b/docs/articles/preset-purchases.md new file mode 100644 index 0000000..5737c08 --- /dev/null +++ b/docs/articles/preset-purchases.md @@ -0,0 +1,103 @@ +--- +title: Tracking Purchases +tags: + - setup + - beginner + - insights + - presets +lead: Track in-app purchases through TelemetryDeck for near-realtime revenue data β€” no waiting hours for App Store Connect to update. +searchEngineDescription: Track your app's in-app purchases through TelemetryDeck with just a couple of seconds delay, providing live revenue data. +testedOn: SwiftSDK 3.0.0 +--- + +## Why track purchases? + +App Store Connect purchase data lags by several hours and requires frequent re-authentication. TelemetryDeck gives you purchase data within seconds. + +You can track purchases through: + +- The TelemetryDeck Swift SDK directly +- RevenueCat integration +- FreemiumKit integration + +!!! warning "Live data vs. correct data" + + TelemetryDeck does not handle refunds or detect subscription renewals. For long-term or 100% correct revenue data, use official sources like App Store Connect. + +## Using the TelemetryDeck Swift SDK + +Pass a StoreKit transaction to the convenience method: + +```swift +await TelemetryDeck.purchaseCompleted(transaction: transaction) +``` + +This automatically: + +- Extracts the price from the transaction +- Converts the currency to USD (using built-in exchange rates) +- Determines if it's a subscription or one-time purchase +- Detects free trial starts vs. paid conversions +- Includes the storefront country and currency codes + +!!! note "Requirements" + + Requires iOS 15+. Accepts optional `parameters` and `customUserID` for additional context. + +### Automatic trial conversion detection + +The SDK includes a `TrialConversionProcessor` that monitors StoreKit `Transaction.updates` in the background. When a user transitions from a free trial to a paid subscription, it automatically fires `TelemetryDeck.Purchase.convertedFromTrial`. No additional code needed. + +## Using TelemetryDeck with RevenueCat + +See our [RevenueCat Setup Guide](/integrations/revenuecat/). + +## Using FreemiumKit + +Add FreemiumKit's `.onPurchaseCompleted` view modifier to your main view β€” it passes the `transaction` parameter directly to `TelemetryDeck.purchaseCompleted(transaction:)`. See their [setup guide](https://freemiumkit.app/documentation/freemiumkit/setupguide#Direct-Access-to-StoreKit-Transactions). + +## Manual signal construction for other platforms + +!!! warning + + Only needed if you are NOT using the TelemetryDeck Swift SDK. + +### Required fields + +- **Event name**: `TelemetryDeck.Purchase.completed` +- **`floatValue`**: The purchase amount in USD + +!!! warning "Currency conversion" + + You must convert the transaction value to USD before sending. Use [an exchange rate API](https://www.exchangerate-api.com/docs/standard-requests) (1,500 free requests/month) or hard-code approximate rates. + +### Optional parameters + +- `TelemetryDeck.Purchase.type`: `subscription` or `one-time-purchase` +- `TelemetryDeck.Purchase.countryCode`: Storefront country code +- `TelemetryDeck.Purchase.currencyCode`: Storefront currency code + +### Example + +```swift +let priceInUSD = convertToUSD(transaction.price, from: transaction.currencyCode) + +await TelemetryDeck.event( + "TelemetryDeck.Purchase.completed", + parameters: [ + "TelemetryDeck.Purchase.type": transaction.subscriptionGroupID != nil + ? "subscription" : "one-time-purchase", + "TelemetryDeck.Purchase.countryCode": transaction.storefrontCountryCode, + "TelemetryDeck.Purchase.currencyCode": transaction.currencyCode ?? "???" + ], + floatValue: priceInUSD +) +``` + +## Privacy + +If you already use RevenueCat or a similar service, you're already sending more data to them than TelemetryDeck collects. No privacy label changes needed. + +If tracking purchases directly, mark "Analytics" under "Purchase History" in your App Privacy page. Answer all subsequent questions with "No". + +![Purchases entry with only 'Used for Analytics' in the box](/assets/purchases-privacy-box.png) diff --git a/articles/set-up-filters-insights.md b/docs/articles/set-up-filters-insights.md similarity index 96% rename from articles/set-up-filters-insights.md rename to docs/articles/set-up-filters-insights.md index b48ad8b..c467918 100644 --- a/articles/set-up-filters-insights.md +++ b/docs/articles/set-up-filters-insights.md @@ -16,7 +16,7 @@ With filters, you can create custom views of your insights and organize the info Navigate to the [dashboard](https://dashboard.telemetrydeck.com), and select your app. You can find the filter editor in your insight group, while editing an insight. Either create a new insight or edit an existing one to see the editor just below the chart itself. -![Screenshot of the filter location in the dashboard](/docs/images/filter_location.png) +![Screenshot of the filter location in the dashboard](/assets/filter_location.png) In your insight group, you can add filters to any insight you want. With advanced filter options available, you can add multiple filters to obtain the desired query results. Nesting filters and combining them with boolean operations can help you refine your query and achieve even more accurate insights. @@ -28,7 +28,7 @@ You can use analytics filters to refine your data and obtain precise insights. W - The **Selector** filter lets you compare any _payload key_ (also known as a dimension) to a value. For instance, if you send a `shouldUseHealthKit` parameter as part of your payload in signals, you can filter for `shouldUseHealthKit = β€œtrue”`. - Additionally, you can use **Regular Expressions** (RegEx) to filter data based on _complex patterns or strings of text_. Sometimes it is not possible to match a value exactly, and in such cases, regular expressions can be incredibly useful to obtain more accurate and specific insights into user behavior. A good example for using RegEx is filtering by device type, locale, or anything else you would like a more precise view on! -![Screenshot of how adding a condition changes the output. Adding that only iOS versions should be shown, and then wrapping the condition inside a NOT so that everything but iOS versions is shown.](/docs/images/filter_example.png) +![Screenshot of how adding a condition changes the output. Adding that only iOS versions should be shown, and then wrapping the condition inside a NOT so that everything but iOS versions is shown.](/assets/filter_example.png) ## Query construction diff --git a/articles/signal-type-naming.md b/docs/articles/signal-type-naming.md similarity index 100% rename from articles/signal-type-naming.md rename to docs/articles/signal-type-naming.md diff --git a/docs/articles/test-mode.md b/docs/articles/test-mode.md new file mode 100644 index 0000000..f3b1d38 --- /dev/null +++ b/docs/articles/test-mode.md @@ -0,0 +1,67 @@ +--- +title: Test Mode +tags: + - setup + - testmode + - quickstart + - beginner +testedOn: SwiftSDK 3.0.0 +description: Use Test Mode to verify your TelemetryDeck setup without polluting production data. +lead: Test Mode keeps your development and testing signals separate from real user data. +searchEngineTitle: How to run test signals +searchEngineDescription: With test mode, you can review your analytics setup without compromising live data. +--- + +During development and testing, your app sends events that aren't from real users. Test Mode keeps these separate from production analytics. + +Benefits of sending test signals: + +- Verify your TelemetryDeck SDK configuration before release +- Confirm new event types and parameters work as expected +- Set up insights and dashboards ahead of launch + +## How it works + +Every event has an `isTestMode` flag. The TelemetryDeck Dashboard has a Test Mode toggle in the upper left β€” flip it to switch between test and production data. A banner reminds you when you're viewing test data. + +![Screenshot of the dashboard showing the Test Mode toggle in the upper left corner.](/assets/test_mode.png) + +## Automatic detection + +The SDKs detect test mode automatically. In the Swift SDK, events sent from `DEBUG` builds are marked as test signals by default. + +## Manual override + +### Swift SDK + +Override test mode at initialization: + +```swift +try await TelemetryDeck.initialize( + appID: "YOUR-APP-ID", + namespace: "YOUR-NAMESPACE", + testMode: true +) +``` + +Check the current test mode state at runtime: + +```swift +let isTest = await TelemetryDeck.isTestMode() +``` + +### JavaScript SDK + +```javascript +const td = new TelemetryDeck({ + app: "YOUR-APP-ID", + user: "anonymous", + testMode: true +}); +``` + +Or set it per-signal: + +```javascript +td.signal("eventName", { isTestMode: "true" }); +``` diff --git a/images/MAU_1.png b/docs/assets/MAU_1.png similarity index 100% rename from images/MAU_1.png rename to docs/assets/MAU_1.png diff --git a/images/MAU_2.png b/docs/assets/MAU_2.png similarity index 100% rename from images/MAU_2.png rename to docs/assets/MAU_2.png diff --git a/images/MAU_3.png b/docs/assets/MAU_3.png similarity index 100% rename from images/MAU_3.png rename to docs/assets/MAU_3.png diff --git a/images/MAU_4.png b/docs/assets/MAU_4.png similarity index 100% rename from images/MAU_4.png rename to docs/assets/MAU_4.png diff --git a/images/MAU_5.png b/docs/assets/MAU_5.png similarity index 100% rename from images/MAU_5.png rename to docs/assets/MAU_5.png diff --git a/images/Notebooks-Overview.png b/docs/assets/Notebooks-Overview.png similarity index 100% rename from images/Notebooks-Overview.png rename to docs/assets/Notebooks-Overview.png diff --git a/images/Notebooks-TQL-Live-Preview.png b/docs/assets/Notebooks-TQL-Live-Preview.png similarity index 100% rename from images/Notebooks-TQL-Live-Preview.png rename to docs/assets/Notebooks-TQL-Live-Preview.png diff --git a/images/Peerigon_Logo_RGB_no_padding.svg b/docs/assets/Peerigon_Logo_RGB_no_padding.svg similarity index 100% rename from images/Peerigon_Logo_RGB_no_padding.svg rename to docs/assets/Peerigon_Logo_RGB_no_padding.svg diff --git a/images/Referrers_1.png b/docs/assets/Referrers_1.png similarity index 100% rename from images/Referrers_1.png rename to docs/assets/Referrers_1.png diff --git a/images/Referrers_2.png b/docs/assets/Referrers_2.png similarity index 100% rename from images/Referrers_2.png rename to docs/assets/Referrers_2.png diff --git a/images/Referrers_3a.png b/docs/assets/Referrers_3a.png similarity index 100% rename from images/Referrers_3a.png rename to docs/assets/Referrers_3a.png diff --git a/images/Referrers_3b.png b/docs/assets/Referrers_3b.png similarity index 100% rename from images/Referrers_3b.png rename to docs/assets/Referrers_3b.png diff --git a/images/Referrers_4.png b/docs/assets/Referrers_4.png similarity index 100% rename from images/Referrers_4.png rename to docs/assets/Referrers_4.png diff --git a/images/Referrers_5.png b/docs/assets/Referrers_5.png similarity index 100% rename from images/Referrers_5.png rename to docs/assets/Referrers_5.png diff --git a/images/Screenshot_SystemVersion.png b/docs/assets/Screenshot_SystemVersion.png similarity index 100% rename from images/Screenshot_SystemVersion.png rename to docs/assets/Screenshot_SystemVersion.png diff --git a/images/aarrr-framework.png b/docs/assets/aarrr-framework.png similarity index 100% rename from images/aarrr-framework.png rename to docs/assets/aarrr-framework.png diff --git a/images/acquisition-active-vs-new.png b/docs/assets/acquisition-active-vs-new.png similarity index 100% rename from images/acquisition-active-vs-new.png rename to docs/assets/acquisition-active-vs-new.png diff --git a/images/acquisition-by-time-last-weeks.png b/docs/assets/acquisition-by-time-last-weeks.png similarity index 100% rename from images/acquisition-by-time-last-weeks.png rename to docs/assets/acquisition-by-time-last-weeks.png diff --git a/images/acquisition-by-time.png b/docs/assets/acquisition-by-time.png similarity index 100% rename from images/acquisition-by-time.png rename to docs/assets/acquisition-by-time.png diff --git a/images/acquisition-device-distribution.png b/docs/assets/acquisition-device-distribution.png similarity index 100% rename from images/acquisition-device-distribution.png rename to docs/assets/acquisition-device-distribution.png diff --git a/images/acquisition-geographic-distribution.png b/docs/assets/acquisition-geographic-distribution.png similarity index 100% rename from images/acquisition-geographic-distribution.png rename to docs/assets/acquisition-geographic-distribution.png diff --git a/images/acquisition-new-users.png b/docs/assets/acquisition-new-users.png similarity index 100% rename from images/acquisition-new-users.png rename to docs/assets/acquisition-new-users.png diff --git a/images/activation-activated-users.png b/docs/assets/activation-activated-users.png similarity index 100% rename from images/activation-activated-users.png rename to docs/assets/activation-activated-users.png diff --git a/images/activation-by-time.png b/docs/assets/activation-by-time.png similarity index 100% rename from images/activation-by-time.png rename to docs/assets/activation-by-time.png diff --git a/images/activation-device-distribution.png b/docs/assets/activation-device-distribution.png similarity index 100% rename from images/activation-device-distribution.png rename to docs/assets/activation-device-distribution.png diff --git a/images/activation-geographic-distribution.png b/docs/assets/activation-geographic-distribution.png similarity index 100% rename from images/activation-geographic-distribution.png rename to docs/assets/activation-geographic-distribution.png diff --git a/images/anonymization-display-image.jpg b/docs/assets/anonymization-display-image.jpg similarity index 100% rename from images/anonymization-display-image.jpg rename to docs/assets/anonymization-display-image.jpg diff --git a/images/bar-chart.PNG b/docs/assets/bar-chart.PNG similarity index 100% rename from images/bar-chart.PNG rename to docs/assets/bar-chart.PNG diff --git a/images/compact_wide_mode.PNG b/docs/assets/compact_wide_mode.PNG similarity index 100% rename from images/compact_wide_mode.PNG rename to docs/assets/compact_wide_mode.PNG diff --git a/images/create-new-insight.png b/docs/assets/create-new-insight.png similarity index 100% rename from images/create-new-insight.png rename to docs/assets/create-new-insight.png diff --git a/images/daily-active-users-example.png b/docs/assets/daily-active-users-example.png similarity index 100% rename from images/daily-active-users-example.png rename to docs/assets/daily-active-users-example.png diff --git a/images/dashboard-acquisition.png b/docs/assets/dashboard-acquisition.png similarity index 100% rename from images/dashboard-acquisition.png rename to docs/assets/dashboard-acquisition.png diff --git a/images/dashboard-custom.png b/docs/assets/dashboard-custom.png similarity index 100% rename from images/dashboard-custom.png rename to docs/assets/dashboard-custom.png diff --git a/images/dashboard-exploring-details.png b/docs/assets/dashboard-exploring-details.png similarity index 100% rename from images/dashboard-exploring-details.png rename to docs/assets/dashboard-exploring-details.png diff --git a/images/dashboard-filtering-data.png b/docs/assets/dashboard-filtering-data.png similarity index 100% rename from images/dashboard-filtering-data.png rename to docs/assets/dashboard-filtering-data.png diff --git a/images/dashboard-main-nav.png b/docs/assets/dashboard-main-nav.png similarity index 100% rename from images/dashboard-main-nav.png rename to docs/assets/dashboard-main-nav.png diff --git a/images/dashboard-notebooks.png b/docs/assets/dashboard-notebooks.png similarity index 100% rename from images/dashboard-notebooks.png rename to docs/assets/dashboard-notebooks.png diff --git a/images/dashboard-overview.png b/docs/assets/dashboard-overview.png similarity index 100% rename from images/dashboard-overview.png rename to docs/assets/dashboard-overview.png diff --git a/images/dashboard-signal-types.png b/docs/assets/dashboard-signal-types.png similarity index 100% rename from images/dashboard-signal-types.png rename to docs/assets/dashboard-signal-types.png diff --git a/images/display_mode.PNG b/docs/assets/display_mode.PNG similarity index 100% rename from images/display_mode.PNG rename to docs/assets/display_mode.PNG diff --git a/images/donut-chart.PNG b/docs/assets/donut-chart.PNG similarity index 100% rename from images/donut-chart.PNG rename to docs/assets/donut-chart.PNG diff --git a/images/duration-signal-01.png b/docs/assets/duration-signal-01.png similarity index 100% rename from images/duration-signal-01.png rename to docs/assets/duration-signal-01.png diff --git a/images/duration-signal-02.png b/docs/assets/duration-signal-02.png similarity index 100% rename from images/duration-signal-02.png rename to docs/assets/duration-signal-02.png diff --git a/images/duration-signal-03.png b/docs/assets/duration-signal-03.png similarity index 100% rename from images/duration-signal-03.png rename to docs/assets/duration-signal-03.png diff --git a/images/edit-dashboard-name.png b/docs/assets/edit-dashboard-name.png similarity index 100% rename from images/edit-dashboard-name.png rename to docs/assets/edit-dashboard-name.png diff --git a/images/faq.jpg b/docs/assets/faq.jpg similarity index 100% rename from images/faq.jpg rename to docs/assets/faq.jpg diff --git a/docs/assets/favicon.svg b/docs/assets/favicon.svg new file mode 100644 index 0000000..dc4a92c --- /dev/null +++ b/docs/assets/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/filter_example.png b/docs/assets/filter_example.png similarity index 100% rename from images/filter_example.png rename to docs/assets/filter_example.png diff --git a/images/filter_location.png b/docs/assets/filter_location.png similarity index 100% rename from images/filter_location.png rename to docs/assets/filter_location.png diff --git a/images/funnel_type.PNG b/docs/assets/funnel_type.PNG similarity index 100% rename from images/funnel_type.PNG rename to docs/assets/funnel_type.PNG diff --git a/images/funnels_example.png b/docs/assets/funnels_example.png similarity index 100% rename from images/funnels_example.png rename to docs/assets/funnels_example.png diff --git a/images/funnels_set_filters.jpg b/docs/assets/funnels_set_filters.jpg similarity index 100% rename from images/funnels_set_filters.jpg rename to docs/assets/funnels_set_filters.jpg diff --git a/images/line-chart.PNG b/docs/assets/line-chart.PNG similarity index 100% rename from images/line-chart.PNG rename to docs/assets/line-chart.PNG diff --git a/images/location-create-dashboard.png b/docs/assets/location-create-dashboard.png similarity index 100% rename from images/location-create-dashboard.png rename to docs/assets/location-create-dashboard.png diff --git a/images/location-edit-group.PNG b/docs/assets/location-edit-group.PNG similarity index 100% rename from images/location-edit-group.PNG rename to docs/assets/location-edit-group.PNG diff --git a/images/location-edit-insight.png b/docs/assets/location-edit-insight.png similarity index 100% rename from images/location-edit-insight.png rename to docs/assets/location-edit-insight.png diff --git a/images/login-mask.png b/docs/assets/login-mask.png similarity index 100% rename from images/login-mask.png rename to docs/assets/login-mask.png diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg new file mode 100644 index 0000000..ae16198 --- /dev/null +++ b/docs/assets/logo.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/metrics-accessibility.png b/docs/assets/metrics-accessibility.png similarity index 100% rename from images/metrics-accessibility.png rename to docs/assets/metrics-accessibility.png diff --git a/images/metrics-devices.png b/docs/assets/metrics-devices.png similarity index 100% rename from images/metrics-devices.png rename to docs/assets/metrics-devices.png diff --git a/images/metrics-errors.png b/docs/assets/metrics-errors.png similarity index 100% rename from images/metrics-errors.png rename to docs/assets/metrics-errors.png diff --git a/images/metrics-localization.png b/docs/assets/metrics-localization.png similarity index 100% rename from images/metrics-localization.png rename to docs/assets/metrics-localization.png diff --git a/images/metrics-versions.png b/docs/assets/metrics-versions.png similarity index 100% rename from images/metrics-versions.png rename to docs/assets/metrics-versions.png diff --git a/images/notebooks-video-thumbnail.png b/docs/assets/notebooks-video-thumbnail.png similarity index 100% rename from images/notebooks-video-thumbnail.png rename to docs/assets/notebooks-video-thumbnail.png diff --git a/images/privacy-overview.png b/docs/assets/privacy-overview.png similarity index 100% rename from images/privacy-overview.png rename to docs/assets/privacy-overview.png diff --git a/images/purchases-privacy-box.png b/docs/assets/purchases-privacy-box.png similarity index 100% rename from images/purchases-privacy-box.png rename to docs/assets/purchases-privacy-box.png diff --git a/images/rc-td-1.png b/docs/assets/rc-td-1.png similarity index 100% rename from images/rc-td-1.png rename to docs/assets/rc-td-1.png diff --git a/images/rc-td-2.png b/docs/assets/rc-td-2.png similarity index 100% rename from images/rc-td-2.png rename to docs/assets/rc-td-2.png diff --git a/images/retention-by-time.png b/docs/assets/retention-by-time.png similarity index 100% rename from images/retention-by-time.png rename to docs/assets/retention-by-time.png diff --git a/images/retention-device-distribution.png b/docs/assets/retention-device-distribution.png similarity index 100% rename from images/retention-device-distribution.png rename to docs/assets/retention-device-distribution.png diff --git a/images/retention-geographic-distribution.png b/docs/assets/retention-geographic-distribution.png similarity index 100% rename from images/retention-geographic-distribution.png rename to docs/assets/retention-geographic-distribution.png diff --git a/images/retention-returning-users.png b/docs/assets/retention-returning-users.png similarity index 100% rename from images/retention-returning-users.png rename to docs/assets/retention-returning-users.png diff --git a/images/revenuecat_webhook_config.png b/docs/assets/revenuecat_webhook_config.png similarity index 100% rename from images/revenuecat_webhook_config.png rename to docs/assets/revenuecat_webhook_config.png diff --git a/images/system-versions-example.png b/docs/assets/system-versions-example.png similarity index 100% rename from images/system-versions-example.png rename to docs/assets/system-versions-example.png diff --git a/images/table-chart.PNG b/docs/assets/table-chart.PNG similarity index 100% rename from images/table-chart.PNG rename to docs/assets/table-chart.PNG diff --git a/images/test_mode.png b/docs/assets/test_mode.png similarity index 100% rename from images/test_mode.png rename to docs/assets/test_mode.png diff --git a/images/typedigital.png b/docs/assets/typedigital.png similarity index 100% rename from images/typedigital.png rename to docs/assets/typedigital.png diff --git a/images/update_next_major_version.png b/docs/assets/update_next_major_version.png similarity index 100% rename from images/update_next_major_version.png rename to docs/assets/update_next_major_version.png diff --git a/images/update_package.png b/docs/assets/update_package.png similarity index 100% rename from images/update_package.png rename to docs/assets/update_package.png diff --git a/images/xcode-swift-package.png b/docs/assets/xcode-swift-package.png similarity index 100% rename from images/xcode-swift-package.png rename to docs/assets/xcode-swift-package.png diff --git a/images/xcode-swift-package1.png b/docs/assets/xcode-swift-package1.png similarity index 100% rename from images/xcode-swift-package1.png rename to docs/assets/xcode-swift-package1.png diff --git a/images/xcode-swift-package2.png b/docs/assets/xcode-swift-package2.png similarity index 100% rename from images/xcode-swift-package2.png rename to docs/assets/xcode-swift-package2.png diff --git a/images/yt-4-minute-setup.png b/docs/assets/yt-4-minute-setup.png similarity index 100% rename from images/yt-4-minute-setup.png rename to docs/assets/yt-4-minute-setup.png diff --git a/images/yt-app-analytics.png b/docs/assets/yt-app-analytics.png similarity index 100% rename from images/yt-app-analytics.png rename to docs/assets/yt-app-analytics.png diff --git a/images/yt-app-store-connect.png b/docs/assets/yt-app-store-connect.png similarity index 100% rename from images/yt-app-store-connect.png rename to docs/assets/yt-app-store-connect.png diff --git a/images/yt-built-in-charts.png b/docs/assets/yt-built-in-charts.png similarity index 100% rename from images/yt-built-in-charts.png rename to docs/assets/yt-built-in-charts.png diff --git a/images/yt-custom-charts.png b/docs/assets/yt-custom-charts.png similarity index 100% rename from images/yt-custom-charts.png rename to docs/assets/yt-custom-charts.png diff --git a/basics/acquisition.md b/docs/basics/acquisition.md similarity index 83% rename from basics/acquisition.md rename to docs/basics/acquisition.md index 2e61e7e..765b228 100644 --- a/basics/acquisition.md +++ b/docs/basics/acquisition.md @@ -19,20 +19,22 @@ User acquisition represents the entry point of your app's growth funnel – how With TelemetryDeck's Acquisition dashboard, these insights are automatically collected and visualized with no additional code required beyond updating to the latest SDK version. -{% noteinfo "How TelemetryDeck Detects New Users" %} -New users are detected by the `TelemetryDeck.Acquisition.newInstallDetected` signal that is automatically sent on first app launch. -{% endnoteinfo %} +!!! info "How TelemetryDeck Detects New Users" + + New users are detected by the `TelemetryDeck.Acquisition.newInstallDetected` signal that is automatically sent on first app launch. ## User growth trends **Questions you can answer:** + - Is my user base growing over time? - Are there patterns in my acquisition rate? - How effective are my marketing campaigns? -![Daily, Weekly, and Monthly New Users](/docs/images/acquisition-new-users.png) +![Daily, Weekly, and Monthly New Users](/assets/acquisition-new-users.png) **How to interpret the charts:** + - **Consistent upward trend**: Your app is steadily gaining users – investigate what's working and double down - **Sudden spikes**: Often correlate with marketing campaigns, app updates, or press coverage – for example, the spike in February on the Weekly New Users chart might indicate a successful promotion - **Cyclical patterns**: May indicate seasonal interest or day-of-week effects - like potential weekend vs. weekday differences @@ -43,29 +45,33 @@ New users are detected by the `TelemetryDeck.Acquisition.newInstallDetected` sig ## New vs. active users ratio **Questions you can answer:** + - What proportion of my users are new versus returning? - Is my app primarily attracting new users or maintaining existing ones? - How does acquisition compare to retention? -![Active vs New Users Ratio](/docs/images/acquisition-active-vs-new.png) +![Active vs New Users Ratio](/assets/acquisition-active-vs-new.png) **How to interpret the chart:** -- **High ratio (>0.5)**: Many new users compared to your active base - suggesting strong acquisition but possibly weak retention -- **Low ratio (<0.2)**: Most of your active users are returning users - indicating strong retention but slower growth -- **Fluctuating ratio**: May indicate inconsistent marketing or seasonal patterns + +* **High ratio (`>0.5`)**: Many new users compared to your active base - suggesting strong acquisition but possibly weak retention +* **Low ratio (`<0.2`)**: Most of your active users are returning users - indicating strong retention but slower growth +* **Fluctuating ratio**: May indicate inconsistent marketing or seasonal patterns **Action example:** In the Daily Active vs New Users chart, you can see the ratio fluctuating between approximately 0.1 and 0.5. When it drops to 0.1 (as seen around March 13th), it suggests that only about 7.69% of your active users that day were new installations - the rest were returning users, which indicates good retention but might signal a need to boost acquisition efforts. ## Temporal usage patterns **Questions you can answer:** + - When during the day do users typically discover my app? - Which days of the week see the most new installations? - Do weekdays significantly outperform weekends for user acquisition? -![Acquisition Time Patterns](/docs/images/acquisition-by-time.png) +![Acquisition Time Patterns](/assets/acquisition-by-time.png) **How to interpret the charts:** + - **Hour of day peaks**: Show prime discovery times (user-local) - **Day of week trends**: Reveal which days drive the most acquisitions - **Weekend vs. weekday balance**: Indicates usage context (work vs. leisure) @@ -75,13 +81,15 @@ New users are detected by the `TelemetryDeck.Acquisition.newInstallDetected` sig ## Recent temporal patterns **Questions you can answer:** + - Are recent user acquisition patterns consistent with long-term trends? - Have specific events or campaigns created temporary spikes? - How can I differentiate between one-off events and genuine trends? -![Recent Acquisition Time Patterns](/docs/images/acquisition-by-time-last-weeks.png) +![Recent Acquisition Time Patterns](/assets/acquisition-by-time-last-weeks.png) **How to interpret the charts:** + - **Four-week hourly view**: Identifies specific days with unusual activity - **Weekly pattern comparison**: Shows consistency or variability week-to-week - **Recent weekend/weekday ratio**: Confirms if the overall pattern holds true @@ -91,13 +99,15 @@ New users are detected by the `TelemetryDeck.Acquisition.newInstallDetected` sig ## Device & platform distribution **Questions you can answer:** + - What devices are my users using to access my app? - Should I prioritize specific hardware or screen sizes? - When can I safely drop support for older devices? -![New Users by Device Type](/docs/images/acquisition-device-distribution.png) +![New Users by Device Type](/assets/acquisition-device-distribution.png) **How to interpret the chart:** + - **Top device models**: Prioritize testing and optimization for these devices - **Age of devices**: Indicates how quickly your users upgrade hardware - **Platform distribution**: Helps inform development priorities @@ -107,13 +117,15 @@ New users are detected by the `TelemetryDeck.Acquisition.newInstallDetected` sig ## Geographic & language distribution **Questions you can answer:** + - Where are my users located geographically? - What languages do my users speak? - Which markets should I prioritize for expansion? -![New Users by Country and Language](/docs/images/acquisition-geographic-distribution.png) +![New Users by Country and Language](/assets/acquisition-geographic-distribution.png) **How to interpret the charts:** + - **Dominant regions**: Countries with the largest segments indicate your strongest markets - **Unexpected regions**: Emerging segments may represent untapped opportunities - **Language distribution**: Helps prioritize localization efforts @@ -125,18 +137,21 @@ New users are detected by the `TelemetryDeck.Acquisition.newInstallDetected` sig As you analyze your acquisition metrics, consider these key questions: 1. **Marketing optimization** - - When should you schedule campaigns based on your users' discovery patterns? - - Which geographic markets show the strongest growth potential? - - How can you tailor your App Store presence for your most important regions? + + * When should you schedule campaigns based on your users' discovery patterns? + * Which geographic markets show the strongest growth potential? + * How can you tailor your App Store presence for your most important regions? 2. **Development priorities** - - Which devices and platforms deserve the most testing attention? - - What language localizations would benefit the most users? - - Are there seasonal patterns that should influence your feature roadmap? + + * Which devices and platforms deserve the most testing attention? + * What language localizations would benefit the most users? + * Are there seasonal patterns that should influence your feature roadmap? 3. **Growth opportunities** - - Are there unexpected regions or user segments showing organic growth? - - Does your user acquisition align with your marketing efforts and spend? - - How can you optimize the first-time experience for your most common user profiles? + + * Are there unexpected regions or user segments showing organic growth? + * Does your user acquisition align with your marketing efforts and spend? + * How can you optimize the first-time experience for your most common user profiles? Remember that acquisition is just the first step in the user journey. Once you've optimized how users find your app, focus next on activation – ensuring they have a great first experience that turns them into engaged users. \ No newline at end of file diff --git a/basics/activation.md b/docs/basics/activation.md similarity index 68% rename from basics/activation.md rename to docs/basics/activation.md index 4642b96..423cef1 100644 --- a/basics/activation.md +++ b/docs/basics/activation.md @@ -19,59 +19,65 @@ User activation is the key moment when users first feel your app’s core value. With TelemetryDeck's Activation dashboard, these insights are automatically collected and visualized. No additional code required. Please update to SwiftSDK 2.8.0 / KotlinSDK 6.0.0 or later. -{% noteinfo "How TelemetryDeck Detects Activated Users" %} -Activated users are those with at least 5 minutes accumulated total usage time within their first 5 sessions. -{% endnoteinfo %} +!!! info "How TelemetryDeck Detects Activated Users" + + Activated users are those with at least 5 minutes accumulated total usage time within their first 5 sessions. ## Activated user growth trends **Questions you can answer:** -- How many users are experiencing my app's core value? -- Are my activation rates improving over time? -- How effective is my onboarding experience? -- What are the immediate activation patterns in the last 24 hours? -![Hourly, Daily, Weekly, and Monthly Activated Users](/docs/images/activation-activated-users.png) +* How many users are experiencing my app's core value? +* Are my activation rates improving over time? +* How effective is my onboarding experience? +* What are the immediate activation patterns in the last 24 hours? + +![Hourly, Daily, Weekly, and Monthly Activated Users](/assets/activation-activated-users.png) **How to interpret the charts:** -- **Hourly (last 24h)**: Shows recent activation activity and immediate trends – useful for spotting issues or validating recent changes -- **Daily patterns**: Consistent upward trend indicates your Onboarding is working well; sudden spikes may correlate with improvements or campaigns -- **Weekly/Monthly trends**: Reveal longer-term activation health and seasonal patterns -- **Declining trends**: May indicate Onboarding friction, confusing first experiences, or performance issues + +* **Hourly (last 24h)**: Shows recent activation activity and immediate trends – useful for spotting issues or validating recent changes +* **Daily patterns**: Consistent upward trend indicates your Onboarding is working well; sudden spikes may correlate with improvements or campaigns +* **Weekly/Monthly trends**: Reveal longer-term activation health and seasonal patterns +* **Declining trends**: May indicate Onboarding friction, confusing first experiences, or performance issues **How to read your data:** In this example dashboard, the monthly chart shows activation growing over time. It peaks around March–April with more than 50 users. This suggests the activation strategy is working well. The weekly data is more stable, with about 12–15 users. Daily numbers vary more, from 1 to 8 users with occasional spikes. This looks like normal variation, not a deeper problem. Use the hourly chart to check onboarding changes right away. If activation drops after a release, investigate immediately. ## Activation time patterns **Questions you can answer:** -- When during the day do users typically get activated? -- Which days of the week see the most successful first experiences? -- Do weekdays or weekends provide better conditions for user activation? -- Are recent activation patterns consistent with long-term trends? -![Activation Time Patterns](/docs/images/activation-by-time.png) +* When during the day do users typically get activated? +* Which days of the week see the most successful first experiences? +* Do weekdays or weekends provide better conditions for user activation? +* Are recent activation patterns consistent with long-term trends? + +![Activation Time Patterns](/assets/activation-by-time.png) **How to interpret the charts:** -- **Hour of day patterns**: Show when users have time to properly explore your app (user-local timezone) -- **Day patterns (past 4 weeks)**: Reveal specific days with exceptional activation performance -- **Weekend vs. weekday balance**: Indicates whether activation requires focused time or fits into busy schedules -- **Recent trends**: Help identify if timing patterns are changing over time + +* **Hour of day patterns**: Show when users have time to properly explore your app (user-local timezone) +* **Day patterns (past 4 weeks)**: Reveal specific days with exceptional activation performance +* **Weekend vs. weekday balance**: Indicates whether activation requires focused time or fits into busy schedules +* **Recent trends**: Help identify if timing patterns are changing over time **How to read your data:** IIn this example, the Hour of Day chart shows activation peaks around 12–1 PM and 5–6 PM. This suggests users activate during lunch breaks and after work, when they have time to focus. The Day of Week chart shows Friday with the highest activation (34 users). Sunday has the lowest (21 users). This points to a clear weekday preference. If you see similar patterns, users likely reach your app’s core value during structured times. You can then time onboarding prompts and feature hints for weekday lunches and early evenings, when engagement is highest. ## Activation by device & platform **Questions you can answer:** -- Which devices provide the best activation experience? -- Should I optimize my Onboarding for specific hardware or screen sizes? -- Are there platform-specific activation barriers? -![Activated Users by Device Type](/docs/images/activation-device-distribution.png) +* Which devices provide the best activation experience? +* Should I optimize my Onboarding for specific hardware or screen sizes? +* Are there platform-specific activation barriers? + +![Activated Users by Device Type](/assets/activation-device-distribution.png) **How to interpret the chart:** -- **Top-performing devices**: These provide the best activation experience – understand why -- **Low activation devices**: May have UI issues, performance problems, or usability challenges -- **Platform differences**: iOS vs. Android vs. other platforms may have different activation patterns + +* **Top-performing devices**: These provide the best activation experience – understand why +* **Low activation devices**: May have UI issues, performance problems, or usability challenges +* **Platform differences**: iOS vs. Android vs. other platforms may have different activation patterns **How to read your data:** In this example, iOS users make up 60% of activated users. macOS users account for the remaining 40%. The iPhone 15 Pro Max leads with 8.57% of activations. Several other models follow at about 5.71% each. This fairly even split suggests onboarding works well across devices. A strong secondary platform, like macOS here, points to successful cross-platform activation. If your data looks similar, focus testing on the most common devices. At the same time, keep activation quality high across all platforms. @@ -79,16 +85,18 @@ Several other models follow at about 5.71% each. This fairly even split suggests ## Activation by geography & language **Questions you can answer:** -- Where are users most successfully experiencing my app's value? -- What languages correlate with higher activation rates? -- Which markets provide the strongest foundation for growth? -![Activated Users by Country and Language](/docs/images/activation-geographic-distribution.png) +* Where are users most successfully experiencing my app's value? +* What languages correlate with higher activation rates? +* Which markets provide the strongest foundation for growth? + +![Activated Users by Country and Language](/assets/activation-geographic-distribution.png) **How to interpret the charts:** -- **Strong activation regions**: Countries where users consistently experience your app's value -- **Emerging markets**: Regions with growing activation that may warrant localization investment -- **Language correlation**: Languages that correlate with successful activation experiences + +* **Strong activation regions**: Countries where users consistently experience your app's value +* **Emerging markets**: Regions with growing activation that may warrant localization investment +* **Language correlation**: Languages that correlate with successful activation experiences **How to read your data:** In this example dashboard, Germany is the strongest activation market at 37.14%. Italy follows with 14.29%, and the United States with 8.57%. The language data shows German users at 48.57%. English users follow at 28.57%, with Italian at 17.14%. These patterns suggest onboarding works best for certain languages. You can use this insight to focus localization on your top regions. This helps maximize activation where it already performs well. diff --git a/basics/basics.json b/docs/basics/basics.json similarity index 100% rename from basics/basics.json rename to docs/basics/basics.json diff --git a/basics/index.md b/docs/basics/index.md similarity index 95% rename from basics/index.md rename to docs/basics/index.md index eb880ad..259be9f 100644 --- a/basics/index.md +++ b/docs/basics/index.md @@ -1,22 +1,17 @@ --- +icon: fontawesome/solid/gauge title: Analytics Overview tags: - setup - basics - analytics -basics: true -description: Learn how to navigate TelemetryDeck's dashboard to find the insights you need about your app's performance. -lead: TelemetryDeck's dashboard is organized into intuitive sections that help you understand how users interact with your app. This guide walks you through each part of the interface. -searchEngineTitle: TelemetryDeck Dashboard Guide | App Analytics -searchEngineDescription: Learn how to navigate the TelemetryDeck dashboard to get the most out of your app analytics data. -order: 10 --- ## Dashboard navigation After integrating the TelemetryDeck SDK into your app, your data will start appearing in the TelemetryDeck dashboard. The main navigation tabs help you find different types of insights: -![TelemetryDeck Main Navigation](/docs/images/dashboard-main-nav.png) +![TelemetryDeck Main Navigation](/assets/dashboard-main-nav.png) Let's explore what you'll find in each section: @@ -24,7 +19,7 @@ Let's explore what you'll find in each section: The Overview tab provides a high-level summary of your app's performance, showing key metrics like daily active users, total users, user retention, app version distribution, and system version distribution: -![Overview Dashboard](/docs/images/dashboard-overview.png) +![Overview Dashboard](/assets/dashboard-overview.png) This view gives you immediate answers to: "How is my app performing right now?" and "Are my users upgrading to new versions?" @@ -32,7 +27,7 @@ This view gives you immediate answers to: "How is my app performing right now?" The Customers section organizes your user data according to the journey users take with your app, from discovery to monetization: -![Acquisition Dashboard](/docs/images/dashboard-acquisition.png) +![Acquisition Dashboard](/assets/dashboard-acquisition.png) Each tab focuses on a different stage of the user journey: @@ -110,7 +105,7 @@ TelemetryDeck also provides built-in presets for tracking purchases and revenue The Metrics section provides essential technical insights about your app across different devices and platforms: -![Metrics Dashboard - Devices](/docs/images/metrics-devices.png) +![Metrics Dashboard - Devices](/assets/metrics-devices.png) **Key metrics include:** @@ -139,7 +134,7 @@ The Metrics section provides essential technical insights about your app across The Explore section gives you direct access to your raw event data through multiple views: -![Explore Dashboard - Signal Types](/docs/images/dashboard-signal-types.png) +![Explore Dashboard - Signal Types](/assets/dashboard-signal-types.png) Here you can examine all signal types your app has sent, explore parameters attached to your events, view recent events chronologically, and use the query playground to experiment with data analysis. This section is particularly useful for debugging, understanding user behavior sequences, and diving deeper into specific user actions. @@ -162,7 +157,7 @@ Here you can examine all signal types your app has sent, explore parameters atta The Dashboards section allows you to create and organize custom visualizations: -![Custom Dashboards](/docs/images/dashboard-custom.png) +![Custom Dashboards](/assets/dashboard-custom.png) You can build insights using either the visual editor (for simple queries) or TelemetryDeck Query Language (TQL) for more complex analysis. Common dashboard setups include: @@ -223,7 +218,7 @@ Each insight can be organized into groups (displayed in the sidebar) to keep rel The Notebooks tab lets you combine live charts and markdown text in a single place, so you can document your questions, structure your investigations, and share insights with your team. Perfect for keeping context during long-running analyses, preparing presentations, or leaving notes for your future self. For example, here's an excerpt of a notebook: -![Sample Notebook investigating Onboarding issues with Explanations](/docs/images/dashboard-notebooks.png) +![Sample Notebook investigating Onboarding issues with Explanations](/assets/dashboard-notebooks.png) You can use Notebooks to: - Document your hypotheses and findings alongside live data @@ -253,7 +248,7 @@ See Notebooks in action in our [feature demo video](https://www.youtube.com/watc At the top of most dashboard pages, you'll find time filters that let you focus on specific periods: -![Filtering Data](/docs/images/dashboard-filtering-data.png) +![Filtering Data](/assets/dashboard-filtering-data.png) - **Last 30 Days** (default) – Shows data from the past month - **Test Mode** – Toggle to see data from development builds (when your app is run directly from your IDE) @@ -277,7 +272,7 @@ At the top of most dashboard pages, you'll find time filters that let you focus Many visualizations have interactive elements: -![Exploring Details](/docs/images/dashboard-exploring-details.png) +![Exploring Details](/assets/dashboard-exploring-details.png) - Hover over chart elements to see detailed values and source info - Use the "…" menu in the top right of cards for additional options diff --git a/docs/basics/metrics.md b/docs/basics/metrics.md new file mode 100644 index 0000000..f8d3518 --- /dev/null +++ b/docs/basics/metrics.md @@ -0,0 +1,178 @@ +--- +title: Metrics Analytics +tags: + - setup + - basics + - technical-metrics + - analytics +basics: true +description: Learn how to interpret and act on TelemetryDeck's technical metrics to optimize your app's performance and compatibility. +lead: Metrics Analytics provides critical technical insights about how your app performs across different devices, platforms, and configurations. TelemetryDeck automatically collects these details to help you make informed decisions about development priorities, OS support, and accessibility needs. +searchEngineTitle: App Technical Metrics & Analytics | TelemetryDeck +searchEngineDescription: Learn how to interpret technical metrics data for your app with TelemetryDeck's automatic device, version, and error analytics. +order: 70 +--- + +## What are Technical Metrics? + +Technical metrics represent the operational aspects of your app – how it performs across devices, versions, languages, and accessibility settings. These metrics help you understand your app's compatibility landscape, identify technical issues, and prioritize development efforts. + +With TelemetryDeck's Metrics dashboard, these insights are automatically collected and visualized with no additional code required beyond the basic SDK integration. + +## Device & platform distribution + +**Questions you can answer:** + +* What devices are my users using to access my app? +* Which platforms should I prioritize for development and testing? +* When can I safely drop support for older devices? + +![Device and Platform Distribution](/assets/metrics-devices.png) + +**How to interpret the charts:** + +* **Models**: Shows specific device models being used, helping prioritize testing +* **Types**: Reveals device categories for understanding user hardware preferences +* **Platforms**: Displays the OS platform breakdown (iOS, macOS, visionOS) +* **Platform distribution changes**: Identifies shifts in platform usage over time + +**Action example:** Looking at the Models chart, we can see MacBook Air (M1 and M2) models account for 17% of devices, while new Apple Vision Pro (RealityDevice) users represent 7%. iPhone models collectively account for a significant portion of usage. With iOS accounting for 62.05% of platform usage and macOS at 34.55%, you should prioritize iOS testing while maintaining solid macOS support. The emergence of visionOS at 3.40% shows early adoption of this platform that may warrant attention for future development. + +## Version analytics + +**Questions you can answer:** + +* How quickly are users adopting my latest app version? +* When do users typically update after a new release? +* Are there patterns in build adoption? +* Which SDK versions are in use across your user base? + +![Version Analytics](/assets/metrics-versions.png) + +**How to interpret the charts:** + +* **App Versions**: Tracks adoption of your app releases over time +* **Build Numbers**: Provides more granular insight into specific build adoption +* **SDK Versions**: Reveals which versions of your SDK are in use + +**Action example:** The App Versions chart shows a clear transition starting in early March, with users moving from the previous version (green) to the latest release (blue). This transition happened relatively quickly, suggesting users are responsive to updates. The Build Numbers chart shows a similar pattern, with a diverse range of builds in use before March consolidating to newer versions. The SDK Versions chart reveals an interesting transition from version 2.2.3 (yellow) to 2.2.4 (light green) and SwiftClient 1.5.1 (green) in March, suggesting your SDK updates are being adopted alongside app updates. This data indicates that you can likely count on most users updating within 2-3 weeks of a release. + +## Error monitoring + +**Questions you can answer:** + +* What are the most common errors occurring in my app? +* When did error rates spike or change? +* Which errors should I prioritize fixing? +* Are errors occurring on specific devices or platforms? + +![Error Monitoring](/assets/metrics-errors.png) + +**How to interpret the charts:** + +* **Most Frequent Errors**: Ranks issues by occurrence count and percentage +* **Error History**: Shows error frequency over time, highlighting spikes +* **Thrown Exception Errors**: Details specific exception types +* **Error distribution**: Helps correlate errors with platforms or versions + +**Action example:** The "Most Frequent Errors" chart identifies "ProjectDetails.loadAppInfo" as your most common error (29% of total errors), followed by "AITranslation.loadAvailability" (25%). These two issues account for more than half of all errors, making them high-priority fixes. The error history graph shows a significant spike in mid-March, which may correlate with a specific release or backend change. Focusing on resolving these top issues first will have the largest impact on improving app stability for your users. + +
+ +!!! info "Action Required" + + TelemetryDeck offers built-in presets for error tracking that require some configuration in your app code. With a few simple implementation steps, you can collect and categorize errors to identify patterns and prioritize fixes. + +- :material-chart-bar:{ .lg .middle } __Implementing Error Tracking__ + + --- + + Learn how to set up TelemetryDeck's error reporting system in your app code to capture and analyze runtime issues. + + [:fontawesome-solid-right-long: Learn more](../articles/preset-errors.md) +
+ +## Localization insights + +**Questions you can answer:** + +* What languages do my users speak? +* Which regions show significant app usage? +* Should I invest in additional localizations? +* Are users using my app in the same language as their device? + +![Localization Insights](/assets/metrics-localization.png) + +**How to interpret the charts:** + +* **Preferred Language**: Shows user device language settings +* **App Language**: Indicates which language the system (or user) has selected for your app +* **Region**: Displays geographical distribution of your users +* **Layout Direction**: Shows text direction preferences (LTR vs RTL) + +**Action example:** The data shows a diverse user base with English (45.52% preferred/51.87% app) and German (30.94% preferred/31.12% app) leading language usage. Germany (25.49%), US (20.60%), and UK (13.29%) are your top markets. The strong German presence in both language and regional data suggests significant opportunity for enhanced German-language support and targeted marketing in German-speaking countries. + +## Accessibility usage + +**Questions you can answer:** + +* How many of my users utilize accessibility features? +* Which accessibility settings are most common? +* Does my app need better accessibility support? +* How would design changes impact users with accessibility needs? + +![Accessibility Usage](/assets/metrics-accessibility.png) + +**How to interpret the charts:** + +* **Preferred Content Size**: Shows text size preferences (88.43% use default size "L") +* **Bold Text Usage**: Indicates users who need stronger text contrast (3.31%) +* **Reduce Motion**: Shows users sensitive to animations (0% in this sample) +* **Reduce Transparency**: Reveals users who need less UI transparency (4.13%) + +**Action example:** While most users (88.43%) use the default content size "L", a significant portion (6.61%) use the larger "XXXL" setting, indicating they need much larger text for readability. Additionally, 4.13% of users have enabled the Reduce Transparency setting. These metrics suggest you should test your app thoroughly with larger text settings and ensure it remains functional and attractive for users who need these accessibility features. The complete absence of "Reduce Motion" usage in your current user base doesn't mean you can ignore motion considerations – it may simply reflect that users who need this setting aren't using your app yet. + +## Making data-driven decisions with Metrics + +Your technical metrics provide critical insights for development decisions. Consider these key questions as you analyze the data: + +1. **Platform strategy** + + * Which device models and OS versions represent your core users? + * When can you adopt new platform features or drop legacy support? + * Does your user base justify expanding to emerging platforms? + +2. **Technical improvements** + + * Which errors have the highest impact on your user experience? + * Are specific devices or configurations experiencing more issues? + * How quickly are users adopting your latest app version? + +3. **Localization priorities** + + * Which languages and regions show significant adoption? + * Are users using your app in their preferred language? + * Would accessibility improvements benefit a meaningful portion of users? + +Remember that technical metrics complement your user behavior analytics. By combining device insights with acquisition and engagement data, you'll build a complete picture of how technical decisions impact real-world user experience. + +## Related guides + +
+ +- :material-chart-bar:{ .lg .middle } __Strategic OS Support Planning__ + + --- + + Learn the practical framework for balancing backward compatibility with development resources using metrics data. + + [:fontawesome-solid-right-long: Learn more](../articles/decide-to-drop-ios-version.md) + +- :material-chart-bar:{ .lg .middle } __Version Migration Insights__ + + --- + + Discover patterns in how quickly users update and what this means for your release strategy. + + [:fontawesome-solid-right-long: Learn more](../articles/check-if-users-upgrade-to-latest-app-version.md) +
\ No newline at end of file diff --git a/basics/pirate-metrics.md b/docs/basics/pirate-metrics.md similarity index 69% rename from basics/pirate-metrics.md rename to docs/basics/pirate-metrics.md index 01b179b..26792f0 100644 --- a/basics/pirate-metrics.md +++ b/docs/basics/pirate-metrics.md @@ -18,7 +18,7 @@ Pirate Metrics, also known as the AARRR framework, was created by venture capita This framework breaks down the complete user journey into five key stages that form a natural progression: -![AARRR Framework Chart](/docs/images/aarrr-framework.png) +![AARRR Framework Chart](/assets/aarrr-framework.png) 1. **Acquisition**: How users discover and install your app 2. **Activation**: How users experience your app for the first time @@ -42,10 +42,11 @@ Most importantly, organizing analytics this way helps you identify your biggest The great news? **TelemetryDeck has done all the hard work for you**. Rather than requiring you to: -- Research which metrics matter for each stage -- Create custom tracking for each metric -- Build visualizations and dashboards -- Understand the intricacies of the framework + +* Research which metrics matter for each stage +* Create custom tracking for each metric +* Build visualizations and dashboards +* Understand the intricacies of the framework TelemetryDeck automatically organizes analytics around the Pirate Metrics framework with built-in insights that require minimal setup. Just update to our latest SDK, and the built-in metrics start working immediately. @@ -56,50 +57,55 @@ Here's what TelemetryDeck automatically tracks for each stage of the Pirate Metr ### Acquisition insights TelemetryDeck automatically tracks: -- Daily, weekly, and monthly new user counts -- Device and platform distribution (which devices are your new users using?) -- Geographic distribution (where are your new users located?) -- Acquisition time patterns (when do users typically discover your app?) + +* Daily, weekly, and monthly new user counts +* Device and platform distribution (which devices are your new users using?) +* Geographic distribution (where are your new users located?) +* Acquisition time patterns (when do users typically discover your app?) All of these insights help you understand where your users come from and optimize your marketing efforts. ### Activation insights Our activation dashboard automatically tracks: -- Daily, weekly, and monthly active user counts -- Average session length distribution -- Average sessions per active user -- Usage patterns by time of day and day of week -- First-time user experiences + +* Daily, weekly, and monthly active user counts +* Average session length distribution +* Average sessions per active user +* Usage patterns by time of day and day of week +* First-time user experiences These insights help you understand if users are successfully getting started with your app. ### Retention insights Our retention dashboard automatically tracks: -- Distinct days used distribution (how often users return) -- Engaged users metrics (session length > 2 minutes, 5+ days used per month) -- Power users metrics (session length > 5 minutes, 12+ days used per month) -- Long-term usage trends + +* Distinct days used distribution (how often users return) +* Engaged users metrics (session length > 2 minutes, 5+ days used per month) +* Power users metrics (session length > 5 minutes, 12+ days used per month) +* Long-term usage trends These insights help you identify if users are finding ongoing value in your app. ### Referral insights (Coming Soon) Our upcoming referral dashboard will track: -- Referral tracking metrics -- Channel effectiveness comparison -- Social sharing activity + +* Referral tracking metrics +* Channel effectiveness comparison +* Social sharing activity These insights help you understand how effectively your app generates word-of-mouth growth. ### Revenue insights (Coming Soon) Our upcoming revenue dashboard will track: -- Purchase counts (trials and non-trials) -- Free trial metrics -- Trial conversion rates -- Revenue trends over time + +* Purchase counts (trials and non-trials) +* Free trial metrics +* Trial conversion rates +* Revenue trends over time These insights help you understand your app's financial performance and optimize monetization. @@ -113,10 +119,10 @@ For some advanced metrics (particularly in the Referral and Revenue categories), By implementing Pirate Metrics as built-in dashboards, TelemetryDeck gives you the benefits of sophisticated growth analytics without requiring you to: -- Become an expert in analytics theory -- Build complex custom dashboards -- Spend time deciphering what metrics matter -- Manually track dozens of different events +* Become an expert in analytics theory +* Build complex custom dashboards +* Spend time deciphering what metrics matter +* Manually track dozens of different events Instead, you get clear, actionable insights organized in a way that naturally guides you toward your biggest growth opportunities. @@ -132,17 +138,13 @@ As you navigate through these sections, you'll develop a comprehensive understan To dive deeper into specific parts of the framework, check out our dedicated guides: -
-
-
- -
-

- - Acquisition Analytics Guide -

-

Learn how to interpret and act on acquisition metrics to optimize how users discover your app.

-
-
-
+
+ +- :material-chart-bar:{ .lg .middle } __Acquisition Analytics Guide__ + + --- + + Learn how to interpret and act on acquisition metrics to optimize how users discover your app. + + [:fontawesome-solid-right-long: Learn more](acquisition.md)
diff --git a/basics/retention.md b/docs/basics/retention.md similarity index 71% rename from basics/retention.md rename to docs/basics/retention.md index 7e83c31..a58fca4 100644 --- a/basics/retention.md +++ b/docs/basics/retention.md @@ -19,75 +19,83 @@ User retention measures how effectively your app keeps users coming back after t With TelemetryDeck's Retention dashboard, these insights focus on users who can be considered "returning users" (those with more than 5 sessions) and are automatically collected with no additional code required beyond updating to SwiftSDK 2.8.0 / KotlinSDK 6.0.0 or later. -{% noteinfo "How TelemetryDeck Detects Returning Users" %} -Returning users are those who have used your app for more than 5 sessions. -{% endnoteinfo %} +!!! info "How TelemetryDeck Detects Returning Users" + + Returning users are those who have used your app for more than 5 sessions. ## Returning user growth trends **Questions you can answer:** -- How many of my users are becoming regular, returning users? -- Are my retention rates improving over time? -- What are the immediate retention patterns in the last 24 hours? -- How do retention trends compare across different time periods? -![Hourly, Daily, Weekly, and Monthly Returning Users](/docs/images/retention-returning-users.png) +* How many of my users are becoming regular, returning users? +* Are my retention rates improving over time? +* What are the immediate retention patterns in the last 24 hours? +* How do retention trends compare across different time periods? + +![Hourly, Daily, Weekly, and Monthly Returning Users](/assets/retention-returning-users.png) **How to interpret the charts:** -- **Hourly (last 24h)**: Shows recent returning user activity – useful for immediate validation of retention initiatives -- **Daily patterns**: Consistent levels indicate stable user engagement; growth suggests improving retention strategies -- **Weekly/Monthly trends**: Reveal long-term retention health and seasonal engagement patterns -- **Declining trends**: May indicate rising churn rate due to user experience issues, feature problems, or competitive pressures + +* **Hourly (last 24h)**: Shows recent returning user activity – useful for immediate validation of retention initiatives +* **Daily patterns**: Consistent levels indicate stable user engagement; growth suggests improving retention strategies +* **Weekly/Monthly trends**: Reveal long-term retention health and seasonal engagement patterns +* **Declining trends**: May indicate rising churn rate due to user experience issues, feature problems, or competitive pressures **How to read your data:** In this example dashboard, the monthly chart shows excellent retention growth from March (~8 users) building steadily through the months to reach a strong July peak (~57 users), indicating highly successful retention strategies building momentum over time. The weekly view shows more gradual growth from ~8 to ~35 users over the past 3 months, while daily retention fluctuates between 3-15 users with occasional spikes. Use the daily fluctuations to validate immediate retention initiatives rather than waiting for weekly trends to develop. ## Retention time patterns **Questions you can answer:** -- When do returning users typically engage with my app during the day? -- Which days of the week show the strongest user loyalty? -- Do returning users prefer weekdays or weekends for app usage? -- Are retention patterns changing over recent weeks? -![Retention Time Patterns](/docs/images/retention-by-time.png) +* When do returning users typically engage with my app during the day? +* Which days of the week show the strongest user loyalty? +* Do returning users prefer weekdays or weekends for app usage? +* Are retention patterns changing over recent weeks? + +![Retention Time Patterns](/assets/retention-by-time.png) **How to interpret the charts:** -- **Hour of day patterns**: Show when returning users are most active (user-local timezone) -- **Day of week distribution**: Reveals which days drive the strongest retention engagement -- **Day patterns (past 4 weeks)**: Identify specific days with exceptional returning user activity -- **Weekend vs. weekday balance**: Indicates whether your app fits into work routines or leisure time + +* **Hour of day patterns**: Show when returning users are most active (user-local timezone) +* **Day of week distribution**: Reveals which days drive the strongest retention engagement +* **Day patterns (past 4 weeks)**: Identify specific days with exceptional returning user activity +* **Weekend vs. weekday balance**: Indicates whether your app fits into work routines or leisure time **How to read your data:** In this example, the Hour of Day chart shows returning users are most active during midday hours (12-3 PM) with peak activity around 2 PM, suggesting users engage with the app during lunch breaks and afternoon periods. The Day of Week chart reveals Wednesday shows the strongest retention (~55 users), followed by Tuesday (~53) and Monday (~50), while Thursday shows the lowest (~40). Friday through Sunday maintain consistent moderate levels (~45 each). When you see patterns like this in your data, it would indicate your app fits well into midweek routines. You could then schedule feature updates and important communications for Wednesday when user engagement peaks. ## Retention by device & platform **Questions you can answer:** -- Which devices retain users most effectively? -- Are there platform-specific retention challenges? -- Should I prioritize retention improvements for specific hardware? -![Returning Users by Device Type](/docs/images/retention-device-distribution.png) +* Which devices retain users most effectively? +* Are there platform-specific retention challenges? +* Should I prioritize retention improvements for specific hardware? + +![Returning Users by Device Type](/assets/retention-device-distribution.png) **How to interpret the charts:** -- **Top-retention devices**: These provide the best long-term user experience -- **Poor retention devices**: May have performance issues, UI problems, or technical barriers -- **Platform differences**: iOS vs. macOS vs. other platforms may show different retention strengths + +* **Top-retention devices**: These provide the best long-term user experience +* **Poor retention devices**: May have performance issues, UI problems, or technical barriers +* **Platform differences**: iOS vs. macOS vs. other platforms may show different retention strengths **How to read your data:** In this example, iOS represents 60% of returning users while macOS accounts for 40%. Among individual devices, several models each contribute about 10% of returning users (iPhone 12 mini, iPad 9th generation, various MacBook Air and MacBook Pro models). When you see relatively even distribution across device types like this, it suggests your app provides consistent long-term value across different platforms and device generations. The strong macOS presence (40%) would indicate successful cross-platform retention and higher repeat purchase rate (RPR) for users who engage across multiple devices. If you see similar patterns, focus on maintaining this consistency rather than optimizing for specific hardware configurations. ## Retention by geography & language **Questions you can answer:** -- Which regions show the strongest user loyalty? -- What languages correlate with higher long-term engagement? -- Are there cultural or regional patterns in user retention? -![Returning Users by Country and Language](/docs/images/retention-geographic-distribution.png) +* Which regions show the strongest user loyalty? +* What languages correlate with higher long-term engagement? +* Are there cultural or regional patterns in user retention? + +![Returning Users by Country and Language](/assets/retention-geographic-distribution.png) **How to interpret the charts:** -- **High-retention regions**: Countries where users consistently return to your app -- **Growing markets**: Regions showing improving retention that warrant additional investment -- **Language loyalty patterns**: Languages that correlate with strong long-term engagement + +* **High-retention regions**: Countries where users consistently return to your app +* **Growing markets**: Regions showing improving retention that warrant additional investment +* **Language loyalty patterns**: Languages that correlate with strong long-term engagement **How to read your data:** In this example dashboard, Germany dominates at 50% of returning users, with several other countries each representing about 10% (Italy, India, Spain, Canada, Afghanistan). The language distribution shows English (40%) and German (30%) as the primary languages, with Turkish (10%) as a notable third language. When you see such strong geographic concentration in your own data (like Germany's 50% here), it suggests that users in this region find exceptional long-term value in your app. You could then study what works particularly well for your top-retention market and apply those insights to improve retention in other regions. diff --git a/glossary.md b/docs/glossary.md similarity index 100% rename from glossary.md rename to docs/glossary.md diff --git a/guides/guides.json b/docs/guides/guides.json similarity index 100% rename from guides/guides.json rename to docs/guides/guides.json diff --git a/guides/privacy-faq.md b/docs/guides/privacy-faq.md similarity index 99% rename from guides/privacy-faq.md rename to docs/guides/privacy-faq.md index 975e825..347089b 100644 --- a/guides/privacy-faq.md +++ b/docs/guides/privacy-faq.md @@ -7,7 +7,7 @@ featured: true description: Frequently asked questions about TelemetryDeck's Privacy Policy lead: This FAQ answers the most common questions about our Privacy Policy and how to answer your users' questions. order: 10000 -headerImage: /docs/images/faq.jpg +headerImage: /assets/faq.jpg --- The TelemetryDeck SDK was developed from the ground up with privacy in mind. An important principle of TelemetryDeck is to store only the minimum amount of data necessary to identify specific user behaviors. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..903bd16 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,73 @@ +--- +tags: + - Get started + - Setup +--- + +!!! warning + + This is a test for a new documentation system. Trust nothing and no one. The original TelemetryDeck + documentation still lives at https://telemetrydeck.com/docs/ for now. + + +--- + +# Get Started + +TelemetryDeck helps you understand how users interact with your application through privacy-focused analytics. With just an SDK integration, you'll get valuable insights automatically – no complex configuration required. + +_You can find all our supported SDKs [here](guides/sdk-setup/#official-sdks){ data-preview }._ + +## Installation + +TelemetryDeck is a SaaS solution for privacy-focused analytics. We provide you with out-of-the-box charts and insights, while maintaining your users privacy at all times. + +!!! note "Prerequisites" + Create an account with TelemetryDeck [here](https://dashboard.telemetrydeck.com/login). + +### Install an SDK in 2 steps + + +1. **[Choose and install](guides/sdk-setup/#official-sdks){ data-preview }** the SDK for your platform. +2. **Deploy your app** to start collecting data + +Once your updated app is in users' hands, TelemetryDeck will begin collecting data. It may take some time before you see meaningful insights, depending on your app's usage. + +## What's next after setup? + +After setting up the SDK and deploying your app, your most important next step is to learn how to use the dashboard to analyze your data: + +
+ +- :material-chart-bar:{ .lg .middle } __Analytics Walkthrough__ + + --- + + Learn how to navigate TelemetryDeck, interpret `insights`, + and use analytics to make `data-driven decisions` that improve your app + and grow your user base. + + [:fontawesome-solid-right-long: Getting started](/basics) + +- :material-test-tube:{ .lg .middle } __Testmode__ + + --- + + Test Mode helps you make sure that TelemetryDeck is set up `correctly` in your app + and allows you to set up your `insights even during development`. + + [:fontawesome-solid-right-long: Getting started](/articles/test-mode.md) +
+ +!!! info "How we handle user data with double hashing" + Our SDKs accept a custom user identifier – like an email or an internal identifier – which we **double hash**, once on the user's device and once on our servers. Read more [here](articles/anonymization-how-it-works.md). + + +## Documentation Feedback + +If you find an error or feel like the documentation could be improved somewhat, we'd love +to hear from you! Either directly submit a change request with the buttons on each page, +or open an issue in our :fontawesome-brands-github: Docs GitHub Repository. + +The [documentation guide](/articles/documentation) explains all markdown and +additional features you can use while writing documentation for TelemetryDeck. \ No newline at end of file diff --git a/ingest/default-parameters.md b/docs/ingest/default-parameters.md similarity index 100% rename from ingest/default-parameters.md rename to docs/ingest/default-parameters.md diff --git a/ingest/ingest.json b/docs/ingest/ingest.json similarity index 100% rename from ingest/ingest.json rename to docs/ingest/ingest.json diff --git a/ingest/v1.md b/docs/ingest/v1.md similarity index 94% rename from ingest/v1.md rename to docs/ingest/v1.md index 8a79b77..ed0f108 100644 --- a/ingest/v1.md +++ b/docs/ingest/v1.md @@ -4,13 +4,12 @@ description: A reference of all properties of the Signal Ingestion API lead: While we have SDKs available for Swift, Kotlin, JavaScript and Unity, developers who work with other languages and frameworks can also use TelemetryDeck by sending signals via HTTP POST request directly to our ingestion API. This is what our SDKs do. order: 99999 searchEngineDescription: A reference of all properties of the Signal Ingestion API. Note -- We’re continuing to host ingest v1, but officially deprecating it and recommending using v2. +status: deprecated --- -{% notewarning "Deprecated" %} +!!! warning "Deprecated" -While we're continuing to host ingest v1, we're officially deprecating it and recommending our customers to use the [v2 API](/docs/ingest/v2/) instead. The v2 API is more flexible, has better support for custom data, and is more future proof. - -{% endnotewarning %} + While we're continuing to host ingest v1, we're officially deprecating it and recommending our customers to use the [v2 API](/docs/ingest/v2/) instead. The v2 API is more flexible, has better support for custom data, and is more future proof. This article will first show the minimal structure needed for a signal to send, and then talk about some of the optional extensions. diff --git a/ingest/v2.md b/docs/ingest/v2.md similarity index 100% rename from ingest/v2.md rename to docs/ingest/v2.md diff --git a/integrations/integrations.json b/docs/integrations/integrations.json similarity index 100% rename from integrations/integrations.json rename to docs/integrations/integrations.json diff --git a/docs/integrations/revenuecat.md b/docs/integrations/revenuecat.md new file mode 100644 index 0000000..446bc1f --- /dev/null +++ b/docs/integrations/revenuecat.md @@ -0,0 +1,89 @@ +--- +title: Using TelemetryDeck with RevenueCat +tags: + - how-to + - iOS +testedOn: RevenueCat Swift SDK 5.2.3, SwiftSDK 3.0.0 +description: Integrate TelemetryDeck with RevenueCat to combine usage data with purchase data. +lead: Integrate TelemetryDeck with RevenueCat to combine usage data with purchase data. +order: 90 +--- + +[RevenueCat](https://www.revenuecat.com/) helps you process payments and in-app purchases across iOS, Android, and the web. + +With this integration, RevenueCat events appear in your TelemetryDeck dashboard alongside your app analytics β€” all on one screen. + +!!! warning "Read the announcement" + + Our [announcement blog post](https://telemetrydeck.com/blog/revenuecat-integration/) shows you what to expect, how to use the new revenue dashboard, and gives examples on what to do with your revenue data. + +## Installing RevenueCat and TelemetryDeck + +1. [Install and set up TelemetryDeck](/guides/swift-setup/) +2. [Install and set up RevenueCat](https://www.revenuecat.com/docs/getting-started/installation) + +## Configuring the RevenueCat SDK + +RevenueCat uses **user attributes** to correlate users across services. Set two attributes so RevenueCat users match their TelemetryDeck identities: + +- `$telemetryDeckAppId`: Your TelemetryDeck App ID +- `$telemetryDeckUserId`: The **already-hashed** user identifier that TelemetryDeck uses + +!!! warning "RevenueCat needs the hashed identifier" + + TelemetryDeck hashes user identifiers before transmission. You need to extract the hashed value and pass that to RevenueCat, otherwise the identifiers won't match. + + If your SDK version doesn't expose the hashed identifier directly, compute it yourself: `SHA256(user_id + salt)`. + +### iOS + +```swift +// 1. Initialize TelemetryDeck +let telemetrydeckAppID = "AAAAAAAA-BBBB-CCCC-DDDD" +Task { + try await TelemetryDeck.initialize( + appID: telemetrydeckAppID, + namespace: "YOUR-NAMESPACE", + salt: "MY_SECRET_SALT" + ) + + // 2. Set a user identifier + let myUserID = UIDevice.current + .identifierForVendor?.uuidString + ?? "unknown user" + await TelemetryDeck.setUserIdentifier(myUserID) +} + +// 3. Configure RevenueCat with the hashed TelemetryDeck user ID +Purchases.configure(withAPIKey: "my_revenuecat_api_key") + +// Compute the hashed identifier to pass to RevenueCat +// This must match what TelemetryDeck sends to the server +import CryptoKit +let hashedUser = SHA256.hash( + data: Data((myUserID + "MY_SECRET_SALT").utf8) +).map { String(format: "%02x", $0) }.joined() + +Purchases.shared.attribution.setAttributes([ + "$telemetryDeckUserId": hashedUser, + "$telemetryDeckAppId": telemetrydeckAppID +]) +``` + +!!! warning "Keep identifiers in sync" + + Whenever you update your TelemetryDeck user identifier, also update RevenueCat's `$telemetryDeckUserId` attribute. + +## Setting up RevenueCat's TelemetryDeck integration + +Tell RevenueCat to forward events to TelemetryDeck: + +1. Navigate to your RevenueCat Project +2. In the left sidebar, click **Integrations** +3. Select **TelemetryDeck** +4. Click **Add Integration** + +![A screenshot of RevenueCat's TelemetryDeck Integration](/assets/rc-td-1.png) +![A screenshot of RevenueCat's TelemetryDeck Integration](/assets/rc-td-2.png) + +RevenueCat events will now appear in your TelemetryDeck dashboard. diff --git a/docs/integrations/superwall.md b/docs/integrations/superwall.md new file mode 100644 index 0000000..4df04a1 --- /dev/null +++ b/docs/integrations/superwall.md @@ -0,0 +1,60 @@ +--- +title: Using TelemetryDeck with Superwall iOS +tags: + - how-to + - iOS +testedOn: SuperWall iOS SDK 3.2 +description: Integrate TelemetryDeck with SuperWall to get insights into your paywalls. +lead: Integrate TelemetryDeck with SuperWall to get insights into your paywalls. +order: 100 +--- + +[Superwall](https://superwall.com/) lets you experiment with different paywalls and monetization strategies. Combined with TelemetryDeck, you can see how users interact with your paywalls. + +Superwall provides hooks that forward events to TelemetryDeck. This guide shows you how to wire them up. + +## Installing Superwall and TelemetryDeck + +Integrate both SDKs into your app: + +1. [Install and set up the TelemetryDeck SDK](/guides/swift-setup/) +2. [Install and set up the Superwall SDK](https://docs.superwall.com/docs/installation-via-spm) + +If you've already set up either SDK, skip that step. The initialization order doesn't matter. + +## Creating a Superwall delegate + +Create a new file `SuperwallService.swift`: + +```swift +import SuperwallKit +import TelemetryDeck + +class SuperwallService: SuperwallDelegate { + func handleSuperwallEvent(withInfo eventInfo: SuperwallEventInfo) { + var stringifiedParams: [String: String] = [:] + + for param in eventInfo.params { + stringifiedParams[param.key] = String(describing: param.value) + } + + Task { + await TelemetryDeck.event( + eventInfo.event.description, + parameters: stringifiedParams + ) + } + } +} +``` + +## Registering the delegate + +In the function where you initialize TelemetryDeck and Superwall (usually your `App` struct or `AppDelegate`), add: + +```swift +let superwallService = SuperwallService() +Superwall.shared.delegate = superwallService +``` + +Paywall shown, dismissed, and subscription events will now appear in your TelemetryDeck dashboard. diff --git a/integrations/web-setup-google-tag-manager.md b/docs/integrations/web-setup-google-tag-manager.md similarity index 100% rename from integrations/web-setup-google-tag-manager.md rename to docs/integrations/web-setup-google-tag-manager.md diff --git a/docs/markdown.md b/docs/markdown.md new file mode 100644 index 0000000..e2da6dc --- /dev/null +++ b/docs/markdown.md @@ -0,0 +1,111 @@ +--- +icon: simple/markdown +--- + +# Markdown in 5min + +## Headers + +``` +# H1 Header +## H2 Header +### H3 Header +#### H4 Header +##### H5 Header +###### H6 Header +``` + +## Text formatting + +``` +**bold text** +*italic text* +***bold and italic*** +~~strikethrough~~ +`inline code` +``` + +## Links and images + +``` +[Link text](https://example.com) +[Link with title](https://example.com "Hover title") +![Alt text](image.jpg) +![Image with title](image.jpg "Image title") +``` + +## Lists + +``` +Unordered: + +- Item 1 +- Item 2 + - Nested item + +Ordered: + +1. First item +2. Second item +3. Third item +``` + +## Blockquotes + +``` +> This is a blockquote +> Multiple lines +>> Nested quote +``` + +## Code blocks + +```` +```javascript +function hello() { + console.log("Hello, world!"); +} +``` +```` + +## Tables + +``` +| Header 1 | Header 2 | Header 3 | +|----------|----------|----------| +| Row 1 | Data | Data | +| Row 2 | Data | Data | +``` + +## Horizontal rule + +``` +--- +or +*** +or +___ +``` + +## Task lists + +``` +- [x] Completed task +- [ ] Incomplete task +- [ ] Another task +``` + +## Escaping characters + +``` +Use backslash to escape: \* \_ \# \` +``` + +## Line breaks + +``` +End a line with two spaces +to create a line break. + +Or use a blank line for a new paragraph. +``` \ No newline at end of file diff --git a/recipes/averagaging-numbers.md b/docs/recipes/averagaging-numbers.md similarity index 100% rename from recipes/averagaging-numbers.md rename to docs/recipes/averagaging-numbers.md diff --git a/recipes/listing-events.md b/docs/recipes/listing-events.md similarity index 100% rename from recipes/listing-events.md rename to docs/recipes/listing-events.md diff --git a/recipes/recipes.json b/docs/recipes/recipes.json similarity index 100% rename from recipes/recipes.json rename to docs/recipes/recipes.json diff --git a/recipes/views-per-purchase-example.md b/docs/recipes/views-per-purchase-example.md similarity index 100% rename from recipes/views-per-purchase-example.md rename to docs/recipes/views-per-purchase-example.md diff --git a/guides/react-setup.md b/docs/sdk/community-sdk/react-setup.md similarity index 70% rename from guides/react-setup.md rename to docs/sdk/community-sdk/react-setup.md index 5ff1982..4468c18 100644 --- a/guides/react-setup.md +++ b/docs/sdk/community-sdk/react-setup.md @@ -1,17 +1,14 @@ --- +icon: fontawesome/brands/react title: React (Native) Setup Guide tags: - - Setup - - JavaScript - - Node - - NPM - - TypeScript - - React - - React Native -featured: true -description: How to include the TelemetryDeck SDK in Your React or React Native App -lead: You have an account, and you have an app ID. Now let's include the TelemetryClient Package in your React Native application or React Web App -order: 300 + - setup + - javascript + - node + - npm + - typescript + - react + - react native --- ## Prerequisites @@ -58,7 +55,7 @@ ReactDOM.render(, document.getElementById("root")); ## Basic usage To send events, use the `useTelemetryDeck` hook and destructure the various methods that can be used to modify the instance or send events to TelemetryDeck. -For more information, see the [JS documentation](/docs/guides/javascript-setup/). +For more information, see the [JS documentation](../official-sdk/javascript-setup.md). ```tsx import * as React from "react"; @@ -140,28 +137,18 @@ registerRootComponent(App); Now that you've integrated TelemetryDeck, learn how to use the analytics platform to gain valuable insights about your users: -
-
-
- -
-

- - πŸ“Š Analytics Walkthrough -

-

Learn how to navigate TelemetryDeck, interpret insights, and use analytics to make data-driven decisions that improve your app and grow your user base.

-

- Start here to get real value from your analytics - β†’ -

-
-
-
+
+ +- **πŸ“Š Analytics Walkthrough** + + Learn how to navigate TelemetryDeck, interpret insights, and use analytics to make data-driven decisions that improve your app and grow your user base. + + [:fontawesome-solid-right-long: Start here to get real value from your analytics](../../basics/index.md)
## Sponsors -[](https://typedigital.de) +[](https://typedigital.de) The development of the TelemetryDeck React SDK was graciously provided by our friends at Augsburg-based web development agency [typedigital](https://typedigital.de). Thanks a lot, and check them out for your web application development needs. 🧑 diff --git a/guides/vue-setup.md b/docs/sdk/community-sdk/vue-setup.md similarity index 67% rename from guides/vue-setup.md rename to docs/sdk/community-sdk/vue-setup.md index 6d58dc8..2a540cd 100644 --- a/guides/vue-setup.md +++ b/docs/sdk/community-sdk/vue-setup.md @@ -1,17 +1,14 @@ --- +icon: fontawesome/brands/vuejs title: Vue.js Setup Guide tags: - - Setup - - JavaScript - - Node - - NPM - - TypeScript - - Vue - - VueJS -featured: true -description: How to include the TelemetryDeck SDK in Your Vue.js App -lead: You have an account, and you have an app ID. Now let's include the TelemetryClient Package in your Vue.js application -order: 300 + - setup + - javascript + - node + - npm + - typescript + - vue + - vuejs --- ## Prerequisites @@ -132,28 +129,18 @@ const buttonQueueClickWithOptions = () => { Now that you've integrated TelemetryDeck, learn how to use the analytics platform to gain valuable insights about your users: -
-
-
- -
-

- - πŸ“Š Analytics Walkthrough -

-

Learn how to navigate TelemetryDeck, interpret insights, and use analytics to make data-driven decisions that improve your app and grow your user base.

-

- Start here to get real value from your analytics - β†’ -

-
-
-
+
+ +- **πŸ“Š Analytics Walkthrough** + + Learn how to navigate TelemetryDeck, interpret insights, and use analytics to make data-driven decisions that improve your app and grow your user base. + + [:fontawesome-solid-right-long: Start here to get real value from your analytics](../../basics/index.md)
## Sponsors -[](https://www.peerigon.com) +[](https://www.peerigon.com) The development of the TelemetryDeck Vue SDK was graciously provided by our friends at Augsburg-based bespoke software development company [Peerigon](https://www.peerigon.com). Thanks a lot, and check them out for your application development needs. 🧑 diff --git a/articles/grand-rename.md b/docs/sdk/guides/grand-rename.md similarity index 94% rename from articles/grand-rename.md rename to docs/sdk/guides/grand-rename.md index 4b2d490..7a23c23 100644 --- a/articles/grand-rename.md +++ b/docs/sdk/guides/grand-rename.md @@ -36,9 +36,12 @@ For our signals & parameters, we decided to prefix them with `TelemetryDeck` to - `UserPreference`: Any choices the user made to express personal preferences, such as language & region. - `Navigation`: Information about how a user is navigating through the app, such as URL paths or view controllers. -{% noteinfo "No immediate action needed" %} -To make sure these changes don't affect existing customers with existing insights anytime soon, we decided to keep sending the old names for a transition period of at least 1 year while also sending the new ones. We recommend using the new ones for any new insights you create and will elaborate ways to support our users in transitioning any existing insights before we fully migrate. -{% endnoteinfo %} +!!! info "No immediate action needed" + + To make sure these changes don't affect existing customers with existing insights anytime soon, + we decided to keep sending the old names for a transition period of at least 1 year while also + sending the new ones. We recommend using the new ones for any new insights you create and will + elaborate ways to support our users in transitioning any existing insights before we fully migrate. ## Filling the Gaps diff --git a/docs/sdk/guides/telemetry-client.md b/docs/sdk/guides/telemetry-client.md new file mode 100644 index 0000000..74ebb42 --- /dev/null +++ b/docs/sdk/guides/telemetry-client.md @@ -0,0 +1,29 @@ +--- +title: TelemetryClient (Legacy) +tags: + - swift + - sdk + - legacy +status: deprecated +--- + +!!! warning "Outdated" + + The APIs on this page (`TelemetryManager`, `TelemetryClient`, `TelemetryManagerConfiguration`) were removed in SwiftSDK 3.0. See the [Swift Setup Guide](/guides/swift-setup) for current documentation or the [V3 Migration Guide](../official-sdk/swift/swift-migration-v3.md) if upgrading. + +## Historical context + +The original Swift SDK (called "SwiftClient") used a `TelemetryManager` singleton with a `TelemetryManagerConfiguration` class. These were replaced by the `TelemetryDeck` static API in the [Grand Rename](grand-rename.md) and formally removed in SwiftSDK 3.0. + +## Quick migration reference + +| Old API | Current API | +|---|---| +| `TelemetryManagerConfiguration(appID:)` | `TelemetryDeck.initialize(appID:namespace:)` | +| `TelemetryManager.initialize(config:)` | `TelemetryDeck.initialize(appID:namespace:)` | +| `TelemetryManager.send("event")` | `await TelemetryDeck.event("event")` | +| `TelemetryManager.send("event", for: user)` | `await TelemetryDeck.event("event", customUserID: user)` | +| `TelemetryManager.send("event", with: params)` | `await TelemetryDeck.event("event", parameters: params)` | +| `TelemetryManager.shared.hashedDefaultUser` | Not exposed directly; use `setUserIdentifier(_:)` | +| `configuration.telemetryAllowDebugBuilds = true` | `testMode: false` parameter in `initialize` | +| `import TelemetryClient` | `import TelemetryDeck` | diff --git a/articles/update-package.md b/docs/sdk/guides/update-package.md similarity index 95% rename from articles/update-package.md rename to docs/sdk/guides/update-package.md index b0ceba9..aa8edc2 100644 --- a/articles/update-package.md +++ b/docs/sdk/guides/update-package.md @@ -25,7 +25,7 @@ With that in mind, here's how to manually update a Swift package using Xcode: ## How to update a Swift Package in Xcode -![Screenshot of the left sidebar in Xcode 14, with the TelemetryDeck Swift Package highlighted](/docs/images/update_package.png) +![Screenshot of the left sidebar in Xcode 14, with the TelemetryDeck Swift Package highlighted](/assets/update_package.png) Updating packages in Xcode can be done using one of two ways. If you want to just update a single package individually – which we recommend by the way – do this: @@ -37,15 +37,13 @@ This will download the newest version of the package that is compatible with you Instead, you can also update every single Swift Package in your project at once. To do this, open the **Files** menu, navigate to **Packages** and then click **Update to Latest Package Versions**. Afterwards, make sure to test your project to see if everything still works, especially in projects with a lot of package dependencies. See further down for more troubleshooting tips. -{% callToAction "TelemetryDeck: Privacy-first analytics for Swift apps" "Make your app even more awesome" %} - ## Updating to the next major version of a Swift Package By default, Xcode and Swift Package Manager will not update the Major version of a package automatically. (The major version is the first number in the version string, e.g. `1.2.3` has a major version of `1`.) If you want to update to the next major version of a package, you can do this by manually changing the version requirement in the Xcode project. To do that, navigate to the project, select "Package Dependencies" and manually update the minimum version requirement to the next major version. -![Screenshot of the left sidebar in Xcode 14, with the TelemetryDeck Swift Package highlighted](/docs/images/update_next_major_version.png) +![Screenshot of the left sidebar in Xcode 14, with the TelemetryDeck Swift Package highlighted](/assets/update_next_major_version.png) For example, if you have a package that is currently at version `1.2.3`, and you want to update to version `2.0.0`, you can change the version requirement in the Xcode project to `2.0.0`. This will tell the Swift Package Manager to download the newest version of the package that is greater than `2.0.0`, but less than `3.0.0`. When a `2.0.1` comes out, you can update to that version by following the steps above. diff --git a/docs/sdk/index.md b/docs/sdk/index.md new file mode 100644 index 0000000..49582d3 --- /dev/null +++ b/docs/sdk/index.md @@ -0,0 +1,85 @@ +--- +icon: lucide/plug +title: Choose an SDK +tags: + - setup + - quickstart + - beginner + - how-to +--- + +## Official SDKs + +
+ +- :fontawesome-brands-swift:{ .lg .middle } __Swift Setup Guide__ + + --- + + Configure the TelemetryDeck SDK in your Swift application for iOS, macOS, watchOS, and tvOS + + [:fontawesome-solid-right-long: Getting started](official-sdk/swift/index.md) + +- :fontawesome-brands-android:{ .lg .middle } __Android Setup Guide__ + + --- + + Include the TelemetryDeck SDK in your Android application + + [:fontawesome-solid-right-long: Getting started](official-sdk/android-setup.md) + +- :fontawesome-brands-flutter:{ .lg .middle } __Flutter Setup Guide__ + + --- + + How to include the TelemetryDeck SDK in your Flutter and Dart app + + [:fontawesome-solid-right-long: Getting started](official-sdk/flutter-setup.md) + +- :fontawesome-brands-js:{ .lg .middle } __JavaScript Setup Guide__ + + --- + + How to include the TelemetryDeck SDK in your JavaScript application or web app + + [:fontawesome-solid-right-long: Getting started](official-sdk/javascript-setup.md) + +- :fontawesome-solid-globe:{ .lg .middle } __Web Setup Guide__ + + --- + + How to include the TelemetryDeck SDK in your website or blog + + [:fontawesome-solid-right-long: Getting started](official-sdk/web-setup.md) + +- :fontawesome-solid-file-code:{ .lg .middle } __Objective-C__ + + --- + + How to include the TelemetryDeck Swift SDK with Objective-C interop + + [:fontawesome-solid-right-long: Getting started](official-sdk/objective-c-setup.md) + +
+ +## Community SDKs + +
+ +- :fontawesome-brands-react:{ .lg .middle } __React Native Setup Guide__ + + --- + + Include the TelemetryDeck SDK in your React or React Native app + + [:fontawesome-solid-right-long: Getting started](community-sdk/react-setup.md) + +- :fontawesome-brands-vuejs:{ .lg .middle } __Vue.js Setup Guide__ + + --- + + How to include the TelemetryDeck SDK in your Vue.js app + + [:fontawesome-solid-right-long: Getting started](community-sdk/vue-setup.md) + +
\ No newline at end of file diff --git a/guides/android-setup.md b/docs/sdk/official-sdk/android-setup.md similarity index 53% rename from guides/android-setup.md rename to docs/sdk/official-sdk/android-setup.md index 3700edb..bcbb922 100644 --- a/guides/android-setup.md +++ b/docs/sdk/official-sdk/android-setup.md @@ -1,15 +1,11 @@ --- +icon: fontawesome/brands/android title: Android Setup Guide tags: - - Setup - - Quickstart - - Code - - Kotlin -testedOn: Android Studio Meerkat -featured: true -description: Include the TelemetryDeck SDK in Your Android Application -lead: You have an account, and you have an app ID. Now let's include the TelemetryClient Kotlin Package in your application. -order: 200 + - setup + - quickstart + - code + - kotlin --- ## Include the SDK @@ -53,23 +49,24 @@ Feel free to browse the [TelemetryDeck SDK's source code](https://github.com/Tel Build and run your app to verify that TelemetryDeck is properly integrated. The SDK automatically tracks user activity when the app starts or returns from the background. -{% notewarning "When running from Android Studio, you're sending test signals" %} -If your app is configured with a debug build type (i.e. the default build variant running from Android Studio), your signals will be tagged as **Test Signals**, meaning that you can easily filter them out later. You'll see them show up in the TelemetryDeck Dashboard when the **Test Mode** toggle under the tab bar is turned on. -{% endnotewarning %} +!!! warning "When running from Android Studio, you're sending test signals" -Open the TelemetryDeck Dashboard, navigate to "Explore > Recent Signals" and make sure "Test Mode" is enabled. You should see automatic signals appear after launching your app. + If your app is configured with a debug build type (i.e. the default build variant running from Android Studio), + your signals will be tagged as **Test Signals**, meaning that you can easily filter them out later. + You'll see them show up in the TelemetryDeck Dashboard when [**Test Mode**](../../articles/test-mode.md) is turned on. ---- - -{% noteinfo "Ready for basic insights" %} -Congratulations! With just the SDK integration you've completed, TelemetryDeck will automatically track user sessions, app launches, and device information. This basic setup provides valuable built-in insights without any additional code. +Open the TelemetryDeck Dashboard, navigate to **Explore -> Recent Signals** and make sure [Test Mode](../../articles/test-mode.md) is enabled. +You should see automatic signals appear after launching your app. +Congratulations! With just the SDK integration you've completed, TelemetryDeck will automatically track +user sessions, app launches, and device information. This basic setup provides valuable built-in insights without any additional code. You can now build and release your app. Once users start using it, your TelemetryDeck dashboard will begin showing data about user behavior, device types, and other key metrics. -{% endnoteinfo %} + +--- ## Privacy Policy -You don't need to update your privacy policy, [but we recommend you do it anyway](/docs/guides/privacy-faq/#do-i-need-to-add-telemetrydeck-to-my-privacy-policy%3F). +You don't need to update your privacy policy, [but we recommend you do it anyway](../../guides/privacy-faq). ## Enhancing your analytics (optional) @@ -95,11 +92,13 @@ You can also add a user identifier and parameters to your events: TelemetryDeck.signal("pizzaOrderConfirmed", myUser.emailAddress, mapOf("pizzaType" to "hawaii")) ``` -{% noteinfo "About Signal Data" %} -A user identifier is any string that uniquely identifies a user of your application. We will _hash_ this string before sending it to the server, and there we'll salt+hash it again for privacy protection. +!!! info "About Signal Data" -The payload is a map of parameters to be sent with the signal. We'll automatically send information about the user's device, system version, and app version with each signal, but you can add custom parameters either with each call or by using [enrich providers](https://github.com/TelemetryDeck/KotlinSDK#custom-telemetry). -{% endnoteinfo %} + A user identifier is any string that uniquely identifies a user of your application. + We will _hash_ this string before sending it to the server, and there we'll salt+hash it again for privacy protection. + The payload is a map of parameters to be sent with the signal. We'll automatically send information + about the user's device, system version, and app version with each signal, but you can add custom parameters + either with each call or by using [enrich providers](https://github.com/TelemetryDeck/KotlinSDK#custom-telemetry). For more advanced configuration options, programmatic usage and information about signals, parameters and all other aspects of the SDK, check out the [README file](https://github.com/TelemetryDeck/KotlinSDK?tab=readme-ov-file#programmatic-usage). @@ -115,21 +114,11 @@ The TelemetryDeck SDK requires Android SDK 21 or later. For a complete list of r Now that you've integrated TelemetryDeck, learn how to use the analytics platform to gain valuable insights about your users: -
-
-
- -
-

- - πŸ“Š Analytics Walkthrough -

-

Learn how to navigate TelemetryDeck, interpret insights, and use analytics to make data-driven decisions that improve your app and grow your user base.

-

- Start here to get real value from your analytics - β†’ -

-
-
-
+
+ +- **πŸ“Š Analytics Walkthrough** + + Learn how to navigate TelemetryDeck, interpret insights, and use analytics to make data-driven decisions that improve your app and grow your user base. + + [:fontawesome-solid-right-long: Start here to get real value from your analytics](../../basics/index.md)
diff --git a/guides/flutter-setup.md b/docs/sdk/official-sdk/flutter-setup.md similarity index 64% rename from guides/flutter-setup.md rename to docs/sdk/official-sdk/flutter-setup.md index e37266c..0912453 100644 --- a/guides/flutter-setup.md +++ b/docs/sdk/official-sdk/flutter-setup.md @@ -1,13 +1,10 @@ --- +icon: fontawesome/brands/flutter title: Flutter Setup Guide tags: - - Setup - - Flutter - - Dart -featured: true -description: How to include the TelemetryDeck SDK in Your Flutter and Dart app -lead: You have an account, and you have an app ID. Now let's include the TelemetryClient Package in your Flutter application -order: 225 + - setup + - flutter + - dart --- ## Prerequisites @@ -58,19 +55,18 @@ Set the `com.apple.security.network.client` entitlement to `true` in the `macos/ Build and run your app to verify that TelemetryDeck is properly integrated. The SDK automatically begins collecting data when initialized. -{% notewarning "When running from your IDE, you're sending test signals" %} -If your app is configured with a debug build type (i.e. the default build variant running from Android Studio), your signals will be tagged as **Test Signals**, meaning that you can easily filter them out later. You'll see them show up in the TelemetryDeck Dashboard when the **Test Mode** toggle under the tab bar is turned on. -{% endnotewarning %} +!!! warning "When running from your IDE, you're sending test signals" -Open the TelemetryDeck Dashboard, navigate to "Explore > Recent Signals" and make sure "Test Mode" is enabled. You should see automatic signals appear after launching your app. + If your app is configured with a debug build type (i.e. the default build variant running from Android Studio), + your signals will be tagged as **Test Signals**, meaning that you can easily filter them out later. + You'll see them show up in the TelemetryDeck Dashboard when [**Test Mode**](../../articles/test-mode.md) is turned on. ---- - -{% noteinfo "Ready for basic insights" %} -Congratulations! With just the SDK integration you've completed, TelemetryDeck will automatically track user sessions, app launches, and device information. This basic setup provides valuable built-in insights without any additional code. +Open the TelemetryDeck Dashboard, navigate to **Explore -> Recent Signals** and make sure [Test Mode](../../articles/test-mode.md) is enabled. +You should see automatic signals appear after launching your app. +Congratulations! With just the SDK integration you've completed, TelemetryDeck will automatically track +user sessions, app launches, and device information. This basic setup provides valuable built-in insights without any additional code. You can now build and release your app. Once users start using it, your TelemetryDeck dashboard will begin showing data about user behavior, device types, and other key metrics. -{% endnoteinfo %} ## Enhancing your analytics (optional) @@ -152,27 +148,17 @@ When publishing your Flutter app, you'll need to address privacy requirements: - **iOS Apps**: Disclose analytics usage in Apple's App Store Connect privacy details. TelemetryDeck is privacy-focused, but disclosure is still required. - **All Platforms**: Consider updating your privacy policy to mention analytics collection. -For guidance, see our [Apple App Privacy guide](/docs/articles/apple-app-privacy/) and [Privacy FAQ](/docs/guides/privacy-faq/#do-i-need-to-add-telemetrydeck-to-my-privacy-policy%3F). +For guidance, see our [Apple App Privacy guide](../../articles/apple-app-privacy.md) and [Privacy FAQ](../../guides/privacy-faq). ## What to do next Now that you've integrated TelemetryDeck, learn how to use the analytics platform to gain valuable insights about your users: -
-
-
- -
-

- - πŸ“Š Analytics Walkthrough -

-

Learn how to navigate TelemetryDeck, interpret insights, and use analytics to make data-driven decisions that improve your app and grow your user base.

-

- Start here to get real value from your analytics - β†’ -

-
-
-
+
+ +- **πŸ“Š Analytics Walkthrough** + + Learn how to navigate TelemetryDeck, interpret insights, and use analytics to make data-driven decisions that improve your app and grow your user base. + + [:fontawesome-solid-right-long: Start here to get real value from your analytics](../../basics/index.md)
diff --git a/guides/javascript-setup.md b/docs/sdk/official-sdk/javascript-setup.md similarity index 64% rename from guides/javascript-setup.md rename to docs/sdk/official-sdk/javascript-setup.md index e896f9b..0b20638 100644 --- a/guides/javascript-setup.md +++ b/docs/sdk/official-sdk/javascript-setup.md @@ -1,30 +1,25 @@ --- +icon: fontawesome/brands/js title: JavaScript App Setup Guide tags: - - Setup - - JavaScript - - Node - - NPM - - TypeScript -featured: true -description: How to include the TelemetryDeck SDK in Your JavaScript Application or Web App -lead: You have an account, and you have an app ID. Now let's include the TelemetryClient Package in your JS application or web app -order: 300 + - setup + - javascript + - node + - npm + - typescript --- The TelemetryDeck SDK has no dependencies and supports **modern evergreen browsers** and **modern versions of Node.js** with support for [cryptography](https://caniuse.com/cryptography). -{% noteinfo "There are multiple ways of adding TelemetryDeck to a web site" %} +!!! warning "There are multiple ways of adding TelemetryDeck to a web site" -There are different tutorials you should read depending on your use case. + There are different tutorials you should read depending on your use case. -- The [TelemetryDeck Web SDK](/docs/guides/web-setup) is a quick and easy way to include web analytics into your website. This is fantastic for blogs, landing pages, static websites, and content-driven websites. - -- If you are building a JavaScript application – a Progressive Web App written in React, Vue, Angular, Svelte, Ember, or mobile or desktop apps written with React Native, Electron, Ionic, and so on, you should read this guide. - -[Our blog post](/blog/js-sdk-2-0/) explains the differences between the two SDKs in more detail. - -{% endnoteinfo %} + - The [TelemetryDeck Web SDK](web-setup.md) is a quick and easy way to include web analytics into your website. This is fantastic for blogs, landing pages, static websites, and content-driven websites. + + - If you are building a JavaScript application – a Progressive Web App written in React, Vue, Angular, Svelte, Ember, or mobile or desktop apps written with React Native, Electron, Ionic, and so on, you should read this guide. + + [Our blog post](https://telemetrydeck.com/blog/js-sdk-2-0) explains the differences between the two SDKs in more detail. ## Set up @@ -87,14 +82,11 @@ If can't specify a user identifier at initialization, you can set it later by se Please note that `td.signal` is an async function that returns a promise. -{% notewarning "Special treatment for frameworks" %} - -Some frameworks, like Svelte, don't need `crypto` and node.js. Here are some tips on how to implement TelemetryDeck when using some of these special frameworks: +!!! warning "Special treatment for frameworks" -- The initialization should happen once, and the TD object should be passed around in a service or singleton. -- The `td.send` function should be used to send signals, either automatically in a router-like object or on a per-feature basis. - -{% endnotewarning %} + Some frameworks, like Svelte, don't need `crypto` and node.js. Here are some tips on how to implement TelemetryDeck when using some of these special frameworks: + - The initialization should happen once, and the TD object should be passed around in a service or singleton. + - The `td.send` function should be used to send signals, either automatically in a router-like object or on a per-feature basis. ### Advanced initialization options @@ -119,7 +111,7 @@ td.signal("Volume.Set", { ## Privacy Policy and Opt-Out -You don't need to update your privacy policy, [but we recommend you do it anyway](/docs/guides/privacy-faq/#do-i-need-to-add-telemetrydeck-to-my-privacy-policy%3F). +You don't need to update your privacy policy, [but we recommend you do it anyway](../../guides/privacy-faq). ## Advanced: Queueing Signals @@ -161,21 +153,11 @@ The default implementation can be found in `src/utils/store.js`. Now that you've integrated TelemetryDeck, learn how to use the analytics platform to gain valuable insights about your users: -
-
-
- -
-

- - πŸ“Š Analytics Walkthrough -

-

Learn how to navigate TelemetryDeck, interpret insights, and use analytics to make data-driven decisions that improve your app and grow your user base.

-

- Start here to get real value from your analytics - β†’ -

-
-
-
+
+ +- **πŸ“Š Analytics Walkthrough** + + Learn how to navigate TelemetryDeck, interpret insights, and use analytics to make data-driven decisions that improve your app and grow your user base. + + [:fontawesome-solid-right-long: Start here to get real value from your analytics](../../basics/index.md)
diff --git a/docs/sdk/official-sdk/objective-c-setup.md b/docs/sdk/official-sdk/objective-c-setup.md new file mode 100644 index 0000000..c171015 --- /dev/null +++ b/docs/sdk/official-sdk/objective-c-setup.md @@ -0,0 +1,89 @@ +--- +icon: fontawesome/solid/file-code +title: Objective-C Setup Guide +tags: + - setup + - ios + - macos + - objective c +--- + +Objective-C apps (or mixed Swift/ObjC projects) can use the TelemetryDeck Swift SDK through Objective-C interop. + +## Installing the package + +1. Open Xcode and navigate to your project +1. Select File β†’ Add Package Dependencies... +1. Paste `https://github.com/TelemetryDeck/SwiftSDK` into the search field +1. Select the `SwiftSDK` package +1. Set the Dependency Rule to Up to Next Major Version +1. Click Add Package + +## Linking the package + +In the Choose Package Products for SwiftSDK screen, select the `TelemetryDeck` library and click Add Package. + +!!! note "Multiple targets" + + If Xcode doesn't prompt you, add it manually: select your target β†’ Build Phases β†’ Link Binary With Libraries β†’ + β†’ select `TelemetryDeck`. + +## Initializing TelemetryDeck + +Add initialization to your App Delegate's `application:didFinishLaunchingWithOptions:`: + +```objc +@import TelemetryDeck; + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + + [TelemetryDeck initializeWithAppID:@"YOUR-APP-ID" + namespace:@"YOUR-NAMESPACE"]; + + return YES; +} +``` + +!!! note "You need your app's unique identifier and namespace" + + Both values are available in the [TelemetryDeck Dashboard](https://dashboard.telemetrydeck.com) under your app's settings. + +## Verify your setup + +Run your app. The SDK automatically sends a `TelemetryDeck.Session.started` event on launch. + +!!! warning "Test signals" + + Events from `DEBUG` builds are tagged as test signals. Enable **Test Mode** in the TelemetryDeck Dashboard to see them. + +Open the Dashboard β†’ "Explore > Recent Signals" with Test Mode enabled. + +--- + +## Sending custom events + +```objc +[TelemetryDeck event:@"pizzaModeActivated"]; +``` + +With parameters: + +```objc +[TelemetryDeck event:@"pizzaModeActivated" + parameters:@{@"cheeseMode": @"extraCheesy"}]; +``` + +## App Store requirements + +See our [Apple App Privacy guide](/articles/apple-app-privacy) and [Privacy FAQ](../../guides/privacy-faq). + +## What's next + +
+ +- **πŸ“Š Analytics Walkthrough** + + Learn how to navigate TelemetryDeck, interpret insights, and use analytics to make data-driven decisions that improve your app and grow your user base. + + [:fontawesome-solid-right-long: Start here to get real value from your analytics](../../basics/index.md) +
diff --git a/docs/sdk/official-sdk/swift/index.md b/docs/sdk/official-sdk/swift/index.md new file mode 100644 index 0000000..39890aa --- /dev/null +++ b/docs/sdk/official-sdk/swift/index.md @@ -0,0 +1,227 @@ +--- +icon: fontawesome/brands/swift +title: Swift Setup Guide +tags: + - Setup + - iOS + - macOS + - watchOS + - tvOS + - visionOS +--- + +## Prerequisites + +- A TelemetryDeck account. [Sign up here](https://dashboard.telemetrydeck.com/register) if you haven't yet. +- Your app's **unique identifier** and **namespace** from the [TelemetryDeck Dashboard](https://dashboard.telemetrydeck.com). +- Xcode 26 or later, Swift 6.2 or later. +- Minimum deployment targets: iOS 15, macOS 12, watchOS 8, tvOS 15, or visionOS 1. + +## Installing the Swift Package + +The TelemetryDeck Swift SDK is distributed via Swift Package Manager. + +1. Open your project in Xcode +1. Select File β†’ Add Package Dependencies... +1. Paste `https://github.com/TelemetryDeck/SwiftSDK` into the search field +1. Select the `SwiftSDK` package that appears in the list +1. Set the Dependency Rule to Up to Next Major Version +1. Click Add Package + +![A screenshot of Xcode adding the TelemetryDeck Package](/assets/xcode-swift-package1.png) + +## Linking the package + +Xcode will ask you to link the package with your target. In the screen titled Choose Package Products for SwiftSDK, set the Add to target column to your app target for TelemetryDeck and click Add Package. + +![A screenshot of Xcode setting the target for the TelemetryDeck library](/assets/xcode-swift-package2.png) + +!!! note "Multiple targets" + + If Xcode doesn't prompt you, add it manually: select your target β†’ Build Phases β†’ Link Binary With Libraries β†’ + β†’ select `TelemetryDeck`. + +## Initializing TelemetryDeck + +Initialize TelemetryDeck as early as possible in your app's lifecycle. Events sent before initialization completes are buffered and delivered automatically once it's ready. + +!!! note "You need your app's unique identifier and namespace" + + Both values are available in the [TelemetryDeck Dashboard](https://dashboard.telemetrydeck.com) under your app's settings. + +Pick whichever lifecycle approach your app uses β€” SwiftUI or AppDelegate. + +### SwiftUI + +```swift +import SwiftUI +import TelemetryDeck + +@main +struct YourApp: App { + init() { + Task { + try? await TelemetryDeck.initialize( + appID: "YOUR-APP-ID", + namespace: "YOUR-NAMESPACE" + ) + } + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} +``` + +### AppDelegate + +```swift +import UIKit +import TelemetryDeck + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + Task { + try? await TelemetryDeck.initialize( + appID: "YOUR-APP-ID", + namespace: "YOUR-NAMESPACE" + ) + } + return true + } +} +``` + +!!! warning "Test mode" + + When running from Xcode in `DEBUG` configuration, events are automatically tagged as **test signals**. Enable **Test Mode** in the TelemetryDeck Dashboard to see them. + +## Verify your setup + +Run your app. The SDK sends a `TelemetryDeck.Session.started` event automatically on launch. Open the TelemetryDeck Dashboard β†’ "Explore > Recent Signals" with Test Mode enabled β€” you should see it appear. + +That's it. Your app is sending analytics. You can ship this and start getting insights from real users. + +--- + +## Sending custom events + +Built-in session tracking covers the basics. Custom events let you answer app-specific questions: which features are popular, where users drop off, what settings they prefer. + +```swift +await TelemetryDeck.event("Oven.startBaking") +``` + +Add parameters for more context: + +```swift +await TelemetryDeck.event( + "Oven.startBaking", + parameters: [ + "pizzaMode": dataStore.pizzaMode.isActive, + "temperature": dataStore.targetTemperature + ] +) +``` + +Parameters accept `String`, `Bool`, `Int`, `Double`, `UUID`, and `Date` values. They're transmitted as native JSON types. + +!!! note "Privacy" + + The `customUserID` value is SHA256-hashed before transmission. Parameter values are **not** hashed β€” don't include personally identifiable information in parameters. + +## User identification + +TelemetryDeck auto-generates an anonymous user identifier per installation. If you have your own user identity (email, account ID), set it after login: + +```swift +await TelemetryDeck.setUserIdentifier("user@example.com") +``` + +The identifier is hashed before it leaves the device. For details on how the default identifier is resolved per platform, cross-device tracking, and server-side usage, see [User Identification](swift-user-identification.md). + +## Configuration options + +The `initialize` function accepts several optional parameters: + +```swift +try await TelemetryDeck.initialize( + appID: "YOUR-APP-ID", + namespace: "YOUR-NAMESPACE", + salt: "optional-extra-hash-salt", + defaultUser: "optional-default-user", + testMode: nil, + eventPrefix: "MyApp.", + parameterPrefix: "MyApp.", + defaultParameters: [ + "theme": UserDefaults.standard.string(forKey: "theme") ?? "default", + "tier": isPremium ? "premium" : "free" + ] +) +``` + +| Parameter | Description | +|---|---| +| `salt` | Extra string mixed into user ID hashing for additional privacy | +| `defaultUser` | User identifier set at startup | +| `testMode` | Override automatic test mode detection (`true`/`false`/`nil` for auto) | +| `eventPrefix` | Automatically prepended to all event names | +| `parameterPrefix` | Automatically prepended to all custom parameter keys | +| `sendSessionStartedEvent` | Whether to fire `TelemetryDeck.Session.started` on each new session (default: `true`) | +| `defaultParameters` | Key-value pairs included with every event | + +## Disabling analytics + +Let users opt out: + +```swift +await TelemetryDeck.setAnalyticsDisabled(true) +``` + +Check the current state: + +```swift +let disabled = await TelemetryDeck.isAnalyticsDisabled +``` + +## Shutting down + +If you need to cleanly shut down the SDK (for example before an app extension terminates): + +```swift +await TelemetryDeck.terminate() +``` + +This flushes pending events, persists the cache, and stops all processors. + +## App Store requirements + +Apple requires you to disclose analytics usage in App Store Connect, even for privacy-focused tools like TelemetryDeck. See our [Apple App Privacy guide](/articles/apple-app-privacy) and [Privacy FAQ](/guides/privacy-faq#do-i-need-to-add-telemetrydeck-to-my-privacy-policy%3F). + +## What's next + +
+ +- **[Processors](swift-processors.md)** + + Learn how events flow through the processor pipeline and what data is automatically collected. + +- **[Custom Processors](swift-custom-processors)** + + Build your own processors to add domain-specific metadata to every event. + +- **[Analytics Walkthrough](../../../basics/index.md)** + + Navigate TelemetryDeck, interpret insights, and make data-driven decisions. + +- **[Migrating from V2](swift-migration-v3)** + + Upgrading from SwiftSDK 2.x? Here's what changed. + +
diff --git a/docs/sdk/official-sdk/swift/swift-custom-processors.md b/docs/sdk/official-sdk/swift/swift-custom-processors.md new file mode 100644 index 0000000..98e12cd --- /dev/null +++ b/docs/sdk/official-sdk/swift/swift-custom-processors.md @@ -0,0 +1,242 @@ +--- +title: Writing Custom Processors +tags: + - Swift + - SDK + - processors + - advanced +testedOn: SwiftSDK 3.0.0 +description: Build your own EventProcessor to enrich, filter, or transform events in the TelemetryDeck Swift SDK pipeline. +lead: Add domain-specific metadata to every event by writing your own processor and plugging it into the pipeline. +--- + +## The EventProcessor protocol + +A processor is any `Sendable` type that conforms to `EventProcessor`: + +```swift +public protocol EventProcessor: Sendable { + func process( + _ input: EventInput, + context: EventContext, + next: @Sendable (EventInput, EventContext) async throws -> Event + ) async throws -> Event + + func start( + storage: any ProcessorStorage, + logger: any Logging, + emitter: any EventSending + ) async + + func stop() async +} +``` + +Only `process` is required. The `start` and `stop` methods have default no-op implementations. + +## A minimal processor + +Here's a processor that adds the current A/B test variant to every event: + +```swift +struct ABTestProcessor: EventProcessor { + let variant: String + + func process( + _ input: EventInput, + context: EventContext, + next: @Sendable (EventInput, EventContext) async throws -> Event + ) async throws -> Event { + var ctx = context + ctx.addParameter("ABTest.variant", value: variant) + return try await next(input, ctx) + } +} +``` + +Key points: + +- Mutate `context` to add metadata parameters. These are merged with the event at finalization. +- Mutate `input` to change the event name, parameters, or float value. +- Always call `next(input, context)` to pass the event down the pipeline β€” unless you want to drop it. +- Input parameters override context parameters when keys collide. + +## Registering your processor + +Pass your processors when initializing TelemetryDeck. Use `defaultProcessors()` to keep the built-in ones: + +```swift +try await TelemetryDeck.initialize( + configuration: TelemetryDeck.Config( + appID: "YOUR-APP-ID", + namespace: "YOUR-NAMESPACE" + ), + processors: TelemetryDeck.defaultProcessors() + [ + ABTestProcessor(variant: "B") + ] +) +``` + +Your processor runs after all default processors. To insert it at a specific position, build the array manually: + +```swift +var processors = TelemetryDeck.defaultProcessors() +processors.insert(ABTestProcessor(variant: "B"), at: 0) + +try await TelemetryDeck.initialize( + configuration: config, + processors: processors +) +``` + +## Filtering events + +Drop events by throwing `ProcessorError.eventFiltered`: + +```swift +struct InternalScreenFilter: EventProcessor { + func process( + _ input: EventInput, + context: EventContext, + next: @Sendable (EventInput, EventContext) async throws -> Event + ) async throws -> Event { + if input.name.hasPrefix("Internal.") { + throw ProcessorError.eventFiltered + } + return try await next(input, context) + } +} +``` + +Filtered events are silently discarded. + +## Transforming events + +Modify the event name or parameters before passing them on: + +```swift +struct ScreenNameSanitizer: EventProcessor { + func process( + _ input: EventInput, + context: EventContext, + next: @Sendable (EventInput, EventContext) async throws -> Event + ) async throws -> Event { + var modified = input + modified.name = modified.name + .replacingOccurrences(of: " ", with: ".") + return try await next(modified, context) + } +} +``` + +## Using persistent storage + +Processors that need to persist state across app launches get access to `ProcessorStorage` in their `start` method: + +```swift +actor SubscriptionTierProcessor: EventProcessor { + private var storage: (any ProcessorStorage)? + private var tier: String = "free" + + func start( + storage: any ProcessorStorage, + logger: any Logging, + emitter: any EventSending + ) async { + self.storage = storage + if let saved = await storage.string(forKey: "subscriptionTier") { + tier = saved + } + } + + func updateTier(_ newTier: String) async { + tier = newTier + await storage?.set(newTier, forKey: "subscriptionTier") + } + + func process( + _ input: EventInput, + context: EventContext, + next: @Sendable (EventInput, EventContext) async throws -> Event + ) async throws -> Event { + var ctx = context + ctx.addParameter("Subscription.tier", value: tier) + return try await next(input, ctx) + } +} +``` + +`ProcessorStorage` supports `Data`, `String`, `Int`, and `Bool` types. The default implementation backs onto `UserDefaults` with a suite scoped to your app ID. + +## Emitting events from a processor + +The `emitter` parameter in `start` lets a processor send its own events back into the pipeline β€” for example, system-level events the processor generates independently: + +```swift +actor WatchdogProcessor: EventProcessor { + private var emitter: (any EventSending)? + + func start( + storage: any ProcessorStorage, + logger: any Logging, + emitter: any EventSending + ) async { + self.emitter = emitter + } + + func reportAnomaly(_ description: String) async { + await emitter?.send(EventInput( + name: "Watchdog.anomalyDetected", + parameters: ["description": description] + )) + } + + func process( + _ input: EventInput, + context: EventContext, + next: @Sendable (EventInput, EventContext) async throws -> Event + ) async throws -> Event { + return try await next(input, context) + } +} +``` + +Events sent via `emitter` go through the entire pipeline, including your processor. + +## Replacing built-in processors + +If a built-in processor doesn't fit your needs, leave it out of the pipeline and supply your own. For example, to replace the session tracking logic: + +```swift +try await TelemetryDeck.initialize( + configuration: config, + processors: TelemetryDeck.defaultProcessors() + .filter { $0 is SessionTrackingProcessor == false } + + [MyCustomSessionProcessor()] +) +``` + +!!! warning + + If you remove built-in processors, features that depend on them (like retention metrics or test mode detection) will stop working. Only do this when you have a specific reason. + +## Testing with SpyEventTransmitter + +The SDK provides `SpyEventTransmitter` and `InMemoryEventCache` for testing. Inject them to capture events without hitting the network: + +```swift +let spy = SpyEventTransmitter() + +try await TelemetryDeck.initialize( + configuration: config, + processors: [MyProcessor()], + cache: InMemoryEventCache(), + transmitter: spy +) + +await TelemetryDeck.event("test.event") +await TelemetryDeck.flush() + +let transmitted = await spy.transmittedEvents +assert(transmitted.contains { $0.type == "test.event" }) +``` diff --git a/docs/sdk/official-sdk/swift/swift-migration-v3.md b/docs/sdk/official-sdk/swift/swift-migration-v3.md new file mode 100644 index 0000000..e894554 --- /dev/null +++ b/docs/sdk/official-sdk/swift/swift-migration-v3.md @@ -0,0 +1,210 @@ +--- +title: Migrating from SwiftSDK 2.x to 3.0 +tags: + - Swift + - SDK + - migration +testedOn: SwiftSDK 3.0.0 +description: What changed in TelemetryDeck SwiftSDK 3.0 and how to update your code. +lead: SwiftSDK 3.0 replaces providers with processors, requires a namespace, and makes the API async. Here's how to migrate. +order: 200 +--- + +## Platform requirements + +SwiftSDK 3.0 raises minimum deployment targets: + +| Platform | V2 | V3 | +|---|---|---| +| iOS | 12 | 15 | +| macOS | 10.13 | 12 | +| watchOS | 5 | 8 | +| tvOS | 13 | 15 | +| visionOS | 1 | 1 | + +Swift 6.2 and Xcode 26 are required. + +## Breaking changes + +### Namespace is required + +`TelemetryDeck.Config` now requires a `namespace` parameter. You'll find your namespace in the TelemetryDeck Dashboard alongside your app ID. + +=== "Before (V2)" + + ```swift + let config = TelemetryDeck.Config(appID: "YOUR-APP-ID") + TelemetryDeck.initialize(config: config) + ``` + +=== "After (V3)" + + ```swift + try await TelemetryDeck.initialize( + appID: "YOUR-APP-ID", + namespace: "YOUR-NAMESPACE" + ) + ``` + +### Initialization is async + +`initialize` is now `async throws`. Wrap it in a `Task` when calling from synchronous contexts like `init()`: + +```swift +init() { + Task { + try? await TelemetryDeck.initialize( + appID: "YOUR-APP-ID", + namespace: "YOUR-NAMESPACE" + ) + } +} +``` + +Events sent before initialization completes are buffered and delivered automatically. + +### signal() β†’ event() + +The `signal` function is deprecated. Replace it with `event`: + +=== "Before (V2)" + + ```swift + TelemetryDeck.signal("buttonTapped") + TelemetryDeck.signal("purchase", parameters: ["item": "widget"]) + ``` + +=== "After (V3)" + + ```swift + await TelemetryDeck.event("buttonTapped") + await TelemetryDeck.event("purchase", parameters: ["item": "widget"]) + ``` + +### Event methods are async + +All event-sending methods now require `await`: + +```swift +await TelemetryDeck.event("myEvent") +await TelemetryDeck.navigationPathChanged(from: "home", to: "settings") +await TelemetryDeck.errorOccurred(id: "ParseError") +await TelemetryDeck.purchaseCompleted(transaction: transaction) +await TelemetryDeck.setUserIdentifier("user@example.com") +``` + +In SwiftUI view bodies and `@MainActor` contexts, wrap calls in a `Task`: + +```swift +Button("Tap") { + Task { + await TelemetryDeck.event("buttonTapped") + } +} +``` + +### Configuration properties moved to initialize parameters + +Properties that were set on `TelemetryDeck.Config` in V2 are now parameters of the `initialize` function: + +=== "Before (V2)" + + ```swift + let config = TelemetryDeck.Config(appID: "YOUR-APP-ID") + config.defaultSignalPrefix = "App." + config.defaultParameterPrefix = "MyApp." + config.defaultParameters = {["theme": "dark"]} + TelemetryDeck.initialize(config: config) + ``` + +=== "After (V3)" + + ```swift + try await TelemetryDeck.initialize( + appID: "YOUR-APP-ID", + namespace: "YOUR-NAMESPACE", + eventPrefix: "App.", + parameterPrefix: "MyApp.", + defaultParameters: ["theme": "dark"] + ) + ``` + +### Typed parameters + +Parameters are no longer `[String: String]`. The new `EventParameters` type accepts `String`, `Bool`, `Int`, `Double`, `UUID`, and `Date` values: + +=== "Before (V2)" + + ```swift + TelemetryDeck.signal("event", parameters: [ + "count": "\(items.count)", + "premium": isPremium ? "true" : "false" + ]) + ``` + +=== "After (V3)" + + ```swift + await TelemetryDeck.event("event", parameters: [ + "count": items.count, + "premium": isPremium + ]) + ``` + +The old `[String: String]` overloads still compile but are deprecated. + +### Duration methods renamed + +=== "Before (V2)" + + ```swift + TelemetryDeck.startDurationSignal("loading") + TelemetryDeck.stopAndSendDurationSignal("loading") + TelemetryDeck.cancelDurationSignal("loading") + ``` + +=== "After (V3)" + + ```swift + await TelemetryDeck.startDurationEvent("loading") + await TelemetryDeck.stopAndSendDurationEvent("loading") + await TelemetryDeck.cancelDurationEvent("loading") + ``` + +### updateDefaultUserID β†’ setUserIdentifier + +=== "Before (V2)" + + ```swift + TelemetryDeck.updateDefaultUserID(to: "user@example.com") + ``` + +=== "After (V3)" + + ```swift + await TelemetryDeck.setUserIdentifier("user@example.com") + ``` + +## Removed APIs + +| Removed | Replacement | +|---|---| +| `TelemetryManager.shared` | Use static methods on `TelemetryDeck` | +| `TelemetryClient` (typealias) | `TelemetryDeck` | +| `TelemetryManagerConfiguration` | `TelemetryDeck.Config` or `initialize(appID:namespace:...)` | +| `Provider` protocol | `EventProcessor` protocol | +| `configuration.telemetryAllowDebugBuilds` | `testMode` parameter in `initialize` | + +## Providers β†’ Processors + +The V2 provider system (`register`, `stop`, `enrich`, `transform`) is replaced by the `EventProcessor` protocol. The new model is a middleware chain where each processor calls `next` to pass control downstream. + +If you wrote custom providers, see [Writing Custom Processors](swift-custom-processors) for the new approach. + +## Data migration + +The SDK includes an automatic `V2DataMigrator` that converts V2 session data (stored in UserDefaults) to the V3 format on first launch. No action needed β€” your users' session continuity and retention metrics are preserved. + +## Payload format + +Event payloads are now typed JSON. Values are sent as native `bool`, `int`, `double`, or `string`. V2 sent everything as strings. If you have server-side code parsing payloads, update it to handle typed values. diff --git a/docs/sdk/official-sdk/swift/swift-processors.md b/docs/sdk/official-sdk/swift/swift-processors.md new file mode 100644 index 0000000..2176ed4 --- /dev/null +++ b/docs/sdk/official-sdk/swift/swift-processors.md @@ -0,0 +1,188 @@ +--- +title: How Events Flow Through Processors +tags: + - Swift + - SDK + - processors +testedOn: SwiftSDK 3.0.0 +description: Understand how the TelemetryDeck Swift SDK processes events through its middleware pipeline before transmission. +lead: Every event passes through a chain of processors that enrich it with device info, session data, and retention metrics before it reaches the server. +--- + +## The processor pipeline + +When you call `TelemetryDeck.event(...)`, the event passes through a pipeline of **processors** before transmission. Each processor can inspect, enrich, transform, or filter the event. + +```mermaid +graph TD + A["TelemetryDeck.event(...)"] --> B[PreviewFilter] + B --> C[DefaultParameters] + C --> D[DefaultPrefix] + D --> E[Validation] + E --> F[TestMode] + F --> G[UserIdentifier] + G --> H[SessionTracking] + H --> I[Device] + I --> J[AppInfo] + J --> K[Locale] + K --> L[Calendar] + L --> M[Accessibility] + M --> N[TrialConversion] + N --> O[Finalizer] + O --> P[Event Cache] + P --> Q[HTTP Transmitter] +``` + +Each processor receives two things: + +- **EventInput** β€” the event name, parameters, and optional float value +- **EventContext** β€” mutable metadata that accumulates as the event moves through the pipeline (session ID, user identifier, test mode flag, and additional parameters) + +A processor can modify either of these, then call `next` to pass control to the next processor. It can also throw `ProcessorError.eventFiltered` to silently drop the event. + +At the end of the pipeline, the **finalizer** merges everything into a single `Event` object, hashes the user identifier with SHA256, and hands it to the cache for transmission. + +## Built-in processors + +The SDK ships with a default set of processors. They run in this order: + +### PreviewFilterProcessor + +Detects Xcode SwiftUI previews and drops all events during preview rendering. You never see preview noise in your dashboard. + +### DefaultParametersProcessor + +Injects the `defaultParameters` you provided at initialization. These appear on every event. Your per-event parameters override them if keys collide. + +### DefaultPrefixProcessor + +Prepends the `eventPrefix` to event names and `parameterPrefix` to custom parameter keys. SDK-internal parameters (prefixed with `TelemetryDeck.`) are not affected. + +For example, with `eventPrefix: "MyApp."`, calling `event("login")` produces `MyApp.login`. + +### ValidationProcessor + +Warns in the console if your event names or parameter keys collide with reserved `TelemetryDeck.*` names. Events pass through unchanged. + +### TestModeProcessor + +Determines whether the event should be marked as test data. In `DEBUG` builds, test mode is on by default. You can override this with the `testMode` parameter during initialization. + +### UserIdentifierProcessor + +Attaches a user identifier to the event context. The SDK auto-resolves a default identifier per platform (IDFV on iOS, a persisted UUID on macOS). You can override it with `TelemetryDeck.setUserIdentifier(_:)` or pass a `customUserID` per event. See [User Identification](swift-user-identification.md) for full details on resolution order, platform behavior, and hashing. + +### SessionTrackingProcessor + +Manages session lifecycle and retention metrics. It: + +- Generates a session ID at startup +- Starts a new session when the app returns from background after 5+ minutes +- Fires `TelemetryDeck.Session.started` on each new session (configurable) +- Detects first-ever launch and fires `TelemetryDeck.Acquisition.newInstallDetected` +- Adds retention parameters to every event: + +| Parameter | Type | Description | +|---|---|---| +| `TelemetryDeck.Retention.totalSessionsCount` | Int | Total sessions across all app launches | +| `TelemetryDeck.Retention.distinctDaysUsed` | Int | Unique calendar days the app was used | +| `TelemetryDeck.Retention.distinctDaysUsedLastMonth` | Int | Unique days used in the last 30 days | +| `TelemetryDeck.Retention.averageSessionSeconds` | Int | Average session length (-1 if only one session) | +| `TelemetryDeck.Retention.previousSessionSeconds` | Int | Length of the previous session (if β‰₯2 sessions) | +| `TelemetryDeck.Acquisition.firstSessionDate` | String | ISO 8601 date of first session | +| `TelemetryDeck.Acquisition.isNewInstall` | Bool | `true` only on the first event of the first session | + +Session history is persisted for 90 days. Older sessions are counted but their details are pruned. + +### DeviceProcessor + +Adds hardware and platform information: + +| Parameter | Example | +|---|---| +| `TelemetryDeck.Device.platform` | `iOS` | +| `TelemetryDeck.Device.operatingSystem` | `iOS` | +| `TelemetryDeck.Device.systemVersion` | `iOS 18.0.0` | +| `TelemetryDeck.Device.systemMajorVersion` | `iOS 18` | +| `TelemetryDeck.Device.systemMajorMinorVersion` | `iOS 18.0` | +| `TelemetryDeck.Device.modelName` | `iPhone16,2` | +| `TelemetryDeck.Device.architecture` | `iPhone16,2` (iOS) / `arm64` (macOS) | +| `TelemetryDeck.Device.timeZone` | `UTC+2` | +| `TelemetryDeck.RunContext.isSimulator` | `false` | +| `TelemetryDeck.RunContext.isDebug` | `true` | +| `TelemetryDeck.RunContext.isTestFlight` | `false` | +| `TelemetryDeck.RunContext.isAppStore` | `false` | +| `TelemetryDeck.RunContext.targetEnvironment` | `native` | + +### AppInfoProcessor + +Adds app and SDK version information: + +| Parameter | Example | +|---|---| +| `TelemetryDeck.AppInfo.version` | `2.1.0` | +| `TelemetryDeck.AppInfo.buildNumber` | `42` | +| `TelemetryDeck.AppInfo.versionAndBuildNumber` | `2.1.0 (build 42)` | +| `TelemetryDeck.SDK.name` | `SwiftSDK` | +| `TelemetryDeck.SDK.version` | `3.0.0` | +| `TelemetryDeck.SDK.nameAndVersion` | `SwiftSDK 3.0.0` | + +### LocaleProcessor + +Adds language and region preferences: + +| Parameter | Example | +|---|---| +| `TelemetryDeck.RunContext.locale` | `en_US` | +| `TelemetryDeck.RunContext.language` | `en` | +| `TelemetryDeck.UserPreference.language` | `de` | +| `TelemetryDeck.UserPreference.region` | `US` | + +### CalendarProcessor + +Adds temporal context for time-based analysis: + +| Parameter | Example | +|---|---| +| `TelemetryDeck.Calendar.dayOfMonth` | `15` | +| `TelemetryDeck.Calendar.dayOfWeek` | `3` | +| `TelemetryDeck.Calendar.dayOfYear` | `166` | +| `TelemetryDeck.Calendar.weekOfYear` | `24` | +| `TelemetryDeck.Calendar.isWeekend` | `false` | +| `TelemetryDeck.Calendar.monthOfYear` | `6` | +| `TelemetryDeck.Calendar.quarterOfYear` | `2` | +| `TelemetryDeck.Calendar.hourOfDay` | `14` | + +### AccessibilityProcessor + +Captures accessibility settings and display properties. Results are cached for 1 hour to avoid excessive main-thread access. + +| Parameter | Example | +|---|---| +| `TelemetryDeck.Accessibility.isBoldTextEnabled` | `false` | +| `TelemetryDeck.Accessibility.isReduceMotionEnabled` | `false` | +| `TelemetryDeck.Accessibility.preferredContentSizeCategory` | `L` | +| `TelemetryDeck.UserPreference.colorScheme` | `Dark` | +| `TelemetryDeck.UserPreference.layoutDirection` | `leftToRight` | +| `TelemetryDeck.Device.screenResolutionWidth` | `430.0` | +| `TelemetryDeck.Device.screenResolutionHeight` | `932.0` | + +### TrialConversionProcessor + +Available on iOS 15, macCatalyst 15, macOS 12, tvOS 15, and watchOS 8. Monitors StoreKit `Transaction.updates` in the background and automatically fires `TelemetryDeck.Purchase.convertedFromTrial` when a user transitions from a free trial to a paid subscription. No setup required β€” it runs silently. + +## Event caching and transmission + +After processing, events land in a persistent cache (up to 10,000 events). The transmitter sends batched events every 10 seconds to `{apiBaseURL}/v2/namespace/{namespace}/`. + +If transmission fails, the SDK retries with exponential backoff (doubling the interval, capped at 5 minutes). Events that receive permanent HTTP errors (400, 401, 403, 404, 413, 422, 501, 505) are dropped immediately. + +You can force an immediate flush: + +```swift +await TelemetryDeck.flush() +``` + +## Pre-initialization buffering + +Events sent before `initialize()` completes are buffered in memory and replayed once initialization finishes. Event ordering is preserved. diff --git a/docs/sdk/official-sdk/swift/swift-user-identification.md b/docs/sdk/official-sdk/swift/swift-user-identification.md new file mode 100644 index 0000000..068ba6c --- /dev/null +++ b/docs/sdk/official-sdk/swift/swift-user-identification.md @@ -0,0 +1,87 @@ +--- +title: User Identification +tags: + - Swift + - SDK + - advanced +testedOn: SwiftSDK 3.0.0 +description: How the TelemetryDeck Swift SDK resolves, hashes, and transmits user identifiers across Apple platforms and Linux. +lead: The SDK assigns each user a stable, anonymous identifier. Here's how it resolves that identifier on each platform, how you can override it, and how it's hashed before transmission. +--- + +## Resolution order + +When an event is processed, the SDK picks the user identifier from the first available source: + +1. **Per-event override** β€” the `customUserID` parameter on `TelemetryDeck.event(...)` +2. **Explicit identifier** β€” set at runtime via `TelemetryDeck.setUserIdentifier(_:)` +3. **Default identifier** β€” from the `defaultUser` initialization parameter, or auto-resolved from the platform + +If you never call `setUserIdentifier` and don't pass `defaultUser` at init, the SDK resolves a default automatically. + +## Platform defaults + +| Platform | Source | +|---|---| +| iOS, tvOS, visionOS | [`UIDevice.identifierForVendor`](https://developer.apple.com/documentation/uikit/uidevice/identifierforvendor) | +| watchOS | `WKInterfaceDevice.identifierForVendor` | +| macOS | Random UUID, generated once and persisted in `UserDefaults` | +| Linux / server | Static string derived from platform and app version | + +### identifierForVendor (iOS, tvOS, visionOS, watchOS) + +On Apple's mobile and wearable platforms, the SDK uses `identifierForVendor` (IDFV). This is a UUID assigned by the OS that: + +- Stays stable across launches and app updates +- Is shared across all apps from the same vendor on a given device +- Resets when the user uninstalls **all** of that vendor's apps from the device +- Differs between TestFlight and the App Store, even for the same build β€” a tester's IDFV in TestFlight will not match their IDFV after installing from the App Store + +See Apple's [identifierForVendor documentation](https://developer.apple.com/documentation/uikit/uidevice/identifierforvendor) for the full lifecycle rules, including edge cases around app groups and enterprise distribution. + +If `identifierForVendor` returns `nil` β€” which can happen before the device is first unlocked after a restart β€” the SDK falls back to a generic string based on platform and app version. Different users on the same app version temporarily share this identifier until IDFV becomes available. The identifier is resolved once at startup with no retry. + +### macOS + +macOS has no `identifierForVendor`. On first launch, the SDK generates a random UUID and stores it in a `UserDefaults` suite scoped to your app ID. Subsequent launches read the same UUID. Deleting the app's UserDefaults data resets the identifier. + +### Linux and server-side Swift + +The SDK returns a static string derived from the platform name and app version. Every user on the same deployment gets the same identifier. For server-side use, pass an explicit user identifier: + +```swift +await TelemetryDeck.event("API.requestHandled", customUserID: request.authenticatedUserID) +``` + +## Setting a user identifier + +Override the default at any point after initialization: + +```swift +await TelemetryDeck.setUserIdentifier("user@example.com") +``` + +Pass `nil` to revert to the platform default: + +```swift +await TelemetryDeck.setUserIdentifier(nil) +``` + +For a single event: + +```swift +await TelemetryDeck.event("checkout.completed", customUserID: order.userID) +``` + +## Hashing + +The raw identifier is SHA256-hashed at the end of the processor pipeline, just before the event enters the cache. The hash input is `identifier + salt`, where `salt` is the value you pass to `initialize(appID:namespace:salt:)` (empty string by default). + +The result is a 64-character lowercase hex string. The unhashed identifier never leaves the device. The TelemetryDeck server applies an additional server-side salt, so even if two apps use the same user email with the same client salt, the final stored hashes differ between apps. + +## Recommendations + +- **Most apps**: the platform default (IDFV) is sufficient. Users are tracked per-device, anonymously. +- **Cross-device identity**: if you want to correlate the same user across iPhone and iPad, call `setUserIdentifier` with a stable account identifier (email, user ID). It gets hashed before transmission. +- **Server-side Swift**: always pass `customUserID` β€” the default identifier is meaningless on servers. +- **Salt**: adding a `salt` at initialization provides an extra layer β€” even if someone intercepts the hashed identifier, they can't reverse it against a known-plaintext attack without knowing your salt. diff --git a/guides/web-setup.md b/docs/sdk/official-sdk/web-setup.md similarity index 72% rename from guides/web-setup.md rename to docs/sdk/official-sdk/web-setup.md index 0a97303..e6ceffe 100644 --- a/guides/web-setup.md +++ b/docs/sdk/official-sdk/web-setup.md @@ -1,9 +1,10 @@ --- +icon: fontawesome/solid/globe title: Web Setup Guide tags: - - Setup - - JavaScript - - Websites + - setup + - javascript + - websites featured: true description: How to include the TelemetryDeck SDK in your website or blog lead: You have an account, and you have an app ID. Now let's include the TelemetryDeck Package in your website @@ -31,7 +32,7 @@ Once you have your App ID, edit the source code of your website and add the foll > ``` -πŸ’‘ If you use Google Tag Manager, please see our separate guide on [how to include TelemetryDeck in your website using Google Tag Manager](/docs/integrations/web-setup-google-tag-manager/). +πŸ’‘ If you use Google Tag Manager, please see our separate guide on [how to include TelemetryDeck in your website using Google Tag Manager](../../integrations/web-setup-google-tag-manager.md). ## Usage @@ -59,7 +60,7 @@ To see test signals, you can enable **Test Mode** in the Dashboard. ## Privacy Policy and Opt-Out -You don't need to update your privacy policy, [but we recommend you do it anyway](/docs/guides/privacy-faq/#do-i-need-to-add-telemetrydeck-to-my-privacy-policy%3F). +You don't need to update your privacy policy, [but we recommend you do it anyway](../../guides/privacy-faq). ## What data is collected? @@ -109,34 +110,22 @@ This means that the Web SDK will recognize recurring users on the same day on th ## If you're a developer -{% noteinfo "There are multiple ways of adding TelemetryDeck to a web site" %} +!!! warning "There are multiple ways of adding TelemetryDeck to a web site" -There are different tutorials you should read depending on your use case. + There are different tutorials you should read depending on your use case. -- If you are building a **JavaScript application or PWA using node package manager**, you should read the [Node Package Setup Guide](/docs/guides/javascript-setup). -- If you are building a **website or blog**, and want to include TelemetryDeck with a simple script tag similar to Google Analytics or Plausible Analytics, you should read this guide. - -{% endnoteinfo %} + - If you are building a **JavaScript application or PWA using node package manager**, you should read the [Node Package Setup Guide](javascript-setup.md). + - If you are building a **website or blog**, and want to include TelemetryDeck with a simple script tag similar to Google Analytics or Plausible Analytics, you should read this guide. ## What to do next Now that you've integrated TelemetryDeck, learn how to use the analytics platform to gain valuable insights about your users: -
-
-
- -
-

- - πŸ“Š Analytics Walkthrough -

-

Learn how to navigate TelemetryDeck, interpret insights, and use analytics to make data-driven decisions that improve your app and grow your user base.

-

- Start here to get real value from your analytics - β†’ -

-
-
-
+
+ +- **πŸ“Š Analytics Walkthrough** + + Learn how to navigate TelemetryDeck, interpret insights, and use analytics to make data-driven decisions that improve your app and grow your user base. + + [:fontawesome-solid-right-long: Start here to get real value from your analytics](../../basics/index.md)
diff --git a/tql/aggregators.md b/docs/tql/aggregators.md similarity index 88% rename from tql/aggregators.md rename to docs/tql/aggregators.md index fddc53d..5be6e2b 100644 --- a/tql/aggregators.md +++ b/docs/tql/aggregators.md @@ -97,13 +97,11 @@ A theta sketch gives you the number of unique values that a dimension has in you Theta sketches are a probabilistic data structure used for the [count-distinct problem](https://en.wikipedia.org/wiki/Count-distinct_problem). They allow us to quickly count elements in sets, such as the set of users in the aggregation buckets -{% noteinfo "More on ThetaSketches" %} +!!! warning "More on ThetaSketches" -A Theta sketch object can be thought of as a Set data structure. At query time, sketches are read and aggregated (set unioned) together. By default, you receive the estimate of the number of unique entries in the sketch object. + A Theta sketch object can be thought of as a Set data structure. At query time, sketches are read and aggregated (set unioned) together. By default, you receive the estimate of the number of unique entries in the sketch object. -Also, you can use post aggregators to do union, intersection or difference on sketch columns in the same row. This means you can create distinct sets of users and compare them against each other. This is necessary for retention and funnel queries. - -{% endnoteinfo %} + Also, you can use post aggregators to do union, intersection or difference on sketch columns in the same row. This means you can create distinct sets of users and compare them against each other. This is necessary for retention and funnel queries. ```json { @@ -117,11 +115,9 @@ Also, you can use post aggregators to do union, intersection or difference on sk `cardinality` computes the cardinality of a dimension. -{% notewarning "Deprecated" %} - -This aggregator is deprecated. Use `thetaSketch` instead. +!!! warning "Deprecated" -{% endnotewarning %} + This aggregator is deprecated. Use `thetaSketch` instead. ```json { diff --git a/tql/baseFilters.md b/docs/tql/baseFilters.md similarity index 100% rename from tql/baseFilters.md rename to docs/tql/baseFilters.md diff --git a/tql/descending.md b/docs/tql/descending.md similarity index 100% rename from tql/descending.md rename to docs/tql/descending.md diff --git a/tql/dimensionSpec.md b/docs/tql/dimensionSpec.md similarity index 80% rename from tql/dimensionSpec.md rename to docs/tql/dimensionSpec.md index 28cd830..1f8b1af 100644 --- a/tql/dimensionSpec.md +++ b/docs/tql/dimensionSpec.md @@ -9,11 +9,9 @@ The default DimensionSpec returns dimension values as is and optionally renames If an extraction function is set, it returns dimension values transformed using the given extraction function. -{% notewarning "What is a dimension?" %} +!!! warning "What is a dimension?" -TelemetryDecks Query Language is based on Apache Druid. In Druid, a dimension is a named field in your data used for filtering, grouping, or joining - typically string or categorical values. - -{% endnotewarning %} + TelemetryDecks Query Language is based on Apache Druid. In Druid, a dimension is a named field in your data used for filtering, grouping, or joining - typically string or categorical values. ## Default DimensionSpec diff --git a/tql/experiment.md b/docs/tql/experiment.md similarity index 100% rename from tql/experiment.md rename to docs/tql/experiment.md diff --git a/tql/extractionFunction.md b/docs/tql/extractionFunction.md similarity index 100% rename from tql/extractionFunction.md rename to docs/tql/extractionFunction.md diff --git a/tql/filters.md b/docs/tql/filters.md similarity index 100% rename from tql/filters.md rename to docs/tql/filters.md diff --git a/tql/firstGuideline.md b/docs/tql/firstGuideline.md similarity index 100% rename from tql/firstGuideline.md rename to docs/tql/firstGuideline.md diff --git a/tql/funnel.md b/docs/tql/funnel.md similarity index 100% rename from tql/funnel.md rename to docs/tql/funnel.md diff --git a/tql/funnels.md b/docs/tql/funnels.md similarity index 94% rename from tql/funnels.md rename to docs/tql/funnels.md index 5b7d9b5..d1a965f 100644 --- a/tql/funnels.md +++ b/docs/tql/funnels.md @@ -11,13 +11,12 @@ lead: This is a quick overview on how to create funnels, or click-stream funnels searchEngineTitle: How to extract funnels from your analytics data searchEngineDescription: This is a quick overview on how to create funnels, or click-stream funnels, using the TelemetryDeck Query Language. order: 1000 +status: deprecated --- -{% notewarning "Deprecated" %} +!!! warning "Deprecated" -The content below is deprecated and will be removed in the future. Please refer to the [new documentation](/docs/tql/funnel/) instead. - -{% endnotewarning %} + The content below is deprecated and will be removed in the future. Please refer to the [new documentation](/docs/tql/funnel/) instead. This article explains the thought process on how to create funnel-type queries; scroll to the bottom for a complete example. @@ -114,11 +113,9 @@ For our filters, we want to grab all signals that might be relevant for the funn We're going to use aggregations to split up (or aggregate) the signals into different buckets, and count them by `clientUser` which is the field for TelemetryDeck's user identifier. We're using Theta Sketches to count the number of different users for the funnel stage. -{% noteinfo "What's a theta sketch?" %} - -A theta sketch is a probabilistic data structure used for the [count-distinct problem](https://en.wikipedia.org/wiki/Count-distinct_problem). It allows us to quickly count elements in sets, such as the set of users in the aggregation buckets. +!!! warning "What's a theta sketch?" -{% endnoteinfo %} + A theta sketch is a probabilistic data structure used for the [count-distinct problem](https://en.wikipedia.org/wiki/Count-distinct_problem). It allows us to quickly count elements in sets, such as the set of users in the aggregation buckets. ```json [ diff --git a/tql/granularity.md b/docs/tql/granularity.md similarity index 100% rename from tql/granularity.md rename to docs/tql/granularity.md diff --git a/tql/groupBy.md b/docs/tql/groupBy.md similarity index 100% rename from tql/groupBy.md rename to docs/tql/groupBy.md diff --git a/tql/post-aggregators.md b/docs/tql/post-aggregators.md similarity index 100% rename from tql/post-aggregators.md rename to docs/tql/post-aggregators.md diff --git a/tql/query.md b/docs/tql/query.md similarity index 92% rename from tql/query.md rename to docs/tql/query.md index 292ad82..2119a4e 100644 --- a/tql/query.md +++ b/docs/tql/query.md @@ -11,13 +11,11 @@ Most queries for TelemetryDeck can be constructed using the regular Insight Edit You can use the TQL Query Builder to construct your query, or you can write it by hand. The minimum required properties are `granularity`, `dataSource` and `queryType`. The following sections describe the properties of the query object. -{% noteinfo "This looks curiously familiar" %} +!!! warning "This looks curiously familiar" -If you've ever worked with [Apache Druid](https://druid.apache.org), you'll notice a lot of similarities between TQL and Druid's native query language. This is because TQL is a superset of Druid's native query language, and TQL queries are compiled down into Druid queries before they're executed. + If you've ever worked with [Apache Druid](https://druid.apache.org), you'll notice a lot of similarities between TQL and Druid's native query language. This is because TQL is a superset of Druid's native query language, and TQL queries are compiled down into Druid queries before they're executed. -Druid queries can do various things that TQL queries can't do for security and privacy reasons, and Druid queries are also more verbose. To get into the fine details of Druid's query language, please check the [Apache Druid Documentation](https://druid.apache.org/docs/latest/querying/). - -{% endnoteinfo %} + Druid queries can do various things that TQL queries can't do for security and privacy reasons, and Druid queries are also more verbose. To get into the fine details of Druid's query language, please check the [Apache Druid Documentation](https://druid.apache.org/docs/latest/querying/). This example timeseries query returns the number of users per week: diff --git a/tql/queryContext.md b/docs/tql/queryContext.md similarity index 100% rename from tql/queryContext.md rename to docs/tql/queryContext.md diff --git a/tql/queryType.md b/docs/tql/queryType.md similarity index 100% rename from tql/queryType.md rename to docs/tql/queryType.md diff --git a/tql/retention.md b/docs/tql/retention.md similarity index 100% rename from tql/retention.md rename to docs/tql/retention.md diff --git a/tql/scan.md b/docs/tql/scan.md similarity index 100% rename from tql/scan.md rename to docs/tql/scan.md diff --git a/tql/time-intervals.md b/docs/tql/time-intervals.md similarity index 100% rename from tql/time-intervals.md rename to docs/tql/time-intervals.md diff --git a/tql/timeseries.md b/docs/tql/timeseries.md similarity index 100% rename from tql/timeseries.md rename to docs/tql/timeseries.md diff --git a/tql/topN.md b/docs/tql/topN.md similarity index 100% rename from tql/topN.md rename to docs/tql/topN.md diff --git a/tql/topNMetricSpec.md b/docs/tql/topNMetricSpec.md similarity index 100% rename from tql/topNMetricSpec.md rename to docs/tql/topNMetricSpec.md diff --git a/tql/tql.json b/docs/tql/tql.json similarity index 100% rename from tql/tql.json rename to docs/tql/tql.json diff --git a/tql/valueFormatter.md b/docs/tql/valueFormatter.md similarity index 100% rename from tql/valueFormatter.md rename to docs/tql/valueFormatter.md diff --git a/guides/objective-c-setup.md b/guides/objective-c-setup.md deleted file mode 100644 index 3693579..0000000 --- a/guides/objective-c-setup.md +++ /dev/null @@ -1,156 +0,0 @@ ---- -title: Objective-C Setup Guide -tags: - - Setup - - iOS - - macOS - - watchOS - - tvOS - - ObjectiveC -featured: false -testedOn: Xcode 14.1 & Swift 5.5 -description: Configure the TelemetryDeck SDK in Your Objective-C Application for iOS and macOS -lead: Let's include the TelemetryClient Swift Package in your Objective-C application and send events! -order: 1000 ---- - -Lots of iOS and macOS application are still written in Objective-C, or a mixture of Swift and ObjC. We've got you covered either way, and this guide will show you how to set up the TelemetryDeck SDK in your Objective-C application. - -## Installing the package - -The TelemetryDeck Swift package uses Swift Package Manager. - -1. Open Xcode and navigate to the project you want to add TelemetryDeck to. -1. In the menu, select File -> Add Packages.... This will open the Swift Package Manager view. -1. Paste `https://github.com/TelemetryDeck/SwiftSDK` into the search field. -1. Select the `SwiftSDK` package that appears in the list -1. Set the Dependency Rule to Up to Next Major Version. -1. Click Add Package. - -![A screenshot of Xcode adding the TelemetryDeck Package](/docs/images/xcode-swift-package.png) - -This will include the TelemetryDeck Swift Client into your app by downloading the source code. Feel free to browse the client's source code. It's tiny and you'll see for yourself how TelemetryDeck is hashing user identifiers before they ever reach the server. Privacy, yay! - -## Including the package in your target - -Xcode will ask you to link the package with your target in the next screen, titles Choose Package Products for SwiftSDK. Select the `TelemetryClient` library and click Add Package. - -{% noteinfo "Link Library with more than one Target" %} - -In case Xcode forgets to ask you to link the library with your target, you can do so manually by selecting your target in the project navigator. Selecting the Build Phases tab. Click the + button in the Link Binary With Libraries section and select the `TelemetryClient` library. -{% endnoteinfo %} - -## Initializing the TelemetryDeck package - -The `TelemetryClient` package will provide you with a class `TelemetryManager` that you'll use for all interactions with TelemetryDeck. Before you can use that class, you'll need to initialize it at the start of your app. The ideal place for this is your **App Delegate**'s `application:didFinishLaunchingWithOptions:` method: - -```objc -// Import the TelemetryClient library -@import TelemetryClient; - -// ... - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - - // Your other initialization code here ... - - // Initialize the TelemetryDeck client - TelemetryManagerConfiguration *telemetryConfig = - [[TelemetryManagerConfiguration alloc] initWithAppID:@"YOUR-APP-ID"]; - [TelemetryManager initializeWith:telemetryConfig]; - - // Optional: Set a default user ID. If you don't do this, - // the SDK will generate a random user ID for you. - // [TelemetryManager updateDefaultUserTo:@"myuserwhojustloggedin@example.com"]; - - return YES; -} -``` - -{% noteinfo "You need your app's Unique Identifier" %} -TelemetryDeck assigns a unique identifier to your app, and you need to hand that identifier to the TelemetryDeck SDK. - -Use the [TelemetryDeck Dashboard](https://dashboard.telemetrydeck.com) to create a new app and copy its unique identifier into your computer's clipboard. -{% endnoteinfo %} - -## Verify your setup - -Run your app to verify that TelemetryDeck is properly integrated. Let's send an event to show the app has launched correctly: - -```objc -[TelemetryManager send:@"applicationDidFinishLaunching"]; -``` - -{% notewarning "When running from Xcode, you're sending test signals" %} -If your app is built in `DEBUG` configuration (i.e. running from Xcode), your events will be tagged as **Test Signals**, meaning that you can easily filter them out later. You'll see them show up in the TelemetryDeck Dashboard when the **Test Mode** toggle under the tab bar is turned on. -{% endnotewarning %} - -Open the TelemetryDeck Dashboard, navigate to "Explore > Recent Signals" and make sure "Test Mode" is enabled. You should see your signal appear after launching your app. - ---- - -{% noteinfo "Ready for basic insights" %} -Congratulations! With just the SDK initialization, TelemetryDeck will automatically track user sessions, app launches, and device information. This basic setup provides valuable built-in insights without any additional code. - -You can now build and ship your app. Once users start using it, your TelemetryDeck dashboard will begin showing data about user behavior, device types, and other key metrics. -{% endnoteinfo %} - -## Enhancing your analytics (optional) - -While basic session tracking provides valuable information, sending custom events lets you answer questions specific to how users engage with *your* app. - -### Sending custom events - -{% noteinfo "What's a signal?" %} -Signals represent an **event** or a **view** that happened in your app, which is used by a **user**. Signals consist of these parts: - -- **Signal Type**: A string that indicates which kind of event happened -- **Metadata Payload**: A dictionary of additional data about your app or the event triggering the signal - -See the [Signals Reference](/docs/api/signals-reference/) for more information about how you can effectively use Signals. -{% endnoteinfo %} - -You don't need to keep an instance of TelemetryManager and hand it around, just call the `send` function on the class directly. If you want to add custom metadata payload, add it to the function call as a dictionary: - -```objc -[ - TelemetryManager - send:@"applicationDidFinishLaunching" - with:@{@"pizzaCheeseMode": @"extraCheesy"} -]; -``` - -The metadata is helpful for additional parameters for filtering or grouping signals. We'll automatically add some metadata for you, like the app version, device model, and more. - -For more information on how to send events, see the [TelemetryDeck package's `README.md` file](https://github.com/TelemetryDeck/SwiftSDK/blob/main/README.md). - -## App Store requirements - -Before uploading your app to the App Store, you'll need to complete Apple's privacy details on App Store Connect. Although TelemetryDeck is privacy-focused, you still need to disclose analytics usage. - -For guidance on completing these requirements, see our [Apple App Privacy guide](/docs/articles/apple-app-privacy/). - -For privacy policy recommendations, check our [Privacy FAQ](/docs/guides/privacy-faq/#do-i-need-to-add-telemetrydeck-to-my-privacy-policy%3F). - -## What to do next - -Now that you've integrated TelemetryDeck, learn how to use the analytics platform to gain valuable insights about your users: - -
-
-
- -
-

- - πŸ“Š Analytics Walkthrough -

-

Learn how to navigate TelemetryDeck, interpret insights, and use analytics to make data-driven decisions that improve your app and grow your user base.

-

- Start here to get real value from your analytics - β†’ -

-
-
-
-
diff --git a/guides/swift-setup.md b/guides/swift-setup.md deleted file mode 100644 index 9af5d71..0000000 --- a/guides/swift-setup.md +++ /dev/null @@ -1,267 +0,0 @@ ---- -title: Swift Setup Guide -tags: - - Setup - - iOS - - macOS - - watchOS - - tvOS -featured: true -testedOn: Xcode 16 & Swift 5.9 -description: Configure the TelemetryDeck SDK in Your Swift Application for iOS, macOS, watchOS and tvOS -lead: Let's include the TelemetryDeck Swift Package in your application and send events! -order: 100 ---- - -## Prerequisites - -This guide assumes you have already created a TelemetryDeck account. If you haven't yet, please [sign up now](https://dashboard.telemetrydeck.com/register)! - -## Installing the Swift Package - -The TelemetryDeck Swift package uses Swift Package Manager. - -1. Open Xcode and navigate to the project you want to add TelemetryDeck to -1. In the menu, select File -> Add Package Dependencies.... This will open the Swift Package Manager view -1. Paste `https://github.com/TelemetryDeck/SwiftSDK` into the search field. -1. Select the `SwiftSDK` package that appears in the list -1. Set the Dependency Rule to Up to Next Major Version -1. Press Add Package to continue - -![A screenshot of Xcode adding the TelemetryDeck Package](/docs/images/xcode-swift-package1.png) - -This will include the TelemetryDeck Swift SDK into your app by downloading the source code. Feel free to browse the client's source code, it's tiny and you'll see for yourself how TelemetryDeck is hashing user identifiers before they ever reach the server. Privacy, yay! - -**Watch our [Quick Start video](https://www.youtube.com/watch?v=FA71bSnK_B8) to setup TelemetryDeck in 4 Minutes!** - -[![TelemetryDeck Setup in 4 Minutes – Swift SDK Integration](/docs/images/yt-4-minute-setup.png)](https://www.youtube.com/watch?v=FA71bSnK_B8) - -## Including the package in your target - -Xcode will ask you to link the package with your target in the next screen, titled Choose Package Products for SwiftSDK. Set the Add to target column to your app target for TelemetryDeck (not "TelemetryClient", which is deprecated) and click Add Package to complete the integration. - -![A screenshot of Xcode setting the target for the TelemetryDeck library](/docs/images/xcode-swift-package2.png) - -{% noteinfo "Link Library with more than one Target" %} - -In case Xcode forgets to ask you to link the library with your target, you can do so manually by selecting your target in the project navigator and selecting the Build Phases tab. Click the + button in the Link Binary With Libraries section and select the `TelemetryDeck` library. -{% endnoteinfo %} - -## Initializing the TelemetryDeck Swift package - -The `TelemetryDeck` package will provide you with a class `TelemetryDeck` that you'll use for all interactions with TelemetryDeck. Before you can use that class, you'll need to initialize it at the start of your app. We strongly recommend doing so as soon as possible, as you won't be able to send events before the `TelemetryDeck` is initialized. - -This is slightly different depending on whether you use SwiftUI or UIKit's `AppDelegate` to manage your app's lifecycle, so let's look at these individually. - -{% noteinfo "You need your app's Unique Identifier" %} -TelemetryDeck assigns a unique identifier to your app, and you need to hand that identifier to the TelemetryDeck SDK. - -Use the [TelemetryDeck Dashboard](https://dashboard.telemetrydeck.com) to create a new app and copy its unique identifier into your computer's clipboard. -{% endnoteinfo %} - -You only need **one** way of initializing the TelemetryDeck SDK, either SwiftUI/SceneKit or AppDelegate. If your app is new, you'll likely want to use SwiftUI/SceneKit. - -### Initialization with SwiftUI - -For Scene-based SwiftUI applications, we recommend adding the initialization to your `@main` App struct! Open `YourAppNameApp.swift` and look for code that looks like this: - -```swift -import SwiftUI - -@main -struct YourAppNameApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } -} -``` - -This is the entry point to your app. Now let's add the initialization here. - -Import the TelemetryDeck Package by adding `import TelemetryDeck`. Then add an `init()` method to your App struct that creates a `TelemetryDeck.Config` instance and hands it to `TelemetryDeck.initialize(config:)`, using the **Unique Identifier of your app** that you copied into your clipboard earlier. If you don't have that anymore, you can get it at any time from the TelemetryDeck Dashboard. - -Your code should now look like this: - -```swift -import SwiftUI -import TelemetryDeck - -@main -struct YourAppNameApp: App { - init() { - let config = TelemetryDeck.Config(appID: "YOUR-APP-ID") - TelemetryDeck.initialize(config: config) - } - - var body: some Scene { - WindowGroup { - ContentView() - } - } -} -``` - -If you prefer to have it on a single line, you can also write: - -```swift -TelemetryDeck.initialize(config: .init(appID: "YOUR-APP-ID")) -``` - -Your app is now ready to use TelemetryDeck. You can skip the next section which explains setup for UIKit-based apps. - -### Initialization in an AppDelegate based app - -If you use `AppDelegate` to manage your app's life cycle, open the file `AppDelegate.swift` and look for the method `application(:didFinishLaunchingWithOptions:)`. It will probably look similar to this: - -```swift -import UIKit - -@main -class AppDelegate: UIResponder, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - return true - } - - // ... -} -``` - -By default, Xcode even adds a comment here to tell you where to add stuff that should happen right after launch. - -Now, import the `TelemetryDeck` package and configure the `TelemetryDeck` using the **Unique Identifier of your app** that you copied into your clipboard earlier. If you don't have that anymore you can get it at any time from the TelemetryDeck Dashboard. - -```swift -import UIKit -import TelemetryDeck - -@main -class AppDelegate: UIResponder, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - - let config = TelemetryDeck.Config(appID: "YOUR-APP-ID") - TelemetryDeck.initialize(config: config) - - return true - } - // ... -} -``` - -## Verify your setup - -Run your app to verify that TelemetryDeck is properly integrated. The SDK automatically sends a `TelemetryDeck.Session.started` signal when your app launches. - -{% notewarning "When running from Xcode, you're sending test events" %} - -If your app is built in `DEBUG` configuration (i.e. running from Xcode), your events will be tagged as **Test Signals**, meaning that you can easily filter them out later. You'll see them show up in the TelemetryDeck Dashboard when the **Test Mode** toggle under the tab bar is turned on. -{% endnotewarning %} - -Open the TelemetryDeck Dashboard, navigate to "Explore > Recent Signals" and make sure "Test Mode" is enabled. You should see the automatic session signal appear after launching your app. - ---- - -{% noteinfo "Ready for basic insights" %} -Congratulations! With just the SDK initialization, TelemetryDeck will automatically track user sessions, app launches, and device information. This basic setup provides valuable built-in insights without any additional code. - -You can now build and ship your app. Once users start using it, your TelemetryDeck dashboard will begin showing data about user behavior, device types, and other key metrics. -{% endnoteinfo %} - -## Enhancing your analytics (optional) - -While basic session tracking provides valuable information, sending custom events lets you answer questions specific to how users engage with *your* app. - -### Common questions you can answer with custom events - -- Which features do users engage with most frequently? -- Where in the onboarding flow do users drop off? -- How are users navigating between different screens? -- Which settings or configurations are most popular? - -### Sending custom events - -{% noteinfo "What is a signal?" %} -Signals are an indication that **an event** happened in your app, which is used by a **user**. Signals consist of these parts: - -- **Signal Name** – A string that indicates which kind of event happened -- **User Identifier** – A string that identifies your user (we auto-generate one for you) -- **Optional Parameters** – A dictionary of additional data about your app or the event triggering the signal - -See the [Signals Reference](/docs/api/signals-reference/) for more information about how you can effectively use Signals. -{% endnoteinfo %} - -Let's imagine your app is a pizza oven monitor and we want to send a signal that tells us the user has tapped the "Start Baking" button. Go to the place in your code where the user taps the button and add the following code: - -```swift -TelemetryDeck.signal("Oven.Bake.startBaking") -``` - -That's all you need to send a signal. You do not need to keep an instance of TelemetryDeck and hand it around, just call the static `signal` function on the class directly. If you want to add custom parameters, add them to the function call like this: - -```swift -TelemetryDeck.signal( - "Oven.Bake.startBaking", - parameters: [ - "numberOfTimesPizzaModeHasActivated": "\(dataStore.pizzaMode.count)", - "pizzaCheeseMode": "\(dataStore.pizzaCheeseMode)" - ] -) -``` - -{% noteinfo "Privacy Note" %} -The value you pass to `customUserID` will be automatically hashed before being sent to our servers to protect the users privacy. This does not happen for the values in `parameters` though, so hash yourself where needed. -{% endnoteinfo %} - -## Configuring default signal properties (optional) - -When initializing TelemetryDeck, you can configure some defaults to help keep your signals organized and consistent: - -```swift -let config = TelemetryDeck.Config(appID: "YOUR-APP-ID") - -// Add a prefix to all signal names -config.defaultSignalPrefix = "App." -// With this set, calling signal("launched") will actually send "App.launched" - -// Add a prefix to all parameter names -config.defaultParameterPrefix = "MyApp." -// This prefixes all keys in your parameters dictionary - -// Set parameters that will be included with every signal -config.defaultParameters = {[ - "theme": UserDefaults.standard.string(forKey: "theme") ?? "default", - "isPayingUser": FreemiumKit.shared.hasPurchased ? "true" : "false", -]} -// These parameters will be merged with any additional parameters you specify in signal() calls -``` - -## App Store requirements - -Before uploading your app to the App Store, you'll need to complete Apple's privacy details on App Store Connect. Although TelemetryDeck is privacy-focused, you still need to disclose analytics usage. - -For guidance on completing these requirements, see our [Apple App Privacy guide](/docs/articles/apple-app-privacy/). For privacy policy recommendations, check our [Privacy FAQ](/docs/guides/privacy-faq/#do-i-need-to-add-telemetrydeck-to-my-privacy-policy%3F). - -## What to do next - -Now that you've integrated TelemetryDeck, learn how to use the analytics platform to gain valuable insights about your users: - -
-
-
- -
-

- - πŸ“Š Analytics Walkthrough -

-

Learn how to navigate TelemetryDeck, interpret insights, and use analytics to make data-driven decisions that improve your app and grow your user base.

-

- Start here to get real value from your analytics - β†’ -

-
-
-
-
diff --git a/index.njk b/index.njk deleted file mode 100644 index 398e42f..0000000 --- a/index.njk +++ /dev/null @@ -1,56 +0,0 @@ ----json -{ -"title": "Quick Start Guide", -"lead": "Set up TelemetryDeck in minutes and start gaining insights into your app's performance", -"tags": "intro", -"categoryOrder": -9000, -"searchEngineDescription":"With this Quick Start Guide, you can set up TelemetryDeck's app analytics in minutes and start gaining insights into your app's performance." -} ---- - -

Welcome to TelemetryDeck

- -

TelemetryDeck helps you understand how users interact with your application through privacy-focused analytics. With just an SDK integration, you'll get valuable insights automatically – no complex configuration required.

- -

Getting Started in 2 simple steps

- -
    -
  1. Choose and install the SDK for your platform from the guides below
  2. -
  3. Deploy your app to start collecting data
  4. -
- -

Once your updated app is in users' hands, TelemetryDeck will begin collecting data. It may take some time before you see meaningful insights, depending on your app's usage.

- -{% include "docs/featured.njk" %} - -

What's next after setup?

- -

After setting up the SDK and deploying your app, your most important next step is to learn how to use the dashboard to analyze your data:

- -
-
-
- -
-

- - πŸ“Š Analytics Walkthrough -

-

Learn how to navigate TelemetryDeck, interpret insights, and use analytics to make data-driven decisions that improve your app and grow your user base.

-

- Start here to get real value from your analytics - β†’ -

-
-
-
-
- -

Documentation Feedback

- -

If you find an error or feel like the documentation could be improved somewhat, we'd love -to hear from you! Either directly submit a change request with the buttons on each page, -or open an issue in our Docs GitHub Repository.

- -

The documentation guide explains all markdown and -additional features you can use while writing documentation for TelemetryDeck.

diff --git a/integrations/revenuecat.md b/integrations/revenuecat.md deleted file mode 100644 index bf80a5a..0000000 --- a/integrations/revenuecat.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: Using TelemetryDeck with RevenueCat -tags: - - how-to - - iOS -testedOn: RevenueCat Swift SDK 5.2.3 -description: Here's how to integrate TelemetryDeck with RevenueCat to combine usage data with purchase data. -lead: Here's how to integrate TelemetryDeck with RevenueCat to combine usage data with purchase data. -order: 90 ---- - -[RevenueCat](https://www.revenuecat.com/) is a service that helps you process payments and in-app purchases on iOS, Android, and the web. It's a great way to add monetization to your app. - -RevenueCat pairs excellently with TelemetryDeck – use TelemetryDeck to improve your users' flow through your application, and then present them with convenient In-App-Purchase offers. - -With this integration, you can import your RevenueCat events into TelemetryDeck, and see everything on one dashboard. We'll be using RevenueCat's _Webhooks_ feature to pass on their data to TelemetryDeck. - -{% noteinfo "Read the announcement" %} - -Our [announcement blog post](https://telemetrydeck.com/blog/revenuecat-integration/) shows you what to expect, how to use the new revenue dashboard, and gives examples on what to do with your revenue data. - -{% endnoteinfo %} - -## Installing RevenueCat and TelemetryDeck - -First, we have to integrate both TelemetryDeck and RevenueCat into your app. You can find guides for both here: - -1. [Install and set up TelemetryDeck](/docs/guides/swift-setup/) -2. [Install and set up RevenueCat](https://www.revenuecat.com/docs/getting-started/installation) - -## Configuring the RevenueCat SDK - -RevenueCat has a concept of **user attributes**. Our goal is to set two new user attributes for our RevenueCat users that will make TelemetryDeck recognize them as the same users it is already managing. - -- `$telemetryDeckAppId`: This attribute should be set to your TelemetryDeck App ID, the same one you pass into the TelemetryDeck SDK for initialization. -- `$telemetryDeckUserId`: This attribute needs to be the **already-hashed user identifier** that TelemetryDeck is using. - -{% noteinfo "RevenueCat gets the hashed version of the TelemetryDeck User Identifier" %} - -While the TelemetryDeck SDK usually takes care of hashing for you, you'll need to extract the identifier after it's been hashed and pass that on to RevenueCat. Only then will the final identifiers in your TelemetryDeck dashboard match up. - -If your version of the TelemetryDeck SDK does not expose a function to vend the hashed user identifier, you can hash it yourself using something like `SHA256(user_id + salt)`. - -{% endnoteinfo %} - -### iOS - -Here's how to set up TelemetryDeck and RevenueCat on iOS. The setup process is similar for other platforms. - -```swift -// 1. -// Initialize TelemetryDeck with your app ID -let telemetrydeckAppID = "AAAAAAAA-BBBB-CCCC-DDDD" -let telemetryDeckConfig = TelemetryDeck.Config( - appID: telemetrydeckAppID, - salt: "MY_SECRET_SALT" // optional but recommended -) -TelemetryDeck.initialize(config: telemetryDeckConfig) - -// 2. -// Manually set a default user for TelemetryDeck -// We're using IFV here, but you can also use -// e.g. an email address or any other identifying property -let myUserID = UIDevice.current - .identifierForVendor?.uuidString - ?? "unknown user" -TelemetryDeck.updateDefaultUserID(to: myUserID) - -// 3. -// Set up RevenueCat with your TelemetryDeck App ID -// and the pre-hashed TelemetryDeck User ID -Purchases.configure(withAPIKey: "my_revenuecat_api_key") -Purchases.shared.attribution.setAttributes([ - "$telemetryDeckUserId": TelemetryManager.shared - .hashedDefaultUser - ?? "no-user", - "$telemetryDeckAppId": telemetrydeckAppID -]) -``` - -Here's what's going on in the above example - -1. First we set up TelemetryDeck as described in the setup guide. If you already have set up TelemetryDeck, you can leave your setup unchanged. -2. We then manually set a default user for TelemetryDeck. This allows us to later retrieve a hashed version of the user identifier. -3. Finally, we configure RevenueCat and set up the necessary user attributes. - -{% notewarning "You need to keep user identifiers in sync" %} - -Whenever you update your TelemetryDeck user identifier, you'll also need to update the user identifier in RevenueCat's `$telemetryDeckUserId` user attribute. - -{% endnotewarning %} - -## Setting up RevenueCat's TelemetryDeck Integration - -Now we need to tell RevenueCat to send copies of all events over to TelemetryDeck. We're using a RevenueCat's **TelemetryDeckIntegration** to do this. - -- Navigate to your RevenueCat Project -- In the left sidebar, click **Integrations** -- Select **TelemetryDeck** -- Click **Add Integration** to confirm - -![A screenshot of RevenueCat's TelemetryDeck Integration](/docs/images/rc-td-1.png) -![A screenshot of RevenueCat's TelemetryDeck Integration](/docs/images/rc-td-2.png) - -And you're done. RevenueCat events should now arrive and be mixed in with your TelemetryDeck signals πŸ₯³ diff --git a/integrations/superwall.md b/integrations/superwall.md deleted file mode 100644 index 9d614df..0000000 --- a/integrations/superwall.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: Using TelemetryDeck with Superwall iOS -tags: - - how-to - - iOS -testedOn: SuperWall iOS SDK 3.2.. -description: Here's how to integrate TelemetryDeck with SuperWall to get insights into your paywalls. -lead: Here's how to integrate TelemetryDeck with SuperWall to get insights into your paywalls. -order: 100 ---- - -[Superwall](https://superwall.com/) is a service that lets you experiment and try out different paywalls and monetization strategies. It's a great way to get started with monetization and to get insights into what works and what doesn't. - -It pairs excellently with TelemetryDeck, as you can use TelemetryDeck to get insights into how your users interact with your paywalls. Superwall has various hooks that you can use to send events to TelemetryDeck, and this guide will show you how to do that. - -## Installing Superwall and TelemetryDeck - -First, we have to integrate both TelemetryDeck and Superwall into your app. You can find guides for both here: - -1. [Install and setup the TelemetryDeck SDK](/docs/guides/swift-setup/) -2. [Install and set up the Superwall SDK](https://docs.superwall.com/docs/installation-via-spm) - -If you've already set up TelemetryDeck or Superwall, you can skip the installation steps. Just make sure that you've set up both SDKs correctly. The order in which you initialize the SDKs doesn't matter. - -## Creating a Superwall delegate - -Superwall allows you to set a delegate that gets notified when a paywall is shown, dismissed, or when a user subscribes. We're going to use this delegate to send events to TelemetryDeck. - -If you want to dive deeper, check out [Superwall's documentation on how to pass information to various analytics providers](https://docs.superwall.com/docs/3rd-party-analytics). - -Create a new Swift file in your project and name it `SuperwallService.swift`. This file will contain the code that sends events to TelemetryDeck. - -Paste the following code into the file: - -```swift -import SuperwallKit -import TelemetryClient - -class SuperwallService: SuperwallDelegate { - func handleSuperwallEvent(withInfo eventInfo: SuperwallEventInfo) { - var stringifiedParams: [String: String] = [:] - - for param in eventInfo.params { - stringifiedParams[param.key] = String(describing: param.value) - } - - TelemetryDeck.signal(eventInfo.event.description, parameters: stringifiedParams) - } -} -``` - -Because TelemetryDeck only accepts strings as event metadata, this method accepts all data from Superwall, converts it into strings, and hands it off to TelemetryDeck. The TelemetryDeck takes care of queuing and sending the events to TelemetryDeck. - -## Registering the Superwall delegate - -Now that we have a delegate, we have to register it with Superwall. We can do this in the function that you initialize TelemetryDeck and Superwall in. This is usually in your `App` struct or `AppDelegate`. When in doubt, search for `TelemetryDeck.initialize` in your project. - -Add the following line to the function, making sure it is below the initialization code for both TelemetryDeck and Superwall: - -```swift -let superwallService = SuperwallService() -Superwall.shared.delegate = superwallService -``` - -This initializes the Superwall delegate and registers it with Superwall. Now, whenever a paywall is shown, dismissed, or a user subscribes, the delegate will be notified and you'll get analytics data in your TelemetryDeck dashboard. diff --git a/overrides/partials/integrations/analytics/telemetrydeck.html b/overrides/partials/integrations/analytics/telemetrydeck.html new file mode 100644 index 0000000..3ec627d --- /dev/null +++ b/overrides/partials/integrations/analytics/telemetrydeck.html @@ -0,0 +1,4 @@ + diff --git a/scripts/build_llms.py b/scripts/build_llms.py new file mode 100644 index 0000000..97a4da0 --- /dev/null +++ b/scripts/build_llms.py @@ -0,0 +1,255 @@ +"""Generate llms.txt, llms-full.txt, and ship raw .md sources alongside the +rendered HTML so coding agents and LLMs can consume the documentation. + +Run after `zensical build`. Reads: + - zensical.toml (for site metadata and navigation) + - docs/ (source markdown) + +Writes into the existing site/ directory: + - site/.md for every source page (preserves directory layout) + - site/llms.txt structured index per https://llmstxt.org + - site/llms-full.txt all page content concatenated for one-shot consumption +""" + +from __future__ import annotations + +import re +import shutil +import sys +import tomllib +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable + +ROOT = Path(__file__).resolve().parent.parent +CONFIG_PATH = ROOT / "zensical.toml" +DOCS_DIR = ROOT / "docs" +SITE_DIR = ROOT / "site" +STATIC_WEB_APP_CONFIG = ROOT / "staticwebapp.config.json" +DEFAULT_SITE_URL = "https://docs.telemetrydeck.com" + +FRONTMATTER_RE = re.compile(r"^---\n(.*?)\n---\n", re.DOTALL) + + +@dataclass +class Page: + rel_path: str + title: str + description: str + + +def load_config() -> dict: + with CONFIG_PATH.open("rb") as f: + return tomllib.load(f) + + +def parse_frontmatter(text: str) -> tuple[dict, str]: + """Extract YAML-ish frontmatter without pulling in PyYAML. + + Only top-level scalar key/value pairs are read β€” that is all we need + (title, description, lead). Lists and nested maps are skipped. + """ + match = FRONTMATTER_RE.match(text) + if not match: + return {}, text + data: dict[str, str] = {} + current_key: str | None = None + for line in match.group(1).splitlines(): + if not line.strip() or line.lstrip().startswith("#"): + continue + if line.startswith(" ") or line.startswith("\t"): + current_key = None + continue + if ":" not in line: + continue + key, _, value = line.partition(":") + key = key.strip() + value = value.strip().strip('"').strip("'") + current_key = key + data[key] = value + body = text[match.end():] + return data, body + + +def page_for(rel_path: str, fallback: str | None = None) -> Page | None: + src = DOCS_DIR / rel_path + if not src.is_file(): + print(f"warning: missing source page {rel_path}", file=sys.stderr) + return None + text = src.read_text(encoding="utf-8") + fm, _ = parse_frontmatter(text) + title = fm.get("title") or fallback or fallback_title(rel_path) + description = fm.get("description") or fm.get("lead", "") + return Page(rel_path=rel_path, title=title, description=description) + + +def fallback_title(rel_path: str) -> str: + stem = Path(rel_path).stem + if stem == "index": + stem = Path(rel_path).parent.name or "Home" + return stem.replace("-", " ").replace("_", " ").title() + + +def md_url(rel_path: str, base_url: str) -> str: + return f"{base_url.rstrip('/')}/{rel_path}" + + +def copy_source_markdown(rel_paths: Iterable[str]) -> int: + count = 0 + for rel_path in rel_paths: + src = DOCS_DIR / rel_path + dst = SITE_DIR / rel_path + if not src.is_file(): + continue + dst.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(src, dst) + count += 1 + return count + + +def collect_nav_pages( + nav: list, prefix: tuple[str, ...] = () +) -> list[tuple[tuple[str, ...], str, str]]: + """Walk the nav structure and yield (section_path, page_rel_path, label) tuples.""" + pages: list[tuple[tuple[str, ...], str, str]] = [] + for entry in nav: + if not isinstance(entry, dict) or len(entry) != 1: + continue + label, value = next(iter(entry.items())) + if isinstance(value, str): + pages.append((prefix, value, label)) + elif isinstance(value, list): + pages.extend(collect_nav_pages(value, prefix + (label,))) + return pages + + +def all_source_pages() -> list[str]: + return sorted( + str(p.relative_to(DOCS_DIR)).replace("\\", "/") + for p in DOCS_DIR.rglob("*.md") + ) + + +def render_llms_txt(config: dict, base_url: str) -> str: + project = config.get("project", {}) + site_name = project.get("site_name", "Documentation") + site_description = project.get("site_description", "") + nav = project.get("nav", []) + + nav_pages = collect_nav_pages(nav) + seen: set[str] = set() + + lines: list[str] = [f"# {site_name}", ""] + if site_description: + lines += [f"> {site_description}", ""] + lines += [ + "Every documentation page is also available as Markdown by visiting", + "the same URL with a `.md` suffix (for example `/articles/insights.md`).", + "A single concatenated copy of all pages lives at `/llms-full.txt`.", + "", + ] + + sections: dict[tuple[str, ...], list[Page]] = {} + for section_path, rel_path, label in nav_pages: + if rel_path in seen: + continue + seen.add(rel_path) + page = page_for(rel_path, fallback=label) + if page is None: + continue + sections.setdefault(section_path, []).append(page) + + for section_path, pages in sections.items(): + heading = " / ".join(section_path) if section_path else "Overview" + lines.append(f"## {heading}") + lines.append("") + for page in pages: + url = md_url(page.rel_path, base_url) + entry = f"- [{page.title}]({url})" + if page.description: + entry += f": {page.description}" + lines.append(entry) + lines.append("") + + extras = sorted( + rel_path for rel_path in all_source_pages() if rel_path not in seen + ) + if extras: + lines.append("## Optional") + lines.append("") + for rel_path in extras: + page = page_for(rel_path) + if page is None: + continue + url = md_url(rel_path, base_url) + entry = f"- [{page.title}]({url})" + if page.description: + entry += f": {page.description}" + lines.append(entry) + lines.append("") + + return "\n".join(lines).rstrip() + "\n" + + +def render_llms_full_txt(config: dict, base_url: str) -> str: + project = config.get("project", {}) + site_name = project.get("site_name", "Documentation") + + parts: list[str] = [ + f"# {site_name} (full text)", + "", + "Concatenated source for every documentation page. Pages are separated", + "by horizontal rules and labelled with their canonical URL.", + "", + ] + + for rel_path in all_source_pages(): + page = page_for(rel_path) + if page is None: + continue + url = md_url(rel_path, base_url) + text = (DOCS_DIR / rel_path).read_text(encoding="utf-8") + parts += [ + "---", + "", + f"# {page.title}", + "", + f"Source: {url}", + "", + text.rstrip(), + "", + ] + + return "\n".join(parts).rstrip() + "\n" + + +def main() -> int: + if not SITE_DIR.is_dir(): + print(f"error: site directory not found at {SITE_DIR}", file=sys.stderr) + print("run `zensical build` before this script", file=sys.stderr) + return 1 + + config = load_config() + site_url = config.get("project", {}).get("site_url") or DEFAULT_SITE_URL + base_url = site_url.rstrip("/") + + rel_paths = all_source_pages() + copied = copy_source_markdown(rel_paths) + print(f"copied {copied} markdown sources into {SITE_DIR}") + + if STATIC_WEB_APP_CONFIG.is_file(): + shutil.copy2(STATIC_WEB_APP_CONFIG, SITE_DIR / STATIC_WEB_APP_CONFIG.name) + print(f"copied {STATIC_WEB_APP_CONFIG.name} into {SITE_DIR}") + + llms_txt = render_llms_txt(config, base_url) + (SITE_DIR / "llms.txt").write_text(llms_txt, encoding="utf-8") + print(f"wrote {SITE_DIR / 'llms.txt'}") + + llms_full_txt = render_llms_full_txt(config, base_url) + (SITE_DIR / "llms-full.txt").write_text(llms_full_txt, encoding="utf-8") + print(f"wrote {SITE_DIR / 'llms-full.txt'}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/staticwebapp.config.json b/staticwebapp.config.json new file mode 100644 index 0000000..d69301a --- /dev/null +++ b/staticwebapp.config.json @@ -0,0 +1,6 @@ +{ + "mimeTypes": { + ".md": "text/markdown; charset=utf-8", + ".txt": "text/plain; charset=utf-8" + } +} diff --git a/zensical.toml b/zensical.toml new file mode 100644 index 0000000..574e158 --- /dev/null +++ b/zensical.toml @@ -0,0 +1,518 @@ +# ============================================================================ +# +# The configuration produced by default is meant to highlight the features +# that Zensical provides and to serve as a starting point for your own +# projects. +# +# ============================================================================ + +[project] + + +repo_url = "https://github.com/TelemetryDeck/docs" + +# The site_name is shown in the page header and the browser window title +# +# Read more: https://zensical.org/docs/setup/basics/#site_name +site_name = "TelemetryDeck Documentation" + +# The site_description is included in the HTML head and should contain a +# meaningful description of the site content for use by search engines. +# +# Read more: https://zensical.org/docs/setup/basics/#site_description +site_description = "Documentation for the installation and usage of TelemetryDeck, a privacy first usage analytics service." + +# The site_author attribute. This is used in the HTML head element. +# +# Read more: https://zensical.org/docs/setup/basics/#site_author +site_author = "TelemetryDeck Docs Team" + +# The site_url is the canonical URL for your site. When building online +# documentation you should set this. +# Read more: https://zensical.org/docs/setup/basics/#site_url +#site_url = "https://docs.telemetrydeck.com/" + +# The copyright notice appears in the page footer and can contain an HTML +# fragment. +# +# Read more: https://zensical.org/docs/setup/basics/#copyright +copyright = """ +Copyright © 2026 TelemetryDeck GmbH +""" + +# Zensical supports both implicit navigation and explicitly defined navigation. +# Pages are grouped thematically below; group titles map to TOML inline tables, +# and a value can be either a path (single page) or an array (collapsible group). +# +# Read more: https://zensical.org/docs/setup/navigation/ +nav = [ + { "Home" = "https://telemetrydeck.com" }, + { "Quick Start Guide" = [ + "index.md", + { "Account" = "articles/create-an-account.md"}, + { "Namespaces" = "articles/namespaces.md" }, + { "Invite members" = "articles/invite-members-to-organization.md" }, + { "Authentication" = "articles/authentication.md"}, + ]}, + { "SDK" = [ + "sdk/index.md", + { "Official SDKs" = [ + { "Swift" = [ + "sdk/official-sdk/swift/index.md", + { "Processors" = "sdk/official-sdk/swift/swift-processors.md" }, + { "Custom Processors" = "sdk/official-sdk/swift/swift-custom-processors.md" }, + { "User Identification" = "sdk/official-sdk/swift/swift-user-identification.md" }, + { "Migrating from V2" = "sdk/official-sdk/swift/swift-migration-v3.md" }, + ] }, + { "Android (Kotlin)" = "sdk/official-sdk/android-setup.md" }, + { "Flutter" = "sdk/official-sdk/flutter-setup.md" }, + { "JavaScript" = "sdk/official-sdk/javascript-setup.md" }, + { "Web" = "sdk/official-sdk/web-setup.md" }, + { "Objective-C" = "sdk/official-sdk/objective-c-setup.md" }, + ] }, + { "Community SDKs" = [ + { "React" = "sdk/community-sdk/react-setup.md" }, + { "Vue" = "sdk/community-sdk/vue-setup.md" }, + ] }, + { "Guides" = [ + { "Swift Client Reference" = "sdk/guides/telemetry-client.md" }, + { "Update a Swift package" = "sdk/guides/update-package.md" }, + { "TelemetryDeck's Grand Rename" = "sdk/guides/grand-rename.md" }, + ] }, + ] }, + { "Privacy & Compliance" = [ + { "TelemetryDeck Privacy" = [ + { "Privacy FAQ" = "guides/privacy-faq.md" }, + { "Anonymization of user data" = "articles/anonymization-how-it-works.md" }, + { "Hosting Solutions at TelemetryDeck" = "articles/hosting-solutions.md" }, + ] }, + { "Apple Privacy" = [ + { "Apple App Privacy" = "articles/apple-app-privacy.md" }, + { "App Privacy Report" = "articles/app-privacy-report.md" }, + { "App Tracking Transparency" = "articles/app-tracking-transparency.md" }, + ] }, + ] }, + { "Usage" = [ + { "Analyze data" = [ + { "Dashboard" = [ + { "Overview" = "basics/index.md" }, + { "Custom dashboards" = "articles/create-custom-dashboards.md" }, + { "Insights overview" = "articles/insights.md" }, + { "Notebooks" = "articles/notebooks.md" }, + { "Filters" = "articles/set-up-filters-insights.md" }, + { "System version distribution" = "articles/insights-about-system-version.md" }, + ] }, + { "Analytics" = [ + { "Acquisition" = "basics/acquisition.md" }, + { "Activation" = "basics/activation.md" }, + { "Retention" = "basics/retention.md" }, + { "Metrics" = "basics/metrics.md" }, + { "Pirate Metrics (AARRR)" = "basics/pirate-metrics.md" }, + { "Built-in Presets" = [ + { "Purchases" = "articles/preset-purchases.md" }, + { "Errors" = "articles/preset-errors.md" }, + { "Navigation signals" = "articles/navigation-signals.md" }, + { "Duration signals" = "articles/duration-signals.md" }, + ] }, + { "TelemetryClient (legacy)" = "articles/telemetry-client.md" }, + ] }, + { "Integrations" = [ + { "RevenueCat" = "integrations/revenuecat.md" }, + { "Superwall" = "integrations/superwall.md" }, + { "Google Tag Manager" = "integrations/web-setup-google-tag-manager.md" }, + ] }, + ] }, + { "Send data" = [ + { "Dashboard" = [ + { "Test mode" = "articles/test-mode.md" }, + { "Signal type naming" = "articles/signal-type-naming.md" }, + { "Funnel insights" = "articles/how-to-funnel-insights.md" }, + { "Referrers" = "articles/insights-about-referrers.md" }, + ] }, + { "Quick set-up" = [ + { "Daily active users" = "articles/insights-about-daily-users.md" }, + { "Monthly active users" = "articles/insights-about-monthly-users.md" }, + { "Latest version adoption" = "articles/check-if-users-upgrade-to-latest-app-version.md" }, + ] } + ] } + ] }, + { "TQL" = [ + "tql/firstGuideline.md", + { "TQL Reference" = [ + { "TQL query reference" = "tql/query.md" }, + { "Query context" = "tql/queryContext.md" }, + { "Query type" = "tql/queryType.md" }, + { "Query types" = [ + { "Timeseries" = "tql/timeseries.md" }, + { "Top N" = "tql/topN.md" }, + { "Group By" = "tql/groupBy.md" }, + { "Scan" = "tql/scan.md" }, + { "Funnel" = "tql/funnel.md" }, + { "Retention" = "tql/retention.md" }, + { "Experiment" = "tql/experiment.md" }, + { "Funnels (deprecated)" = "tql/funnels.md" }, + ] }, + { "Filters & time" = [ + { "Filters" = "tql/filters.md" }, + { "Base filters" = "tql/baseFilters.md" }, + { "Time intervals" = "tql/time-intervals.md" }, + ] }, + { "Aggregators" = [ + { "Aggregators" = "tql/aggregators.md" }, + { "Post-aggregators" = "tql/post-aggregators.md" }, + ] }, + { "Specs & helpers" = [ + { "Dimension spec" = "tql/dimensionSpec.md" }, + { "Extraction function" = "tql/extractionFunction.md" }, + { "Granularity" = "tql/granularity.md" }, + { "TopN metric spec" = "tql/topNMetricSpec.md" }, + { "Value formatter" = "tql/valueFormatter.md" }, + { "Descending" = "tql/descending.md" }, + ] }, + ] }, + { "Recipes" = [ + { "Averaging numbers" = "recipes/averagaging-numbers.md" }, + { "Listing events" = "recipes/listing-events.md" }, + { "Views per purchase" = "recipes/views-per-purchase-example.md" }, + ] }, + ] }, + { "API" = [ + { "Reference" = [ + { "Signals reference" = "api/signals-reference.md" }, + { "Insights reference" = "api/insights-reference.md" }, + { "Run a query" = "api/api-run-query.md" }, + { "Query from insight" = "api/api-query-from-insight.md" }, + { "API tokens" = "api/api-token.md" }, + ] }, + { "Server-side ingest API" = [ + { "Default parameters" = "ingest/default-parameters.md" }, + { "Ingest API v2" = "ingest/v2.md" }, + { "Ingest API v1 (legacy)" = "ingest/v1.md" }, + ] }, + ] }, + { "Community" = [ + { "Glossary" = "glossary.md" }, + { "FAQ" = "" }, + { "Contribute" = [ + { "Bug Report" = "" }, + { "Documentation" = "articles/documentation.md"}, + { "Change Request" = "" }, + { "Pull Request" = "" }, + { "Code of Conduct" = "" }, + ] }, + ] }, +] + +# With the "extra_css" option you can add your own CSS styling to customize +# your Zensical project according to your needs. You can add any number of +# CSS files. +# +# The path provided should be relative to the "docs_dir". +# +# Read more: https://zensical.org/docs/customization/#additional-css +# +#extra_css = ["stylesheets/extra.css"] + +# With the `extra_javascript` option you can add your own JavaScript to your +# project to customize the behavior according to your needs. +# +# The path provided should be relative to the "docs_dir". +# +# Read more: https://zensical.org/docs/customization/#additional-javascript +#extra_javascript = ["javascripts/extra.js"] + +# ---------------------------------------------------------------------------- +# Section for configuring theme options +# ---------------------------------------------------------------------------- +[project.theme] +# change this to "classic" to use the traditional Material for MkDocs look. +#variant = "classic" + +# Zensical allows you to override specific blocks, partials, or whole +# templates as well as to define your own templates. To do this, uncomment +# the custom_dir setting below and set it to a directory in which you +# keep your template overrides. +# +# Read more: +# - https://zensical.org/docs/customization/#extending-the-theme +# +custom_dir = "overrides" + +# With the "favicon" option you can set your own image to use as the icon +# browsers will use in the browser title bar or tab bar. The path provided +# must be relative to the "docs_dir". +# +# Read more: +# - https://zensical.org/docs/setup/logo-and-icons/#favicon +# - https://developer.mozilla.org/en-US/docs/Glossary/Favicon +# +favicon = "assets/favicon.png" + +# Zensical supports more than 60 different languages. This means that the +# labels and tooltips that Zensical's templates produce are translated. +# The "language" option allows you to set the language used. This language +# is also indicated in the HTML head element to help with accessibility +# and guide search engines and translation tools. +# +# The default language is "en" (English). It is possible to create +# sites with multiple languages and configure a language selector. See +# the documentation for details. +# +# Read more: +# - https://zensical.org/docs/setup/language/ +# +language = "en" + +# Zensical provides a number of feature toggles that change the behavior +# of the documentation site. +features = [ + # Zensical includes an announcement bar. This feature allows users to + # dismiss it when they have read the announcement. + # https://zensical.org/docs/setup/header/#announcement-bar + "announce.dismiss", + + # If you have a repository configured and turn on this feature, Zensical + # will generate an edit button for the page. This works for common + # repository hosting services. + # https://zensical.org/docs/setup/repository/#content-actions + "content.action.edit", + + # If you have a repository configured and turn on this feature, Zensical + # will generate a button that allows the user to view the Markdown + # code for the current page. + # https://zensical.org/docs/setup/repository/#content-actions + "content.action.view", + + # Code annotations allow you to add an icon with a tooltip to your + # code blocks to provide explanations at crucial points. + # https://zensical.org/docs/authoring/code-blocks/#code-annotations + "content.code.annotate", + + # This feature turns on a button in code blocks that allow users to + # copy the content to their clipboard without first selecting it. + # https://zensical.org/docs/authoring/code-blocks/#code-copy-button + "content.code.copy", + + # Code blocks can include a button to allow for the selection of line + # ranges by the user. + # https://zensical.org/docs/authoring/code-blocks/#code-selection-button + "content.code.select", + + # Zensical can render footnotes as inline tooltips, so the user can read + # the footnote without leaving the context of the document. + # https://zensical.org/docs/authoring/footnotes/#footnote-tooltips + "content.footnote.tooltips", + + # If you have many content tabs that have the same titles (e.g., "Python", + # "JavaScript", "Cobol"), this feature causes all of them to switch to + # at the same time when the user chooses their language in one. + # https://zensical.org/docs/authoring/content-tabs/#linked-content-tabs + "content.tabs.link", + + # With this feature enabled users can add tooltips to links that will be + # displayed when the mouse pointer hovers the link. + # https://zensical.org/docs/authoring/tooltips/#improved-tooltips + "content.tooltips", + + # With this feature enabled, Zensical will automatically hide parts + # of the header when the user scrolls past a certain point. + # https://zensical.org/docs/setup/header/#automatic-hiding + # "header.autohide", + + # Turn on this feature to expand all collapsible sections in the + # navigation sidebar by default. + # https://zensical.org/docs/setup/navigation/#navigation-expansion + # "navigation.expand", + + # This feature turns on navigation elements in the footer that allow the + # user to navigate to a next or previous page. + # https://zensical.org/docs/setup/footer/#navigation + "navigation.footer", + + # When section index pages are enabled, documents can be directly attached + # to sections, which is particularly useful for providing overview pages. + # https://zensical.org/docs/setup/navigation/#section-index-pages + "navigation.indexes", + + # When instant navigation is enabled, clicks on all internal links will be + # intercepted and dispatched via XHR without fully reloading the page. + # https://zensical.org/docs/setup/navigation/#instant-navigation + "navigation.instant", + + # With instant prefetching, your site will start to fetch a page once the + # user hovers over a link. This will reduce the perceived loading time + # for the user. + # https://zensical.org/docs/setup/navigation/#instant-prefetching + "navigation.instant.prefetch", + + # In order to provide a better user experience on slow connections when + # using instant navigation, a progress indicator can be enabled. + # https://zensical.org/docs/setup/navigation/#progress-indicator + #"navigation.instant.progress", + + # When navigation paths are activated, a breadcrumb navigation is rendered + # above the title of each page + # https://zensical.org/docs/setup/navigation/#navigation-path + "navigation.path", + + # When pruning is enabled, only the visible navigation items are included + # in the rendered HTML, reducing the size of the built site by 33% or more. + # https://zensical.org/docs/setup/navigation/#navigation-pruning + #"navigation.prune", + + # When sections are enabled, top-level sections are rendered as groups in + # the sidebar for viewports above 1220px, but remain as-is on mobile. + # https://zensical.org/docs/setup/navigation/#navigation-sections + #"navigation.sections", + + # When tabs are enabled, top-level sections are rendered in a menu layer + # below the header for viewports above 1220px, but remain as-is on mobile. + # https://zensical.org/docs/setup/navigation/#navigation-tabs + #"navigation.tabs", + + # When sticky tabs are enabled, navigation tabs will lock below the header + # and always remain visible when scrolling down. + # https://zensical.org/docs/setup/navigation/#sticky-navigation-tabs + #"navigation.tabs.sticky", + "navigation.tabs", + "navigation.tabs.sticky", + + # A back-to-top button can be shown when the user, after scrolling down, + # starts to scroll up again. + # https://zensical.org/docs/setup/navigation/#back-to-top-button + "navigation.top", + + # When anchor tracking is enabled, the URL in the address bar is + # automatically updated with the active anchor as highlighted in the table + # of contents. + # https://zensical.org/docs/setup/navigation/#anchor-tracking + "navigation.tracking", + + # When search highlighting is enabled and a user clicks on a search result, + # Zensical will highlight all occurrences after following the link. + # https://zensical.org/docs/setup/search/#search-highlighting + "search.highlight", + + # When anchor following for the table of contents is enabled, the sidebar + # is automatically scrolled so that the active anchor is always visible. + # https://zensical.org/docs/setup/navigation/#anchor-following + # "toc.follow", + + # When navigation integration for the table of contents is enabled, it is + # always rendered as part of the navigation sidebar on the left. + # https://zensical.org/docs/setup/navigation/#navigation-integration + #"toc.integrate", +] + +# ---------------------------------------------------------------------------- +# You can configure your own logo to be shown in the header using the "logo" +# option in the "theme" subsection. The logo must be a relative path to a file +# in your "docs_dir", e.g., to use `docs/assets/logo.png` you would set: +# ---------------------------------------------------------------------------- +logo = "assets/logo.svg" + +# ---------------------------------------------------------------------------- +# If you don't have a dedicated project logo, you can use a built-in icon from +# the icon sets shipped in Zensical. Please note that the setting lives in a +# different subsection, and that the above take precedence over the icon. +# +# Read more: +# - https://zensical.org/docs/setup/logo-and-icons +# - https://github.com/zensical/ui/tree/master/dist/.icons +# ---------------------------------------------------------------------------- +#[project.theme.icon] +#logo = "lucide/smile" + +# ---------------------------------------------------------------------------- +# In the "font" subsection you can configure the fonts used. By default, fonts +# are loaded from Google Fonts, giving you a wide range of choices from a set +# of suitably licensed fonts. There are options for a normal text font and for +# a monospaced font used in code blocks. +# ---------------------------------------------------------------------------- +#[project.theme.font] +#text = "Inter" +#code = "Jetbrains Mono" + +# ---------------------------------------------------------------------------- +# In the "palette" subsection you can configure options for the color scheme. +# You can configure different color schemes, e.g., to turn on dark mode, +# that the user can switch between. Each color scheme can be further +# customized. +# +# Read more: +# - https://zensical.org/docs/setup/colors/ +# ---------------------------------------------------------------------------- +[[project.theme.palette]] +media = "(prefers-color-scheme)" +toggle.icon = "lucide/sun-moon" +toggle.name = "Switch to light mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: light)" +scheme = "default" +toggle.icon = "lucide/sun" +toggle.name = "Switch to dark mode" +primary = "deep orange" +accent = "orange" + +[[project.theme.palette]] +media = "(prefers-color-scheme: dark)" +scheme = "slate" +toggle.icon = "lucide/moon" +toggle.name = "Switch to system preference" +primary = "orange" +accent = "deep orange" + +# ---------------------------------------------------------------------------- +# The "extra" section contains miscellaneous settings. +# ---------------------------------------------------------------------------- +#[[project.extra.social]] +#icon = "fontawesome/brands/github" +#link = "https://github.com/user/repo" + +[project.extra.analytics] +provider = "telemetrydeck" + +# ---------------------------------------------------------------------------- +# In this section you can configure the Markdown extensions that are used when +# rendering your documentation. We enable the most useful extensions by default, +# but you can customize this list to your needs. +# +# Read more: +# - https://zensical.org/docs/setup/extensions/ +# ---------------------------------------------------------------------------- +[project.markdown_extensions.abbr] +[project.markdown_extensions.admonition] +[project.markdown_extensions.attr_list] +[project.markdown_extensions.def_list] +[project.markdown_extensions.footnotes] +[project.markdown_extensions.md_in_html] +[project.markdown_extensions.toc] +permalink = true +[project.markdown_extensions.pymdownx.arithmatex] +generic = true +[project.markdown_extensions.pymdownx.betterem] +[project.markdown_extensions.pymdownx.caret] +[project.markdown_extensions.pymdownx.details] +[project.markdown_extensions.pymdownx.emoji] +emoji_generator = "zensical.extensions.emoji.to_svg" +emoji_index = "zensical.extensions.emoji.twemoji" +[project.markdown_extensions.pymdownx.highlight] +anchor_linenums = true +line_spans = "__span" +pygments_lang_class = true +[project.markdown_extensions.pymdownx.inlinehilite] +[project.markdown_extensions.pymdownx.keys] +[project.markdown_extensions.pymdownx.magiclink] +[project.markdown_extensions.pymdownx.mark] +[project.markdown_extensions.pymdownx.smartsymbols] +[project.markdown_extensions.pymdownx.superfences] +custom_fences = [ + { name = "mermaid", class = "mermaid", format = "pymdownx.superfences.fence_code_format" } +] +[project.markdown_extensions.pymdownx.tabbed] +alternate_style = true +combine_header_slug = true +[project.markdown_extensions.pymdownx.tasklist] +custom_checkbox = true +[project.markdown_extensions.pymdownx.tilde]