Skip to content

sherlockscore/forge-todo-example-with-analytics-app

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

64 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Forge Todo App with Privacy-First Analytics

This project contains a Forge custom UI app written in React that displays in a Jira issue panel. This analytics branch demonstrates best-in-class implementation of privacy-aware analytics in Atlassian Forge applications.

πŸ” Privacy-First: No PII or end-user data is transmitted from the frontend. All analytics are processed through Forge backend resolvers with proper egress permissions.

πŸ“Š Event Tracking: Comprehensive tracking of user interactions (CRUD operations, UI events) and automated daily group analytics.

πŸ—οΈ Type-Safe Architecture: Modular, maintainable analytics system with clear separation between frontend events, backend processing, and external API integration.

See developer.atlassian.com/platform/forge/ for documentation and tutorials explaining Forge.

Todo app for Jira

Requirements

See Set up Forge for instructions to get set up.

Quick start

Register the app

  • Register the app by running:
forge register

Configure the environment

This app supports the following environment variables. It is recommended to set the variables in each environment as you progress through your development process. Note that changing variables requires a redeploy.

See the Forge documentation for more information on environment variables.

See the Forge CLI documentation for more information on using variables.

ANALYTICS_API_KEY

Set the Accoil API key in the environment. This is available from your account in Accoil. For example in development:

forge variables set --environment development ANALYTICS_API_KEY <your_api_key>

ANALYTICS_DEBUG

Set this to true and no real calls will be made to Accoil, instead it will log lines like:

Running analytics in debug. The following payload would be sent to https://in.accoil.com/v1/users:
{"user_id":"xxxx","group_id":"xxxx","traits":{"name":"xxxx"},"api_key":"xxxx","timestamp":1749789914400}

Set or unset with

forge variables set --environment development ANALYTICS_DEBUG true
forge variables unset --environment development ANALYTICS_DEBUG

ANALTYICS_USER_ID_OVERRIDE

Set this to true to always send the group ID in place of user IDs. This is useful for reducing Monthly Tracked Users (MTU) costs by consolidating user identification at the instance level.

Set or unset with

forge variables set --environment development ANALTYICS_USER_ID_OVERRIDE true
forge variables unset --environment development ANALTYICS_USER_ID_OVERRIDE

Analytics Architecture

This Forge app implements a comprehensive, privacy-aware analytics system that demonstrates best practices for Atlassian Forge applications. The analytics architecture follows Atlassian's privacy guidelines and ensures no PII or end-user data leaves the frontend.

πŸ—οΈ Architecture Overview

Frontend (React)
β”‚
β”œβ”€β”€ Analytics Events (static/spa/src/analytics/events.js)
β”‚   └── trackTodoItemsLoaded()
β”‚
└── Forge Bridge β†’ Backend Resolvers (Frontend communication only)
                   β”‚
                   β”œβ”€β”€ Track Events (src/analytics/resolvers.js)
                   └── Event Queue (src/analytics/events.js)
                       β”‚
                       β”œβ”€β”€ Queue Consumer Function (src/analytics/consumer.js)
                       β”‚   └── Direct function handler (Forge Events 2.0)
                       β”‚
                       β”œβ”€β”€ Event Dispatcher (src/analytics/dispatcher.js)
                       └── Scheduled Jobs (src/analytics/schedule.js)
                           β”‚
                           └── Accoil API (https://in.accoil.com)

πŸ“Š Event Types

The app tracks the following events automatically:

Frontend Events:

  • Todos Loaded - When the app loads todo items from storage

Backend Events (CRUD Operations):

  • Todo Created - When a new todo item is created
  • Todo Updated - When a todo item is modified (checked/unchecked)
  • Todo Deleted - When a single todo item is deleted
  • Todos Cleared - When all todo items are deleted

Scheduled Events:

  • Daily group analytics with license information and activity status

πŸ” Privacy & Compliance

The analytics implementation adheres to Atlassian's privacy requirements:

  • No Frontend Egress: All analytics requests originate from the backend
  • No PII Transmission: Only uses Atlassian-generated IDs (accountId, cloudId)
  • No End User Data (EUD) Leakage: By routing all events through the backend, we prevent:
    • User IP addresses from being logged (only Atlassian Forge server IPs are seen)
    • Browser referrer information that might contain sensitive URLs
    • Client-side data that could inadvertently include PII
    • URL parameters or fragments that might contain user information
  • Proper Permissions: External fetch permissions explicitly defined in manifest
  • Category Compliance: Analytics endpoints marked with category: analytics and inScopeEUD: false

🧩 Implementation Details

For comprehensive implementation guidance and best practices, see:

Frontend Integration

The frontend uses a simple event tracking pattern:

import { trackTodoItemsLoaded } from "./analytics/events";

// Track when data is loaded
invoke('get-all').then((values) => {
  trackTodoItemsLoaded();
  setTodos(values);
});

Backend Processing

Backend events are automatically triggered on CRUD operations:

// In src/index.js resolvers
resolver.define('create', async ({ payload, context }) => {
  await trackCreate(context);  // Analytics tracking
  // ... business logic
});

Event Queue System (Forge Events 2.0)

The analytics system uses Forge Events 2.0 for reliable delivery:

  1. Events are queued using @forge/events Queue
  2. Consumer function processes events asynchronously via direct function declaration in manifest.yml
    • No resolvers needed - uses handler export pattern
    • Configured as: handler: analytics/consumer.handler
  3. Dispatcher handles different event types (track, identify, group)
  4. HTTP requests are sent to Accoil API with proper error handling

Note: Resolvers are only used for frontend-to-backend communication. Event consumers use direct function handlers as per Forge Events 2.0 architecture.

Identity Management

The system provides flexible identity resolution:

  • User ID: Uses context.accountId by default
  • Group ID: Uses context.cloudId for instance-level tracking
  • Override Mode: Can substitute group ID for user ID to reduce MTU costs

πŸ› οΈ Analytics Configuration

Debug Mode

Enable debug mode to test analytics without sending real data:

forge variables set --environment development ANALYTICS_DEBUG true

This will log payloads instead of sending HTTP requests:

Running analytics in debug. The following payload would be sent to https://in.accoil.com/v1/events:
{"user_id":"123","event":"Todo Created","api_key":"[REDACTED]","timestamp":1749789914400}

Cost Optimization

Use the user ID override to reduce Monthly Tracked Users:

forge variables set ANALTYICS_USER_ID_OVERRIDE true

This sends the cloudId as the user ID for all events, consolidating tracking at the instance level.

πŸ“ File Structure

src/analytics/
β”œβ”€β”€ events.js      # Backend event definitions and queue management
β”œβ”€β”€ resolvers.js   # Frontend-backend bridge (resolvers for UI communication only)
β”œβ”€β”€ dispatcher.js  # HTTP dispatch logic for Accoil API
β”œβ”€β”€ consumer.js    # Queue consumer function (Forge Events 2.0 direct handler)
β”œβ”€β”€ schedule.js    # Scheduled job function for daily group analytics
└── utils.js       # Utility functions for context processing

static/spa/src/analytics/
└── events.js      # Frontend event definitions

🎯 Event Definition Strategy

The analytics system uses centralized event definition files to maintain consistency and discoverability:

Backend Events (src/analytics/events.js):

  • Single source of truth for all backend analytics events
  • Easy auditing - see every tracked event in one place
  • Consistent naming conventions across the application
  • Simple discovery - no hunting through business logic code

Frontend Events (static/spa/src/analytics/events.js):

  • Centralized frontend event definitions
  • Mirror the backend pattern for consistency
  • Clear separation between UI and backend tracking

Event Naming Convention: All events follow the "Object Verb" format for consistency and clarity:

  • βœ… "Todo Created" (not "Create Todo" or "Backend: Create")
  • βœ… "Todos Loaded" (not "Load Todos" or "Todo Items Loaded")
  • βœ… "View Opened" (not "Open View" or "Page Viewed")

This approach ensures that adding, modifying, or auditing analytics events is straightforward and maintainable.

πŸ”„ Data Flow Examples

Frontend Event Flow

User loads app β†’ trackTodoItemsLoaded() β†’ invoke('track-event') β†’ 
Backend resolver β†’ Event queue β†’ Consumer β†’ Dispatcher β†’ Accoil API
                   ↑
              Privacy Barrier: Only Forge server IPs reach analytics provider

Backend Event Flow

User creates todo β†’ trackCreate(context) β†’ Event queue β†’ 
Consumer β†’ Dispatcher β†’ Accoil API

Scheduled Flow

Daily trigger β†’ dailyGroupAnalytics() β†’ Dispatcher β†’ Accoil API

πŸ§ͺ Testing Analytics

  1. Enable debug mode to see payload logs without sending requests
  2. Check Forge logs for analytics processing: forge logs --tail
  3. Verify queue processing by monitoring consumer function execution
  4. Test scheduled jobs by checking daily analytics execution

🎯 Extending Analytics

To add new events:

  1. Frontend events: Add functions to static/spa/src/analytics/events.js
  2. Backend events: Add functions to src/analytics/events.js
  3. Update dispatcher: Add handling in src/analytics/dispatcher.js
  4. Register resolvers: Add to src/index.js if needed

Example:

// Add to src/analytics/events.js
export const trackCustomEvent = async (context) => {
    await track(context, "Custom Event Name");
}

// For events with different naming patterns:
export const trackProjectCreated = async (context) => {
    await track(context, "Project Created");
}

export const trackUserInvited = async (context) => {
    await track(context, "User Invited");
}

Note: Accoil API does not support event properties, so all context and metadata must be encoded in the event name itself. Use descriptive event names that capture the essential information rather than relying on separate properties.

Frontend

  • Change into the frontend directory by running:
cd ./static/spa
  • Install your frontend dependencies by running:
npm install
  • Build your frontend by running:
npm run build

Deployment

For this section, ensure you have navigated back to the root of the repository.

  • Install the forge dependencies by running:
npm install
  • Build and deploy your app by running:
forge deploy
  • Install your app in an Atlassian site by running:
forge install

Support

See Get help for how to get help and provide feedback.

Contributions

Contributions are welcome! Please see CONTRIBUTING.md for details.

License

Copyright (c) 2025 Accoil, Atlassian and others. Apache 2.0 licensed, see LICENSE file.

About

A working example of how to implement best-practice, privacy-aware analytics in Atlassian Forge apps.

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Contributors