mcpField
E
experience-ui-bundle-file-upload-generate

experience-ui-bundle-file-upload-generate

github.com/forcedotcom開発1.1k5.2k 回インストール1 日前に更新
リポジトリ

experience-ui-bundle-file-upload-generate について

Salesforce を使う React UI bundle にファイルアップロードを追加するワークフロースキルです。アップロード API と進捗コールバックを提供し、UI は開発者が独自に構築します。

主な機能

  1. 01コールバックによる進捗追跡
  2. 02Salesforce レコードへのファイル関連付け
  3. 03レコード関連付けの延期
  4. 04AbortController によるアップロード中止
  5. 05現在のユーザー ID の取得

活用シーン

  1. 01既存レコードへのファイル添付
  2. 02レコード作成前のファイルアップロード
  3. 03独自のアップロード・進捗 UI の構築

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/forcedotcom/sf-skills --skill experience-ui-bundle-file-upload-generate

インストール後、Claude Code で/experience-ui-bundle-file-upload-generateを入力して読み込まれたか確認します。

詳細

ファイルアップロード API(ワークフロー)

ユーザーが React UI バンドルでファイルアップロード機能を必要としている場合は、このワークフローに従ってください。この機能が提供するのは API のみ です。提供される API を使って、UI コンポーネントを独自に構築してください。

重要:これは API 専用パッケージです

このパッケージがエクスポートするのは、プログラムから利用する API であり、React コンポーネントやフックではありません。以下を行います。

  • upload() 関数を使って、進捗を追跡しながらファイルをアップロードする
  • ファイル入力欄、ドロップゾーン、プログレスバーなど、独自の UI を構築する
  • onProgress コールバックを通じてアップロードの進捗を追跡する

次のことはしないでください。

  • <FileUpload /> のようなビルド済みコンポーネントを期待する。エクスポートされていません。
  • useFileUpload のような React フックをインポートしようとする。エクスポートされていません。
  • ドロップゾーンコンポーネントを探す。エクスポートされていません。

ソースコードにはデモ用の参考コンポーネントが含まれていますが、インポートして利用することはできません。これらを例として、独自の UI を構築してください。

1. パッケージをインストールする

npm install @salesforce/ui-bundle-template-feature-react-file-upload

依存関係は自動的にインストールされます。

  • @salesforce/ui-bundle(API クライアント)
  • @salesforce/platform-sdk(データ SDK。旧称の @salesforce/sdk-data は廃止されています。experience-ui-bundle-salesforce-data-access スキルを参照してください)

2. 3 つのアップロードパターンを理解する

パターン A:基本的なアップロード(レコードとの紐付けなし)

ファイルを Salesforce にアップロードし、各ファイルの contentBodyId を取得します。ContentVersion レコードは作成されません。

使用するケース:

  • 先にファイルをアップロードし、後からレコードを作成または紐付けする場合
  • レコードがまだ存在しない複数ステップのフォームを作成する場合
  • レコードとの紐付けを後回しにするシナリオ
import { upload } from "@salesforce/ui-bundle-template-feature-react-file-upload";

const results = await upload({
  files: [file1, file2],
  onProgress: (progress) => {
    console.log(`${progress.fileName}: ${progress.status} - ${progress.progress}%`);
  },
});

// results[0].contentBodyId: "069..." (always available)
// results[0].contentVersionId: undefined (no record linked)
パターン B:アップロードと同時にレコードに紐付ける

ファイルをアップロードし、ContentVersion レコードを作成して既存の Salesforce レコードにすぐに紐付けます。

使用するケース:

  • レコード(Account、Opportunity、Case など)がすでに存在する場合
  • ファイルをすぐにレコードに添付したい場合
  • アップロードと添付を直接行うシナリオ
import { upload } from "@salesforce/ui-bundle-template-feature-react-file-upload";

const results = await upload({
  files: [file1, file2],
  recordId: "001xx000000yyyy", // Existing record ID
  onProgress: (progress) => {
    console.log(`${progress.fileName}: ${progress.status} - ${progress.progress}%`);
  },
});

// results[0].contentBodyId: "069..." (always available)
// results[0].contentVersionId: "068..." (linked to record)
パターン C:レコードとの紐付けを後回しにする(レコード作成フロー)

レコードを指定せずにファイルをアップロードし、レコードの作成後に紐付けます。

使用するケース:

  • 「添付ファイル付きのレコードを作成」するフォームを構築する場合
  • フォームを送信するまでレコードが存在しない場合
  • 最終的なレコード ID がわかる前にファイルをアップロードする必要がある場合
import {
  upload,
  createContentVersion,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";

// Step 1: Upload files (no recordId)
const uploadResults = await upload({
  files: [file1, file2],
  onProgress: (progress) => console.log(progress),
});

// Step 2: Create the record
const newRecordId = await createRecord(formData);

// Step 3: Link uploaded files to the new record
for (const file of uploadResults) {
  const contentVersionId = await createContentVersion(
    new File([""], file.fileName),
    file.contentBodyId,
    newRecordId,
  );
}

3. 独自の UI を構築する

このパッケージが提供するのはバックエンド機能です。フロントエンドは独自に構築してください。最小限の例を以下に示します。

import {
  upload,
  type FileUploadProgress,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";
import { useState } from "react";

function CustomFileUpload({ recordId }: { recordId?: string }) {
  const [progress, setProgress] = useState<Map<string, FileUploadProgress>>(new Map());

  const handleFileSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(event.target.files || []);

    await upload({
      files,
      recordId,
      onProgress: (fileProgress) => {
        setProgress((prev) => new Map(prev).set(fileProgress.fileName, fileProgress));
      },
    });
  };

  return (
    <div>
      <input type="file" multiple onChange={handleFileSelect} />

      {Array.from(progress.entries()).map(([fileName, fileProgress]) => (
        <div key={fileName}>
          {fileName}: {fileProgress.status} - {fileProgress.progress}%
          {fileProgress.error && <span>Error: {fileProgress.error}</span>}
        </div>
      ))}
    </div>
  );
}

4. アップロードの進捗を追跡する

onProgress コールバックは、各ファイルの処理段階に応じて複数回呼び出されます。

ステータスタイミング進捗値
"pending"ファイルがアップロード待ちのキューに入ったとき0
"uploading"アップロード中(XHR)0-100(パーセント)
"processing"ContentVersion を作成しているとき(recordId が指定されている場合)0
"success"アップロード完了100
"error"アップロード失敗0

必ず視覚的なフィードバックを提供してください。

  • ファイル名を表示する
  • 現在のステータスを表示する
  • ステータスが「uploading」の場合はプログレスバーを表示する
  • ステータスが「error」の場合はエラーメッセージを表示する

5. アップロードをキャンセルする(任意)

ユーザーがアップロードをキャンセルできるようにするには、AbortController を使います。

const abortController = new AbortController();

const handleUpload = async (files: File[]) => {
  try {
    await upload({
      files,
      signal: abortController.signal,
      onProgress: (progress) => console.log(progress),
    });
  } catch (error) {
    console.error("Upload cancelled or failed:", error);
  }
};

const cancelUpload = () => {
  abortController.abort();
};

6. 現在のユーザーに紐付ける(特別なケース)

ユーザーが自分のプロフィールまたは個人ライブラリにファイルをアップロードしたい場合:

import {
  upload,
  getCurrentUserId,
} from "@salesforce/ui-bundle-template-feature-react-file-upload";

const userId = await getCurrentUserId();
await upload({ files, recordId: userId });

API リファレンス

upload(options)

進捗を追跡しながら一連の処理を行う、メインのアップロード API です。

interface UploadOptions {
  files: File[];
  recordId?: string | null; // If provided, creates ContentVersion
  onProgress?: (progress: FileUploadProgress) => void;
  signal?: AbortSignal; // Optional cancellation
}

interface FileUploadProgress {
  fileName: string;
  status: "pending" | "uploading" | "processing" | "success" | "error";
  progress: number; // 0-100 for uploading, 0 for other states
  error?: string;
}

interface FileUploadResult {
  fileName: string;
  size: number;
  contentBodyId: string; // Always available
  contentVersionId?: string; // Only if recordId was provided
}

戻り値: Promise<FileUploadResult[]>

createContentVersion(file, contentBodyId, recordId)

以前アップロードしたファイルから ContentVersion レコードを手動で作成します。

async function createContentVersion(
  file: File,
  contentBodyId: string,
  recordId: string,
): Promise<string | undefined>;

パラメーター:

  • file — ファイルオブジェクト(名前などのメタデータに使用)
  • contentBodyId — 前回のアップロードで取得した ContentBody ID
  • recordId — FirstPublishLocationId に指定するレコード ID

戻り値: 成功した場合は ContentVersion ID

getCurrentUserId()

現在のユーザーの Salesforce ID を取得します。

async function getCurrentUserId(): Promise<string>;

戻り値: 現在のユーザー ID

一般的な UI パターン

ボタン付きファイル入力
<input type="file" multiple accept=".pdf,.doc,.docx,.jpg,.png" onChange={handleFileSelect} />
ドラッグ&ドロップ領域

ネイティブイベントを使用して独自のドロップゾーンを構築します。

function DropZone({ onDrop }: { onDrop: (files: File[]) => void }) {
  const handleDrop = (e: React.DragEvent) => {
    e.preventDefault();
    const files = Array.from(e.dataTransfer.files);
    onDrop(files);
  };

  return (
    <div
      onDrop={handleDrop}
      onDragOver={(e) => e.preventDefault()}
      style={{ border: "2px dashed #ccc", padding: "2rem" }}
    >
      Drop files here
    </div>
  );
}
プログレスバー
{
  progress.status === "uploading" && (
    <div style={{ width: "100%", background: "#eee" }}>
      <div
        style={{
          width: `${progress.progress}%`,
          background: "#0176d3",
          height: "8px",
        }}
      />
    </div>
  );
}

エージェント向けの判断フロー

ユーザーがファイルのアップロード機能を求めている場合:

  1. レコードのコンテキストを確認します:

    • 「アップロードしたファイルを特定のレコードにリンクしますか。それとも、まずアップロードして後からリンクしますか。」
  2. 回答に応じて選択します:

    • 既存のレコードにリンクする → recordId を指定してパターン B を使用
    • 先にアップロードし、後からリンクする → パターン A(recordId なし)を使用し、その後パターン C でリンク
    • 現在のユーザーにリンクする → getCurrentUserId() を指定してパターン B を使用
  3. UI を構築します:

    • ファイル入力またはドロップゾーンを作成します(パッケージには含まれていません)
    • 各ファイルの進捗表示(ステータスとプログレスバー)を追加します
    • UI でエラーを処理します
  4. 実装をテストします:

    • 進捗コールバックが正しく呼び出されることを確認します
    • contentBodyId が返されることを確認します
    • recordId を指定した場合、contentVersionId が返されることを確認します

リファレンス実装

パッケージには、src/features/fileupload/ にリファレンス実装が含まれています。

  • FileUpload.tsx — ドロップゾーンとダイアログを備えた完全なコンポーネント
  • FileUploadDialog.tsx — 進捗状況を追跡するダイアログ
  • FileUploadDropZone.tsx — ドラッグ&ドロップ領域
  • useFileUpload.ts — 状態管理用の React フック

これらはエクスポートされていませんが、例として参照できます。ソースファイルを読んで、独自の UI を構築する際のパターンを確認してください。

トラブルシューティング

アップロードが CORS エラーで失敗する場合:

  • UI バンドルが Salesforce に正しくデプロイされているか、localhost で実行されていることを確認します
  • 組織の CORS 設定でオリジンが許可されているか確認します

進捗状況が更新されない場合:

  • onProgress コールバックが指定されていることを確認します
  • コールバック関数が React の状態を正しく更新しているか確認します

ContentVersion が作成されない場合:

  • upload() 関数に recordId が指定されていることを確認します
  • レコード ID が有効で、組織内に存在することを確認します
  • ユーザーに ContentVersion レコードを作成する権限があることを確認します

ファイルはアップロードされるものの、レコードに表示されない場合:

  • recordId が正しいことを確認します
  • ContentVersion が作成されたことを確認します(結果に contentVersionId があるか確認してください)
  • ユーザーがレコード上のファイルを表示するアクセス権を持っていることを確認します

次のことはしないでください

  • XHR/fetch によるアップロード処理を一から作成する — upload() API を使用してください
  • <FileUpload /> コンポーネントをインポートしようとする — エクスポートされていません
  • useFileUpload フックをインポートしようとする — エクスポートされていません
  • この機能がある場合に、サードパーティー製のファイルアップロードライブラリを使用する
  • 進捗状況の追跡を省略する — 常にユーザーに状況を知らせてください
  • エラーを無視する — 常にエラーメッセージを処理して表示してください

コメント