Skip to content
fandhe-frontendcore v0.4.3
GitHub

settings-webhook-tester

Webhook のテスト送信画面を表した設定ブロックです。宛先エンドポイントとイベント種類を選択欄で選び、送信されるペイロードをコード表示で確認し、送信ボタンを押す構成を縦に並べます。送信結果は成功・失敗の 2 状態を並記し、状態バッジと応答内容を確認できます。native-select / field / code / button / badge / heading の 6 部品を合成します。Blocks は既存部品の合成例であり、新しい UI 部品は追加しません。

主参照は対応表 ID R0387(代表構成)の 1 件のみで、集約元の差分はありません。狭いコンテナ幅でもブレークポイントによる横並び切り替えは行わず、常に縦積みのまま表示します。

宛先 URL・イベント名・応答内容はすべて架空のデータです(example.com ドメイン・evt_/ord_ 系の架空 ID)。署名シークレット等のクレデンシャル様の値は出力しません。

本 Demo は無 JS の静的表示です。<form> を含まず、選択欄の選択操作自体はネイティブに機能しますが、ペイロード表示・送信ボタン・送信結果はいずれも選択値に追従しない固定表示です。「テスト送信」ボタンは送信先・検証・永続化の経路を一切持ちません。

Demo

テスト送信

宛先とイベント種類を選び、固定のペイロードを送信します。

送信されるペイロード

{
  "event": "order.created",
  "id": "evt_7f0c1a9d",
  "data": {
    "order_id": "ord_4821",
    "amount": 3200
  }
}

送信結果

成功した場合

成功

200 OK・182ms

{
  "received": true
}

失敗した場合

失敗

500・タイムアウトまで 3 回再試行

{
  "error": "internal_error"
}

使用部品

Rust コード

Rust
use fandhe_frontend_core::{div, el, h4, p, pre, text, Node};
use fandhe_frontend_pre_styled_ui::badge::{self, BadgeProps, BadgeVariant};
use fandhe_frontend_pre_styled_ui::button::{self, ButtonProps, ButtonVariant};
use fandhe_frontend_pre_styled_ui::code::{self, CodeProps};
use fandhe_frontend_pre_styled_ui::field::{
    self, FieldIds, FieldOrientation, FieldProps, FieldRootProps,
};
use fandhe_frontend_pre_styled_ui::heading::{self, HeadingLevel, HeadingProps, HeadingSize};
use fandhe_frontend_pre_styled_ui::native_select::{self, NativeSelectProps};
use fandhe_frontend_pre_styled_ui::ColorPalette;

/// 送信されるペイロード例(架空の JSON、実クレデンシャル・PII を含まない。
/// 選択欄の選択値には追従しない静的表示、モジュール doc「無 JS のため
/// 選択は送信・ペイロードに反映されない」節参照)。
const PAYLOAD_JSON: &str = "{\n  \"event\": \"order.created\",\n  \"id\": \"evt_7f0c1a9d\",\n  \"data\": {\n    \"order_id\": \"ord_4821\",\n    \"amount\": 3200\n  }\n}";

/// 成功時の応答本文(架空値)。
const SUCCESS_BODY: &str = "{\n  \"received\": true\n}";

/// 失敗時の応答本文(架空値)。
const FAILURE_BODY: &str = "{\n  \"error\": \"internal_error\"\n}";

/// 送信結果の種別(成功・失敗の 2 状態を並記する、モジュール doc
/// 「成功・失敗の 2 状態を並記する」節参照)。
#[derive(Clone, Copy)]
enum ResultKind {
    Success,
    Failure,
}

impl ResultKind {
    /// 結果見出し(素の `h4`。`heading` 部品を使わずページ右目次への混入を
    /// 避ける判断は `settings_webhook_detail`/`settings_webhook_stats` と
    /// 同型)。
    fn heading(self) -> &'static str {
        match self {
            ResultKind::Success => "成功した場合",
            ResultKind::Failure => "失敗した場合",
        }
    }

    /// 状態バッジの色。
    fn palette(self) -> ColorPalette {
        match self {
            ResultKind::Success => ColorPalette::Success,
            ResultKind::Failure => ColorPalette::Danger,
        }
    }

    /// 状態バッジの文言(色だけでなく文言でも判別できるようにする)。
    fn label(self) -> &'static str {
        match self {
            ResultKind::Success => "成功",
            ResultKind::Failure => "失敗",
        }
    }

    /// 補足説明(架空値)。
    fn detail(self) -> &'static str {
        match self {
            ResultKind::Success => "200 OK・182ms",
            ResultKind::Failure => "500・タイムアウトまで 3 回再試行",
        }
    }

    /// 応答本文(架空値)。
    fn body(self) -> &'static str {
        match self {
            ResultKind::Success => SUCCESS_BODY,
            ResultKind::Failure => FAILURE_BODY,
        }
    }
}

/// 選択欄 1 件分(`field` + `native-select`、`id` は
/// `blocks-settings-webhook-tester-{id_suffix}`)。`options` は
/// `(value, label, selected)` の並び。
fn select_field(
    id_suffix: &'static str,
    label_text: &'static str,
    options: &[(&'static str, &'static str, bool)],
) -> Node {
    let id = format!("blocks-settings-webhook-tester-{id_suffix}");
    let field_props = FieldProps {
        id: &id,
        ids: FieldIds::default(),
        disabled: false,
        invalid: false,
        required: false,
        readonly: false,
        has_helper_text: false,
    };
    let option_nodes: Vec<Node> = options
        .iter()
        .map(|(value, label, selected)| {
            let mut attrs = vec![("value", *value)];
            if *selected {
                attrs.push(("selected", "selected"));
            }
            el("option", attrs, vec![text(*label)])
        })
        .collect();
    field::root(
        &FieldRootProps {
            orientation: FieldOrientation::Vertical,
        },
        &field_props,
        vec![("data-blocks-settings-webhook-tester-field", "")],
        vec![
            field::label(&field_props, vec![], vec![text(label_text)]),
            native_select::native_select(
                &NativeSelectProps::default(),
                &field_props,
                vec![],
                option_nodes,
            ),
        ],
    )
}

/// 送信結果 1 件分(見出し行〔`h4` + `badge`〕+ 補足 `p` + 応答本文
/// `pre`/`code`)。
fn result_item(kind: ResultKind) -> Node {
    div(
        vec![("class", "blocks-settings-webhook-tester-result")],
        vec![
            div(
                vec![("class", "blocks-settings-webhook-tester-result-heading")],
                vec![
                    h4(vec![], vec![text(kind.heading())]),
                    badge::badge(
                        &BadgeProps {
                            variant: BadgeVariant::Subtle,
                            palette: kind.palette(),
                            ..BadgeProps::default()
                        },
                        vec![],
                        vec![text(kind.label())],
                    ),
                ],
            ),
            p(vec![], vec![text(kind.detail())]),
            pre(
                vec![("class", "blocks-settings-webhook-tester-code")],
                vec![code::code(
                    &CodeProps::default(),
                    vec![("data-blocks-settings-webhook-tester-code", "")],
                    vec![text(kind.body())],
                )],
            ),
        ],
    )
}

/// `settings-webhook-tester` の Demo 本体(呼び出しごとに同一の `Node` を
/// 返す純関数)。
pub fn demo() -> Node {
    div(
        vec![("class", "blocks-settings-webhook-tester-layout")],
        vec![
            heading::heading(
                HeadingLevel::H3,
                &HeadingProps {
                    size: HeadingSize::Sm,
                    ..HeadingProps::default()
                },
                vec![],
                vec![text("テスト送信")],
            ),
            p(
                vec![("class", "blocks-settings-webhook-tester-lead")],
                vec![text(
                    "宛先とイベント種類を選び、固定のペイロードを送信します。",
                )],
            ),
            div(
                vec![("class", "blocks-settings-webhook-tester-fields")],
                vec![
                    select_field(
                        "endpoint",
                        "送信先エンドポイント",
                        &[
                            (
                                "https://hooks.example.com/ingest/8f2c91ab",
                                "https://hooks.example.com/ingest/8f2c91ab",
                                true,
                            ),
                            (
                                "https://hooks.example.com/ingest/staging",
                                "https://hooks.example.com/ingest/staging",
                                false,
                            ),
                        ],
                    ),
                    select_field(
                        "event",
                        "イベント種類",
                        &[
                            ("order.created", "order.created", true),
                            ("order.updated", "order.updated", false),
                            ("payment.succeeded", "payment.succeeded", false),
                        ],
                    ),
                ],
            ),
            h4(vec![], vec![text("送信されるペイロード")]),
            pre(
                vec![("class", "blocks-settings-webhook-tester-code")],
                vec![code::code(
                    &CodeProps::default(),
                    vec![("data-blocks-settings-webhook-tester-code", "")],
                    vec![text(PAYLOAD_JSON)],
                )],
            ),
            div(
                vec![("class", "blocks-settings-webhook-tester-actions")],
                vec![button::button(
                    &ButtonProps {
                        variant: ButtonVariant::Solid,
                        ..ButtonProps::default()
                    },
                    vec![],
                    vec![text("テスト送信")],
                )],
            ),
            heading::heading(
                HeadingLevel::H3,
                &HeadingProps {
                    size: HeadingSize::Sm,
                    ..HeadingProps::default()
                },
                vec![],
                vec![text("送信結果")],
            ),
            div(
                vec![("class", "blocks-settings-webhook-tester-results")],
                vec![
                    result_item(ResultKind::Success),
                    result_item(ResultKind::Failure),
                ],
            ),
        ],
    )
}

原案差分メモ

  • 集約元は対応表 ID R0387(代表構成)の 1 件のみで、差分併記はありません。
  • _/blocks-intake/ の対応ファイルは本イシュー着手時点で本 worktree に存在しないため、参照ファイルは未参照のまま対応表 ID のみを記載し、構成は Issue 本文のレイアウト仕様から組み立てました(settings-webhook-detail 等の既存 block と同じ扱い)。

関連情報: Native Select / Field / Code / Button / Badge