Gymterview
middle

Что такое Forge Storage API и Custom UI?

Forge Storage API — встроенное хранилище данных для Forge-приложений, позволяющее сохранять данные без собственной БД. Custom UI — возможность использовать полноценное React-приложение вместо декларативного UI Kit.

Типы хранилища

Тип Лимит Назначение
Key-Value (storage) 32 KB на ключ Конфигурация, маленькие данные
Entity Storage 32 KB на entity, queryable Структурированные данные с поиском
Secrets 4 KB Токены, пароли

Key-Value Storage

Пример
import { storage } from '@forge/api';

await storage.set('config:PROJ', { enabled: true, maxRetries: 3 });
const config = await storage.get('config:PROJ');
await storage.delete('config:PROJ');

// Список ключей (с пагинацией)
const result = await storage.query()
    .where('key', startsWith('config:'))
    .limit(20)
    .getMany();

Entity Storage (структурированное хранилище)

Пример
// Сохранение entity
await storage.entity('task-record').set('task-001', {
    projectKey: 'PROJ',
    status: 'active',
    score: 85,
    createdAt: Date.now()
});

// Поиск по атрибутам
const activeTasks = await storage.entity('task-record')
    .query()
    .index('projectKey')
    .where('projectKey', 'PROJ')
    .sort('createdAt', 'DESC')
    .limit(50)
    .getMany();

Custom UI (React-приложение)

Пример
// static/my-custom-ui/src/App.tsx
import React, { useEffect, useState } from 'react';
import { invoke, view } from '@forge/bridge';
import Button from '@atlaskit/button';

const App: React.FC = () => {
    const [details, setDetails] = useState(null);

    useEffect(() => {
        invoke('getIssueDetails').then(setDetails);
    }, []);

    return (
        <div style={{ padding: '16px' }}>
            <h3>{details?.summary}</h3>
            <p>Статус: {details?.status}</p>
            <Button appearance="primary" onClick={() => invoke('saveScore')}>
                Сохранить
            </Button>
        </div>
    );
};

Bridge API (@forge/bridge)

Пример
import { invoke, view, router } from '@forge/bridge';

const result = await invoke('functionName', { param1: 'value' });
const context = await view.getContext();
const issueKey = context.extension.issue.key;
await router.navigate('/browse/PROJ-123');

Частые ошибки

  • Хранение большого объёма данных в Storage (>32KB) — ошибка при записи
  • Не использовать Entity Storage для данных, по которым нужен поиск — Key-Value не поддерживает query
  • Не собирать Custom UI перед forge deploy — забыть npm run build в static/
  • Прямые вызовы Jira REST API из Custom UI (frontend) — нужно через resolver (backend)

Как используется в 2026

  • Entity Storage стал основным способом хранения данных в Forge
  • Custom UI + Atlaskit — стандарт для сложных UI
  • Forge Remote Backend — позволяет вызывать внешние серверы из Forge
  • Storage API получил улучшенные лимиты и возможности запросов

На собеседовании: разграничьте Key-Value (простое, без поиска) и Entity Storage (с индексами и query). Custom UI даёт полную свободу дизайна через React + Atlaskit, UI Kit проще для стандартных сценариев. Все вызовы API из Custom UI идут через resolver (backend), а не напрямую.