Architecture Overview
This document describes the system architecture of Lens (codebase: Foundation), a Project Portfolio Management app for Jira Cloud built on Atlassian Forge.
High-Level Architecture
Section titled “High-Level Architecture”+=========================================================================+| JIRA CLOUD INSTANCE || || +-------------------------------------------------------------------+ || | FORGE SANDBOX (per-installation isolation) | || | | || | +-------------------------------------------------------------+ | || | | Custom UI Layer (cross-origin iframe) | | || | | | | || | | +------------------+ +---------+ +-------------------+ | | || | | | AG Grid Engine | | dnd-kit | | Inline Editors | | | || | | | (tree data, | | (DnD | | (24 field types) | | | || | | | inline editing) | | layer) | +-------------------+ | | || | | +------------------+ +---------+ | | || | | | | || | | +------------------+ +-------------------+ | | || | | | Gantt/Timeline | | Pipeline Studio | | | || | | | (bars, deps, | | (sort, group, | | | || | | | critical path) | | filter) | | | || | | +------------------+ +-------------------+ | | || | | | | || | | @forge/bridge invoke() ──> all data operations | | || | | Forge Realtime subscribe() ──> live UI updates | | || | +------------------------------+------------------------------+ | || | | | || | +------------------------------v------------------------------+ | || | | Resolver Layer (Node.js 22.x, 25s timeout) | | || | | | | || | | +-------------+ +------------------+ +---------------+ | | || | | | Lens CRUD | | Hierarchy Engine | | Inline Edit | | | || | | +-------------+ +------------------+ +---------------+ | | || | | +-------------+ +------------------+ +---------------+ | | || | | | Views | | Permissions | | Jira Sync | | | || | | +-------------+ +------------------+ +---------------+ | | || | | +-------------+ +------------------+ +---------------+ | | || | | | Sync Agents | | Dependencies | | Resources | | | || | | +-------------+ +------------------+ +---------------+ | | || | +------------------------------+------------------------------+ | || | | | || | +------------------------------v------------------------------+ | || | | Rovo Agent Layer | | || | | | | || | | +------------------+ +----------------------------------+ | | || | | | Rovo Agent | | 35 Action Functions | | | || | | | (rovo:agent) | | (standalone + 5 dispatchers) | | | || | | | Chat sidebar | | GET / CREATE / UPDATE / DELETE | | | || | | +------------------+ +----------------------------------+ | | || | +--------------------------------------------------------------+ | || | | | || | +------------------------------v------------------------------+ | || | | Data Layer | | || | | | | || | | +---------------------+ +------------------------------+ | | || | | | Forge SQL (MySQL) | | Forge KVS | | | || | | | ─────────────────── | | ─────────────────────────── | | | || | | | lenses | | user:{id}:prefs | | | || | | | hierarchy_nodes | | rate_limit:hourly_points | | | || | | | generators | | foundation:schema_version | | | || | | | issue_cache | | announcements_dismissed:{id} | | | || | | | views | | feature_flags | | | || | | | lens_permissions | | | | | || | | | resources | | | | | || | | | teams / skills | | | | | || | | | baselines | | | | | || | | +---------------------+ +------------------------------+ | | || | +--------------------------------------------------------------+ | || | | | || | +------------------------------v------------------------------+ | || | | Event Processing Layer | | || | | | | || | | Jira Product Events ──> Issue Cache Updater | | || | | (avi:jira:created/updated/deleted:issue) (free, no points) | | || | | | | || | | Async Consumers (15-min timeout): | | || | | generator-queue ──> Sync Agent Executor | | || | | leveling-queue ──> Resource Leveling | | || | | import-queue ──> Data Import | | || | | wbs-queue ──> WBS Builder | | || | | | | || | | Scheduled Triggers: | | || | | hourly ──> Cache Integrity Check | | || | | daily ──> Health Check | | || | +--------------------------------------------------------------+ | || +-------------------------------------------------------------------+ || || +-------------------------------------------------------------------+ || | Jira REST API v3 (rate-limited: 65K-500K pts/hr) | || | Issue CRUD, JQL search, transitions, user search, permissions | || +-------------------------------------------------------------------+ |+=========================================================================+Design Philosophy: Server-Centric Architecture
Section titled “Design Philosophy: Server-Centric Architecture”The most important architectural decision in Lens is its server-centric design. The backend assembles complete views; the frontend is a thin rendering layer.
How it works:
- When a user opens a lens, the frontend calls a single resolver:
getLensView(lensId) - The backend performs all data assembly: permission check, tree loading, issue cache enrichment, Jira BROWSE filtering
- The backend returns a pre-assembled flat row array ready for AG Grid to render
- The frontend never calls Jira APIs directly — all data flows through the resolver layer
Why this matters:
- Jira API rate limits (65,000 points/hour in Tier 1) are a hard constraint. Every API call costs points. The event-driven issue cache means opening a lens costs zero Jira API points.
- The frontend runs in a Forge sandboxed iframe with limited capabilities. Complex data assembly in the frontend would be fragile and slow.
- Backend assembly enables permission filtering at the data layer, ensuring users never see issues they cannot access in Jira.
See Design Decisions for the full rationale.
Forge Platform Overview
Section titled “Forge Platform Overview”Lens runs entirely on Atlassian Forge, Atlassian’s cloud app platform. Forge provides:
| Capability | How Lens Uses It |
|---|---|
| Sandboxed Runtime | Node.js 22.x functions execute in Forge’s isolated environment. No self-hosted infrastructure. |
| Forge SQL | MySQL-compatible database (1 GiB per installation). Stores lenses, hierarchy, views, permissions, issue cache. Per-installation isolation enforced by the platform. |
| Forge KVS | Key-value store for lightweight data: user preferences, rate limit counters, schema version tracking, feature flags, announcement dismissals. |
| Product Events | Free event triggers (avi:jira:created/updated/deleted:issue) keep the issue cache fresh without consuming API points. |
| Async Events | Queue-based consumers with 15-minute timeout for long-running operations: sync agent execution, resource leveling, data imports, WBS generation. |
| Scheduled Triggers | Hourly cache integrity checks and daily health monitoring. |
| Custom UI | React apps served in cross-origin iframes with Forge bridge for resolver communication. |
| Forge Realtime | Pub/sub channels for live UI updates (e.g., when a sync agent completes). |
| Rovo Agent | AI agent accessible via Rovo Chat sidebar with 35 action functions for conversational CRUD. |
| Licensing | Built-in license enforcement via app.licensing.enabled: true in the manifest. |
Module Structure
Section titled “Module Structure”The app registers three Forge UI modules, each serving a different Jira integration point:
jira:globalPage — Main Application (key: fm)
Section titled “jira:globalPage — Main Application (key: fm)”The primary module. Uses layout: blank for full-page rendering. Routes:
| Route | Component | Purpose |
|---|---|---|
/ | LensListOrRedirect | Auto-redirects to last-viewed lens, or shows lens menu |
/lens/:id | LensView | Main grid + Gantt + toolbar + inspector |
/import | ImportHub | Data import wizard (CSV, competitor imports) |
/admin | AdminPage | Admin settings (cache, stats, CSS overrides) |
/structure/:id | StructureRedirect | Legacy URL redirect to /lens/:id |
jira:issueContext — Issue Sidebar Panel (key: fic)
Section titled “jira:issueContext — Issue Sidebar Panel (key: fic)”Shows which lenses contain a given Jira issue. Appears in the issue detail sidebar. Lightweight React app in static/issue-context/.
jira:adminPage — Admin Settings (key: fad)
Section titled “jira:adminPage — Admin Settings (key: fad)”Jira site admin configuration page. Accessed via Jira administration. React app in static/admin/.
Frontend Architecture
Section titled “Frontend Architecture”The frontend is a React 18 + TypeScript application. It has no state management library — all state is managed with useState, useCallback, and useMemo.
Component Hierarchy
Section titled “Component Hierarchy”App.tsx ErrorBoundary IntlProvider (react-intl, 11 locales) FeatureFlagProvider RovoProvider LicenseBanner AnnouncementPopup Router (React Router v6, Forge bridge history) LensListOrRedirect / LensMenu LensView <-- owns all data state Toolbar ColumnPicker ViewSwitcher SortGroupPresets ZoomControls HierarchyGrid (AG Grid) <-- controlled component Inline Editors (8 types) Column Header Menus Gantt/Timeline Dependency Arrows Rollup Brackets InspectorPanel AccordionSections PipelineStudio (sort/group/filter) GeneratorPanel BulkActions CommandPalette (Cmd+K)Key Design Rules
Section titled “Key Design Rules”LensViewowns all data state. Child components receive props and callbacks.HierarchyGridnever fetches its own data.- No new CSS files. All styles go in
foundation.cssor inline for dynamic values. Use--ds-*Atlassian Design System tokens with hex fallbacks. - Density-aware. Five density levels (comfortable, standard, compact, supercompact, micro) via parent CSS class selectors.
- Feature-organized directories. Components live under
components/Grid/,components/Toolbar/,components/Gantt/, etc. Tests in__tests__/subdirectories. - AG Grid specifics:
suppressRowHoverHighlight={true}(CSS:hoverinstead of JS hover),stopEditingWhenCellsLoseFocus={false}(manual click-outside handling due to Forge iframe).
AG Grid Integration
Section titled “AG Grid Integration”AG Grid is the core rendering engine. Lens uses its Enterprise features:
- Tree Data: Hierarchical rows with expand/collapse, arbitrary depth
- Inline Editing: 24 cell editor types — 8 core (text, number, date, status, priority, assignee, sprint, labels) + 16 custom field editors (see Inline Editing Guide)
- Column Management: Dynamic column definitions built by
buildColumnDefs()incolumns.tsx - Row Grouping: Group rows by status, priority, assignee, etc.
- Custom Cell Renderers: Issue links, user cards, rich text (ADF), progress bars
Backend Architecture
Section titled “Backend Architecture”Resolver Pattern
Section titled “Resolver Pattern”All frontend-to-backend communication goes through the Forge resolver. The frontend calls invoke('resolverName', payload) via @forge/bridge, and the backend dispatches to the matching handler.
foundation/src/index.ts -- Resolver registry (100+ handlers) defineResolver('name', handler) -- Registers each resolver with optional metrics wrappingResolvers are organized by domain:
| Domain | Key Resolvers | Source |
|---|---|---|
| Lens CRUD | listLenses, createLens, deleteLens, updateLens | resolvers/lenses.ts |
| Hierarchy | getLensView, addIssueById, moveNode, deleteNode, addFlexItem | resolvers/hierarchy.ts |
| Inline Edit | updateField, getTransitions, getPriorities | resolvers/inline-edit.ts |
| Views | listViews, createView, updateView, deleteView, setDefaultView | resolvers/views.ts |
| Sync Agents | createSyncAgent, executeSyncAgents, listSyncAgents | resolvers/sync-agents.ts |
| Permissions | getPermissions, grantPermission, revokePermission | resolvers/permissions.ts |
| Dependencies | createIssueLink, deleteIssueLink, getDependencyLags | resolvers/dependencies.ts |
| Admin | getAdminStats, refreshCache, resetAllData | resolvers/admin.ts |
| Resources | getResourceWorkload, startLeveling, teams, skills, baselines | Multiple files |
| Import | previewCsvImport, executeStructureImport, competitor imports | Multiple files |
Resolver Guards
Section titled “Resolver Guards”Resolvers use composable guard middleware:
// Compose guards: schema check + admin checkdefineResolver('getAdminStats', compose(withSchema, withAdmin)(getAdminStatsHandler));
// Guards available:// withSchema -- ensures database schema is at current version// withAuth -- verifies user is authenticated// withAdmin -- verifies user is Jira site admin// withLicense -- checks app license is active// withLensPermission('view'|'edit'|'control') -- checks lens-level ACLServices Layer
Section titled “Services Layer”Business logic lives in src/services/, separate from resolver handlers:
| Service | Purpose |
|---|---|
hierarchy-engine.ts | Tree manipulation with gap=100 position strategy |
cache-manager.ts | Issue cache CRUD (upsert with ON DUPLICATE KEY) |
permission-service.ts | checkPermission() + group/role resolution |
view-service.ts | View persistence + default view creation |
generator-service.ts | Sync agent execution queue + job tracking |
jira-sync.ts | Jira API fetch + custom field parsing |
realtime.ts | Forge Realtime channel publish |
error-reporter.ts | Sentry error reporting |
resolver-metrics.ts | Per-resolver timing and performance tracking |
Event Handlers
Section titled “Event Handlers”Event-driven processing keeps the app responsive and the cache fresh:
| Handler | Trigger | Purpose |
|---|---|---|
issue-handler.ts | avi:jira:created/updated/deleted:issue, issuelink events | Sync issue cache, trigger auto-sync for affected lenses |
sprint-handler.ts | avi:jira:started/closed/updated:sprint | Update sprint fields on cached issues |
cache-integrity.ts | Hourly scheduled trigger | Detect stale entries, clean orphans |
sync-agent-executor.ts | generator-queue consumer | Execute sync agents (JQL fetch, 15-min timeout) |
leveling-executor.ts | leveling-queue consumer | Resource leveling calculations |
import-executor.ts | import-queue consumer | Background data import processing |
wbs-executor.ts | wbs-queue consumer | AI-driven WBS generation |
daily-health-check.ts | Daily scheduled trigger | Installation health monitoring |
uninstall-handler.ts | avi:forge:uninstalled:app | Cleanup on app uninstall |
Database: Forge SQL (Schema v32)
Section titled “Database: Forge SQL (Schema v32)”Forge SQL provides a MySQL-compatible database, isolated per installation (platform-enforced). Current schema version is 32, managed by a sequential migration system.
Core Tables:
| Table | Key Columns | Purpose |
|---|---|---|
lenses | id, name, owner_account_id, archived | Top-level lens definitions |
hierarchy_nodes | lens_id, parent_id, position, node_type, jira_issue_id | Tree structure (adjacency list, gap=100 positioning) |
generators | lens_id, generator_type, config (JSON), parent_node_id | Sync agent definitions |
issue_cache | jira_issue_id, jira_issue_key, 30+ cached fields, custom_fields (JSON) | Local copy of Jira issue data |
views | lens_id, columns (JSON), settings_json, is_default | View configurations |
lens_permissions | lens_id, grantee_type, grantee_id, permission_level | Access control lists |
resources | jira_account_id, hours_per_day, efficiency_multiplier | Resource management |
teams | name, members | Team organization |
baselines | lens_id, created_at, entries | Baseline snapshots |
Schema Migration System:
CURRENT_SCHEMA_VERSIONinsrc/db/migrations.ts(currently 32)ensureSchema()runs on every Forge cold-start, reads version from KVS- Fresh installs: CREATE TABLE statements in
src/db/schema.ts - Existing installs: sequential migrations (v2 through v32)
safeDDL()wrapper for idempotent ALTER TABLE operations- KVS-based distributed lease prevents concurrent migration runs
Rovo Agent
Section titled “Rovo Agent”The Rovo Agent provides conversational AI access via the Rovo Chat sidebar in Jira. It has 35 action functions organized as:
- 29 standalone actions (direct function handlers)
- 6 dispatcher actions that route via
_actionparameter:lensCrud— lens create/rename/delete (3 sub-actions)hierMeta— hierarchy metadata operations (10 sub-actions)viewPerm— view and permission CRUD (5 sub-actions)jiraUtil— JQL validation, project analysis (3 sub-actions)r2g— read-only intelligence/analytics (19 sub-actions, GET verb, no confirmation)r2r5— destructive operations (14 sub-actions, TRIGGER verb, requires confirmation)
The dispatcher pattern exists to work around the CaaS manifest byte size limit (~257KB). See Design Decisions.
Security Model
Section titled “Security Model”Three-Tier Permission Model
Section titled “Three-Tier Permission Model”Lens uses a three-tier permission system (the edit_generators level was collapsed into edit in schema v30):
| Level | Capabilities |
|---|---|
| View | See the lens and its data (read-only) |
| Edit | View + rearrange, add, remove items, flex items, create/modify/delete sync agents |
| Control | Edit + change settings, permissions, archive, delete |
Resolution order:
- Lens owner automatically has Control
- Jira site admins automatically have Control
- Explicit grants checked: user > group > role > everyone (highest level wins)
Issue-Level Visibility
Section titled “Issue-Level Visibility”The issue cache stores data fetched as the app. When a user opens a lens, the backend filters the row set based on the user’s Jira permissions:
- Batch JQL permission check:
api.asUser()verifies BROWSE permission - Results cached in KVS with 30-minute TTL
- Issues the user cannot access in Jira are excluded from the response
- Fail-open strategy: If Jira API is unreachable, assume all issues visible (prevents outage from blocking the entire app)
- Hidden parent cascades to children
Platform Security
Section titled “Platform Security”- Forge sandbox isolation: Each installation has a separate database. Code runs in Forge’s isolated runtime.
- CSP enforcement: Custom UI runs in a sandboxed iframe with strict Content Security Policy. No external scripts.
- Parameterized SQL: All queries use parameterized statements, never string concatenation.
- No PII in logs: No user emails, display names, or account IDs in log output.
- License enforcement:
app.licensing.enabled: truein manifest. FrontendLicenseBannercomponent. Backend resolvers detect and surfacelicense_inactiveerrors.
Key Platform Constraints
Section titled “Key Platform Constraints”These hard limits shape the architecture. They are enforced by Forge and Jira, not by application code.
| Constraint | Limit | Impact |
|---|---|---|
| Standard function timeout | 25 seconds | All resolver calls must complete within 25s. Complex operations use async consumers. |
| Async event timeout | 900 seconds (15 min) | Sync agent execution, resource leveling, imports run as async consumers. |
| Frontend response payload | 5 MB | A lens with 1,000 issues fits within this limit. Larger lenses may need pagination. |
| Forge SQL storage | 1 GiB per installation | At ~10 MB for a moderate deployment, this leaves 99% headroom. |
| Forge SQL DML rate | 150 operations/second | Bulk operations must batch and throttle writes. |
| Jira API rate limit (Tier 1) | 65,000 points/hour (global) | Shared across ALL installations. The event-driven cache is the primary mitigation. |
| Jira API rate limit (Tier 2) | 100K-500K points/hour (per-tenant) | Granted to Lens. Per-tenant isolation prevents noisy-neighbor problems. |
| Invocation limits | 1,200/user/min, 5,000/install/min | Sets an upper bound on how frequently the frontend can call resolvers. |
| CaaS manifest byte limit | ~257KB (converted format) | Limits the number of Rovo action entries. Currently at 35 actions, ~22,850 bytes. |
| Forge Realtime | 60 messages/channel/sec, 32 KB payload | Sufficient for live update notifications. Preview status. |
What to Read Next
Section titled “What to Read Next”- Data Flow — Detailed walkthrough of every data path through the system
- Design Decisions — Why each architectural choice was made
- Backend API Reference — Full resolver and service documentation
- Frontend Guide — Component architecture and patterns