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.
See Set up Forge for instructions to get set up.
- Register the app by running:
forge register
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.
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>
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
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
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.
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)
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 createdTodo Updated- When a todo item is modified (checked/unchecked)Todo Deleted- When a single todo item is deletedTodos Cleared- When all todo items are deleted
Scheduled Events:
- Daily group analytics with license information and activity status
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: analyticsandinScopeEUD: false
For comprehensive implementation guidance and best practices, see:
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 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
});The analytics system uses Forge Events 2.0 for reliable delivery:
- Events are queued using
@forge/eventsQueue - Consumer function processes events asynchronously via direct function declaration in
manifest.yml- No resolvers needed - uses
handlerexport pattern - Configured as:
handler: analytics/consumer.handler
- No resolvers needed - uses
- Dispatcher handles different event types (track, identify, group)
- 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.
The system provides flexible identity resolution:
- User ID: Uses
context.accountIdby default - Group ID: Uses
context.cloudIdfor instance-level tracking - Override Mode: Can substitute group ID for user ID to reduce MTU costs
Enable debug mode to test analytics without sending real data:
forge variables set --environment development ANALYTICS_DEBUG trueThis 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}
Use the user ID override to reduce Monthly Tracked Users:
forge variables set ANALTYICS_USER_ID_OVERRIDE trueThis sends the cloudId as the user ID for all events, consolidating tracking at the instance level.
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
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.
User loads app β trackTodoItemsLoaded() β invoke('track-event') β
Backend resolver β Event queue β Consumer β Dispatcher β Accoil API
β
Privacy Barrier: Only Forge server IPs reach analytics provider
User creates todo β trackCreate(context) β Event queue β
Consumer β Dispatcher β Accoil API
Daily trigger β dailyGroupAnalytics() β Dispatcher β Accoil API
- Enable debug mode to see payload logs without sending requests
- Check Forge logs for analytics processing:
forge logs --tail - Verify queue processing by monitoring consumer function execution
- Test scheduled jobs by checking daily analytics execution
To add new events:
- Frontend events: Add functions to
static/spa/src/analytics/events.js - Backend events: Add functions to
src/analytics/events.js - Update dispatcher: Add handling in
src/analytics/dispatcher.js - Register resolvers: Add to
src/index.jsif 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.
- 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
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
See Get help for how to get help and provide feedback.
Contributions are welcome! Please see CONTRIBUTING.md for details.
Copyright (c) 2025 Accoil, Atlassian and others. Apache 2.0 licensed, see LICENSE file.
