次の方法で共有


ACS UI ライブラリでのユーザー フィードバックの収集

はじめに

この包括的なガイドは、開発者がバックエンド処理に Azure サービスを使用して、ACS UI ライブラリに拡張サポートを統合するのを支援することを目的にしています。 このガイドは、わかりやすく実装しやすいように、クライアント側とサーバー側の手順に分かれています。

前提条件

  • Azure サブスクリプション: アクティブな Azure サブスクリプションが必要です。 お持ちでない場合は、Azure 無料アカウントで無料アカウントを作成できます。
  • Azure Communication Services リソース: 通話およびチャット機能を使用するには、ACS リソースが必要です。 Azure portal で作成することができます。
  • 開発環境の設定: 開発環境が、Android、iOS、Web のいずれか 1 つ以上のターゲット プラットフォームに設定されていることを確認します。
  • Azure Storage アカウント: ユーザー フィードバックと関連データを安全に保存するには、Azure Storage アカウントが必要です。
  • Node.js と Express.js: サポート リクエストを受信して処理するようにサーバー側アプリケーションを設定する際に、Node.js と Express.js の基本的な知識が役立ちます。
  • RESTful API の知識: RESTful API を作成および使用する方法を理解していると、クライアントとサーバーの両方の展開と構成に役立ちます。
  • クライアント開発スキル: Android または iOS アプリケーションの開発能力。

これらの前提条件を満たすことで、Azure Communication Services やその他の Azure リソースを使用した包括的なユーザー フィードバック システムの統合をスムーズに開始することができます。

学習内容

このガイドでは、Azure Communication Services (ACS) アプリケーション内のユーザー フィードバック メカニズムの統合に関する包括的な分析情報を得ることができます。 Azure バックエンド サービスを処理に使用し、ACS UI ライブラリを通じてカスタマー サポートを強化することに重点を置いています。 このガイドに従うことで、開発者は次の内容を習得できます。

  • クライアント側フィードバック キャプチャを実装する: ACS UI ライブラリを使用して、Android および iOS アプリケーションから直接ユーザー フィードバック、エラー ログ、サポート リクエストをキャプチャする方法について説明します。
  • サーバー側アプリケーションを設定する: Express.js を使用して Azure Blob Storage でサポート リクエストを受信、処理、保存する Node.js アプリケーションを設定する詳細な手順。 このサーバーには、ファイルのアップロードに使用される multipart/form-data の処理と、ユーザー データの安全な管理が含まれます。
  • サポート チケットを作成する: 一意のサポート チケット番号を生成し、関連するアプリケーション データと共にユーザー フィードバックを保存する方法について説明します。
  • Azure Blob Storage を利用する: フィードバックとサポート リクエスト データを保存するために Azure Blob Storage を使用する方法についての詳細を説明します。これにより、効率的な取得と分析をサポートする安全で構造化されたデータ管理を実現します。
  • アプリケーションの信頼性とユーザー満足度を向上させる: 開発者は、このガイドに説明されている方法を実装することで、ユーザーの問題に迅速に対処して解決できます。

サーバー側の設定

サポート リクエストを処理する Node.js アプリケーションを設定する

セクションの目標: 目標は、Express.js を使用して、ユーザーからのサポート リクエストを受け取るバックエンドとして機能する Node.js アプリケーションを作成することです。 これらのリクエストには、ユーザーの問題の診断と解決に役立つテキスト フィードバック、エラー ログ、スクリーンショット、その他の関連情報が含まれる場合があります。 アプリケーションは、整理された方法で安全にアクセスできるよう、このデータを Azure Blob Storage に保存します。

フレームワークとツール

  • Express.js: Web アプリケーションと API を構築するための Node.js フレームワーク。 サーバーの設定とリクエスト処理の基礎として機能します。
  • Formidable: フォーム データを解析するためのライブラリ。特にファイルのアップロードによく使用される multipart/form-data を処理することを目的にしています。
  • Azure Blob Storage: 大量の非構造化データを保存するための Microsoft Azure サービス。

手順 1: 環境の設定

開始する前に、Node.js がインストールされた開発環境が準備できていることを確認します。 また、送信されたデータを保存するために Azure Storage アカウントへのアクセスも必要です。

  1. Node.js をインストールする: Node.js がシステムにインストールされていることを確認します。 これは Node.js からダウンロードできます。

  2. Azure Blob Storage アカウントを作成する: まだ Azure Storage アカウントを作成していない場合は、Azure portal を使用して作成します。 このアカウントはサポート リクエスト データを保存するために使用されます。

  3. 必要な資格情報を収集する: Azure Blob Storage アカウントの接続文字列があることを確認します。

手順 2: アプリケーションの設定

  1. 新しい Node.js プロジェクトを初期化する:

    • プロジェクト用の新しいディレクトリを作成し、npm init を使用して初期化し、package.json ファイルを作成します。

    • npm を使用して、Express.js、Formidable、Azure Storage Blob SDK、その他の必要なライブラリをインストールします。

      npm install express formidable @azure/storage-blob uuid
      
  2. サーバーの実装:

    • Express.js を使用して、特定のエンドポイントで POST 要求をリッスンする基本的な Web サーバーを設定します。
    • Formidable を使用して受信フォーム データを解析し、multipart/form-data コンテンツを処理します。
    • 各サポート リクエストに一意のチケット番号を生成します。これを使用して、Azure Blob Storage 内のデータを整理し、ユーザーに参照を指定できます。
    • ユーザー メッセージやログ ファイル メタデータなどの構造化されたデータを Blob Storage 内の JSON ファイルに保存します。 実際のログ ファイルとスクリーンショットまたは添付ファイルを、同じチケットのディレクトリ内の別の BLOB に保存します。
    • サポートの詳細を取得するためのエンドポイントを指定します。これには、Azure Blob Storage からのデータのフェッチと表示が含まれます。
  3. セキュリティに関する考慮事項:

    • アプリケーションで受信データが検証され、悪意のあるペイロードから保護されていることを確認します。
    • 環境変数を使用して、Azure Storage 接続文字列などの機密情報を安全に保存します。

手順 3: アプリケーションの実行とテスト

  1. 環境変数:

    • Azure Blob Storage 接続文字列とその他の機密情報の環境変数を設定します。 たとえば、.env ファイル (およびこれらの変数を読み込むための dotenv npm パッケージ) を使用できます。
  2. サーバーの実行:

    • node <filename>.js を実行して Node.js アプリケーションを開始します。ここで、メイン サーバー ファイルの名前は <filename> です。
    • Web 開発に適したツールを使用してサーバーを検証します。

サーバー コード:

ここでは、まず実際に機能する実装を示します。 このコードは、ACS UI サンプル アプリケーションからのチケット作成を示すために調整された基本的な実装です。

const express = require('express');
const formidable = require('formidable');
const fs = require('fs').promises
const { BlobServiceClient } = require('@azure/storage-blob');
const { v4: uuidv4 } = require('uuid');
const app = express();
const connectionString = process.env.SupportTicketStorageConnectionString
const port = process.env.PORT || 3000;
const portPostfix = (!process.env.PORT || port === 3000 || port === 80 || port === 443) ? '' : `:${port}`;

app.use(express.json());

app.all('/receiveEvent', async (req, res) => {
    try {
        const form = new formidable.IncomingForm();
        form.parse(req, async (err, fields, files) => {
            if (err) {
                return res.status(500).send("Error processing request: " + err.message);
            }
            // Generate a unique ticket number
            const ticketNumber = uuidv4();
            const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
            const containerClient = blobServiceClient.getContainerClient('supporttickets');
            await containerClient.createIfNotExists();

            // Prepare and upload support data
            const supportData = {
                userMessage: fields.user_message,
                uiVersion: fields.ui_version,
                sdkVersion: fields.sdk_version,
                callHistory: fields.call_history
            };
            const supportDataBlobClient = containerClient.getBlockBlobClient(`${ticketNumber}/supportdata.json`);
            await supportDataBlobClient.upload(JSON.stringify(supportData), Buffer.byteLength(JSON.stringify(supportData)));

            // Upload log files
            Object.values(files).forEach(async (fileOrFiles) => {
                // Check if the fileOrFiles is an array (multiple files) or a single file object
                const fileList = Array.isArray(fileOrFiles) ? fileOrFiles : [fileOrFiles];
            
                for (let file of fileList) {
                    const blobClient = containerClient.getBlockBlobClient(`${ticketNumber}/logs/${file.originalFilename}`);
                    
                    // Read the file content into a buffer
                    const fileContent = await fs.readFile(file.filepath);
                    
                    // Now upload the buffer
                    await blobClient.uploadData(fileContent); // Upload the buffer instead of the file path
                }
            });
            // Return the ticket URL
            const endpointUrl = `${req.protocol}://${req.headers.host}${portPostfix}/ticketDetails?id=${ticketNumber}`;
            res.send(endpointUrl);
        });
    } catch (err) {
        res.status(500).send("Error processing request: " + err.message);
    }
});

// ticketDetails endpoint to serve details page
app.get('/ticketDetails', async (req, res) => {
    const ticketNumber = req.query.id;
    if (!ticketNumber) {
        return res.status(400).send("Ticket number is required");
    }

    // Fetch the support data JSON blob to display its contents
    try {
        const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
        const containerClient = blobServiceClient.getContainerClient('supporttickets');
        const blobClient = containerClient.getBlobClient(`${ticketNumber}/supportdata.json`);
        const downloadBlockBlobResponse = await blobClient.download(0);
        const downloadedContent = (await streamToBuffer(downloadBlockBlobResponse.readableStreamBody)).toString();
        const supportData = JSON.parse(downloadedContent);

        // Generate links for log files
        let logFileLinks = `<h3>Log Files:</h3>`;
        const listBlobs = containerClient.listBlobsFlat({ prefix: `${ticketNumber}/logs/` });
        for await (const blob of listBlobs) {
            logFileLinks += `<a href="/getLogFile?id=${ticketNumber}&file=${encodeURIComponent(blob.name.split('/')[2])}">${blob.name.split('/')[2]}</a><br>`;
        }

        // Send a simple HTML page with support data and links to log files
        res.send(`
            <h1>Ticket Details</h1>
            <p><strong>User Message:</strong> ${supportData.userMessage}</p>
            <p><strong>UI Version:</strong> ${supportData.uiVersion}</p>
            <p><strong>SDK Version:</strong> ${supportData.sdkVersion}</p>
            <p><strong>Call History:</strong> </p> <pre>${supportData.callHistory}</pre>
            ${logFileLinks}
        `);
    } catch (err) {
        res.status(500).send("Error fetching ticket details: " + err.message);
    }
});

// getLogFile endpoint to allow downloading of log files
app.get('/getLogFile', async (req, res) => {
    const { id: ticketNumber, file } = req.query;
    if (!ticketNumber || !file) {
        return res.status(400).send("Ticket number and file name are required");
    }

    try {
        const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
        const containerClient = blobServiceClient.getContainerClient('supporttickets');
        const blobClient = containerClient.getBlobClient(`${ticketNumber}/logs/${file}`);

        // Stream the blob to the response
        const downloadBlockBlobResponse = await blobClient.download(0);
        res.setHeader('Content-Type', 'application/octet-stream');
        res.setHeader('Content-Disposition', `attachment; filename=${file}`);
        downloadBlockBlobResponse.readableStreamBody.pipe(res);
    } catch (err) {
        res.status(500).send("Error downloading file: " + err.message);
    }
});

// Helper function to stream blob content to a buffer
async function streamToBuffer(stream) {
    const chunks = [];
    return new Promise((resolve, reject) => {
        stream.on('data', (chunk) => chunks.push(chunk));
        stream.on('end', () => resolve(Buffer.concat(chunks)));
        stream.on('error', reject);
    });
}


app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

クライアント側の設定

このセクションでは、クライアント側の設定と、次の目標を達成する方法について説明します。

  1. ユーザーから報告された問題に登録する。
  2. データをシリアル化する。
  3. データをサーバーに転送する。
  4. 応答を受信する。
  5. ユーザーに応答を表示する。

Azure Communication Services (ACS) UI ライブラリ内でユーザー フィードバックを有効にするには、開発者側でのアクションが必要です。 ライブラリの統合で onUserReportedIssueEventHandler を利用することで、開発者は組み込みサポート フォームを有効にし、ユーザーが直接問題を報告できるようになります。 このセクションでは、クライアント側のフィードバック フォームを設定する手順について説明します。

Android でのクライアント側フィードバック キャプチャの実装

サポート フォームの有効化

  1. イベント ハンドラーの登録:

    • Android アプリケーション内でサポート フォームをアクティブ化するには、アプリケーションのライフサイクルの適切な時点で onUserReportedIssueEventHandler を登録します。 この登録により、フォームが有効になるだけでなく、ユーザーに表示され、アクセスできるようになります。
  2. フォームの表示とアクセシビリティ:

    • 登録された onUserReportedIssueEventHandler が存在すると、サポート フォームの表示に直接影響します。 このハンドラーがないと、フォームはユーザー インターフェイスで非表示のままとなり、問題を報告するためにアクセスできなくなります。

サポート イベントのキャプチャと処理

  1. 問題報告時のイベントの出力:

    • ユーザーが有効なサポート フォームから問題を報告すると、onUserReportedIssueEventHandler は出力されたイベントをキャプチャします。 これらのイベントは、説明、エラー ログ、場合によってはスクリーンショットなど、ユーザーが報告した問題に関連するすべての必要な情報をカプセル化します。
  2. 送信のためのデータ準備:

    • ユーザーが問題を報告したら、次の手順では、報告された問題データをサーバーに送信するための準備を行います。 この準備には、サーバーの仕様に準拠して、キャプチャされた情報を HTTP 転送に適した形式に構成する手順が含まれています。

問題データをサーバーに送信する

  1. 非同期データ転送:

    • 非同期メカニズムを利用して、準備したデータを指定したサーバー エンドポイントに送信します。 この方法により、アプリケーションの応答性が維持され、バックグラウンドでデータが送信されている間もスムーズなユーザー エクスペリエンスが提供されます。
  2. サーバーの応答処理:

    • データの送信時には、サーバーの応答を巧みに処理することが重要です。 この処理には、サーバーのフィードバックを解析してデータが正常に送信されたことを確認し、報告された問題への参照 (チケット番号や URL など) を抽出してユーザーに伝えることが含まれる場合があります。

ユーザー フィードバックと通知の提供

  1. 即時のユーザー フィードバック:

    • アプリケーションのユーザー インターフェイスを使用して、問題報告の送信状態をすぐにユーザーに通知します。 正常に送信された場合、送信された問題への参照を提供し、ユーザーがレポートの進行状況を追跡できるようにすることを検討してください。
  2. Android O 以降の通知方法:

    • Android O (API レベル 26) 以降を実行しているデバイスの場合、レポート送信専用の通知チャネルが実装されていることを確認します。 この設定は、通知を効果的に配信するために不可欠であり、これらの Android バージョンでは必須です。

これらの手順に従うことで、開発者は Android アプリケーションに堅牢なユーザー フィードバック メカニズムを統合し、onUserReportedIssueEventHandler を使用して効率的な問題の報告と追跡を行うことができます。 このプロセスは、ユーザーの問題をタイムリーに解決できるだけでなく、アプリケーションに対する全体的なユーザー エクスペリエンスと満足度の向上にも大きく寄与します。

Android コード サンプル

Kotlin コード スニペットは、Azure Communication Services を使用して Android アプリケーション内でユーザーが報告した問題を処理するためのシステムを統合するプロセスを示しています。 この統合は、ユーザーとサポート チーム間の直接的なコミュニケーションを可能にすることで、サポート プロセスを効率化することを目的にしています。 手順の概要を次に示します。

  1. イベント キャプチャ: システムは、ACS UI ライブラリを通じてユーザーが報告した問題をリッスンします。 onUserReportedIssueEventHandler を利用して、アプリケーションの UI からのフィードバック (エラーやユーザーの懸念事項など) をキャプチャします。

  2. サーバーへのデータ送信: 問題が報告されると、ユーザー メッセージ、エラー ログ、バージョン、診断情報を含む関連データがパッケージ化されます。 その後、このデータは非同期 POST 要求を使用してサーバー エンドポイントに送信され、プロセスによってアプリのパフォーマンスが妨げられないようにします。

  3. ユーザー フィードバックと通知: 送信後、ユーザーにはアプリ内通知を通じて、レポートの状態がすぐに通知されます。 正常に送信された場合、通知には、送信されたチケットへのリンクまたは参照が含まれるため、ユーザーは解決の進行状況を追跡できます。

この設定は、ユーザーの問題に迅速に対処するのに役立つだけでなく、サポートとフィードバックのための明確なチャネルを提供することで、ユーザー満足度とアプリの信頼性を向上させることにも大きく寄与します。

package com.azure.android.communication.ui.callingcompositedemoapp

import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.azure.android.communication.ui.calling.CallCompositeEventHandler
import com.azure.android.communication.ui.calling.models.CallCompositeCallHistoryRecord
import com.azure.android.communication.ui.calling.models.CallCompositeUserReportedIssueEvent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.asRequestBody
import org.threeten.bp.format.DateTimeFormatter
import java.io.File
import java.io.IOException

/**
 * This class is responsible for handling user-reported issues within the Azure Communication Services Calling UI composite.
 * It implements the CallCompositeEventHandler interface to listen for CallCompositeUserReportedIssueEvents.
 * The class demonstrates how to send diagnostic information to a server endpoint for support purposes and
 * how to provide user feedback through notifications.
 */
class UserReportedIssueHandler : CallCompositeEventHandler<CallCompositeUserReportedIssueEvent> {
    // Flow to observe user reported issues.
    val userIssuesFlow = MutableStateFlow<CallCompositeUserReportedIssueEvent?>(null)

    // Reference to the application context, used to display notifications.
    lateinit var context: Application

    // Lazy initialization of the NotificationManagerCompat for managing notifications.
    private val notificationManager by lazy { NotificationManagerCompat.from(context) }

    /**
     * Handles the event when a user reports an issue.
     * - Creates a notification channel for Android O and above.
     * - Updates the userIssuesFlow with the new event data.
     * - Sends the event data including user message, app and SDK versions, call history, and log files to a server.
     */
    override fun handle(eventData: CallCompositeUserReportedIssueEvent?) {
        createNotificationChannel()
        userIssuesFlow.value = eventData
        eventData?.apply {
            sendToServer(
                userMessage,
                debugInfo.versions.azureCallingUILibrary,
                debugInfo.versions.azureCallingLibrary,
                debugInfo.callHistoryRecords,
                debugInfo.logFiles
            )
        }
    }

    /**
     * Prepares and sends a POST request to a server with the user-reported issue data.
     * Constructs a multipart request body containing the user message, app versions, call history, and log files.
     */
    private fun sendToServer(
        userMessage: String?,
        callingUIVersion: String?,
        callingSDKVersion: String?,
        callHistoryRecords: List<CallCompositeCallHistoryRecord>,
        logFiles: List<File>
    ) {
        if (SERVER_URL.isBlank()) { // Check if the server URL is configured.
            return
        }
        showProgressNotification()
        CoroutineScope(Dispatchers.IO).launch {
            val client = OkHttpClient()
            val requestBody = MultipartBody.Builder().setType(MultipartBody.FORM).apply {
                userMessage?.let { addFormDataPart("user_message", it) }
                callingUIVersion?.let { addFormDataPart("ui_version", it) }
                callingSDKVersion?.let { addFormDataPart("sdk_version", it) }
                addFormDataPart(
                    "call_history",
                    callHistoryRecords.map { "\n\n${it.callStartedOn.format(DateTimeFormatter.BASIC_ISO_DATE)}\n${it.callIds.joinToString("\n")}" }
                        .joinToString("\n"))
                logFiles.filter { it.length() > 0 }.forEach { file ->
                    val mediaType = "application/octet-stream".toMediaTypeOrNull()
                    addFormDataPart("log_files", file.name, file.asRequestBody(mediaType))
                }
            }.build()

            val request = Request.Builder()
                .url("$SERVER_URL/receiveEvent")
                .post(requestBody)
                .build()

            client.newCall(request).enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    CoroutineScope(Dispatchers.Main).launch {
                        onTicketFailed(e.message ?: "Unknown error")
                    }
                }

                override fun onResponse(call: Call, response: Response) {
                    CoroutineScope(Dispatchers.Main).launch {
                        if (response.isSuccessful) {
                            onTicketCreated(response.body?.string() ?: "No URL provided")
                        } else {
                            onTicketFailed("Server error: ${response.message}")
                        }
                    }
                }
            })
        }
    }

    /**
     * Displays a notification indicating that the issue ticket has been created successfully.
     * The notification includes a URL to view the ticket status, provided by the server response.
     */
    private fun onTicketCreated(url: String) {
        showCompletionNotification(url)
    }

    /**
     * Displays a notification indicating that the submission of the issue ticket failed.
     * The notification includes the error reason.
     */
    private fun onTicketFailed(error: String) {
        showErrorNotification(error)
    }

    companion object {
        // The server URL to which the user-reported issues will be sent. Must be configured.
        private const val SERVER_URL = "${INSERT_YOUR_SERVER_ENDPOINT_HERE}"
    }

    /**
     * Creates a notification channel for Android O and above.
     * This is necessary to display notifications on these versions of Android.
     */
    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val name = "Report Submission"
            val descriptionText = "Notifications for report submission status"
            val importance = NotificationManager.IMPORTANCE_DEFAULT
            val channel = NotificationChannel("report_submission_channel", name, importance).apply {
                description = descriptionText
            }
            notificationManager.createNotificationChannel(channel)
        }
    }

    /**
     * Shows a notification indicating that the report submission is in progress.
     * This uses an indeterminate progress indicator to signify ongoing activity.
     */
    private fun showProgressNotification() {
        val notification = NotificationCompat.Builder(context, "report_submission_channel")
            .setContentTitle("Submitting Report")
            .setContentText("Your report is being submitted...")
            .setSmallIcon(R.drawable.image_monkey) // Replace with an appropriate icon for your app
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setProgress(0, 0, true) // Indeterminate progress
            .build()

        notificationManager.notify(1, notification)
    }

    /**
     * Shows a notification indicating that the report has been successfully submitted.
     * The notification includes an action to view the report status via a provided URL.
     */
    private fun showCompletionNotification(url: String) {
        val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
        val pendingIntent = PendingIntent.getActivity(
            context,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        val notification = NotificationCompat.Builder(context, "report_submission_channel")
            .setContentTitle("Report Submitted")
            .setContentText("Tap to view")
            .setSmallIcon(R.drawable.image_monkey) // Replace with an appropriate icon for your app
            .setContentIntent(pendingIntent)
            .setAutoCancel(true) // Removes notification after tap
            .build()

        notificationManager.notify(1, notification)
    }

    /**
     * Shows a notification indicating an error in submitting the report.
     * The notification includes the reason for the submission failure.
     */
    private fun showErrorNotification(error: String) {
        val notification = NotificationCompat.Builder(context, "report_submission_channel")
            .setContentTitle("Submission Error")
            .setContentText("Error submitting report\nReason: $error")
            .setSmallIcon(R.drawable.image_monkey) // Replace with an appropriate icon for your app
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .build()

        notificationManager.notify(1, notification)
    }
}

イベントのハンドラーを入手したら、複合呼び出しを作成する際に登録できます。

            callComposite.addOnUserReportedEventHandler(userReportedIssueEventHandler)

iOS サポートの概要

Azure Communication Services (ACS) UI ライブラリを使用して iOS アプリケーション内にユーザー フィードバック収集機能を統合するには、開発者は構造化された方法に従う必要があります。 このプロセスには、エラー ログやユーザー情報などのユーザー フィードバックのキャプチャが含まれます。 完了すると、この情報は処理のためにサーバーに送信されます。 このセクションでは、このタスクを実行するために必要な手順について詳細に説明します。

この例では、Alamofire ライブラリを使用して、ログ ファイルを含むマルチパート フォームをサーバーに送信します。

サポート フォームの実装

  1. イベント ハンドラーの登録: まず、ユーザーが報告した問題をリッスンするイベント ハンドラーを登録します。 このハンドラーは、ACS UI ライブラリの機能を使用して、iOS アプリケーションのインターフェイスから直接フィードバックをキャプチャするために不可欠です。

  2. フォームの表示とアクセシビリティ: アプリケーション内のユーザーがサポート フォームに簡単にアクセスして表示できるようにします。 フォームのアクティブ化は、イベント ハンドラーの実装に直接リンクしており、UI 内にフォームが表示されるようになり、ユーザーは問題を報告することができます。

サポート リクエストのキャプチャと処理

  1. ユーザー アクションでのイベントの出力: ユーザーがサポート フォームを通じて問題を報告すると、イベント ハンドラーはこのアクションをキャプチャします。 問題に関するユーザーの説明、エラー ログ、呼び出し ID などの情報を、サーバーに送信できるように準備する必要があります。

  2. 送信のためのデータ構造化: キャプチャされた情報を送信に適した構造化された形式に整理します。 サポート リクエストを受信して処理するサーバー エンドポイントが必要とする形式に合わせてデータを準備します。

サーバーへのデータの送信

  1. 非同期送信: 非同期ネットワーク呼び出しを利用して、構造化されたデータをサーバーに送信します。 この方法により、アプリケーションの応答性が維持され、バックグラウンドでデータが送信されている間もユーザーにシームレスなエクスペリエンスが提供されます。

  2. サーバー応答の処理: 送信時に、サーバーの応答を効率的に処理します。 応答を受信して解析し、データが正常に受信されたことを確認します。 解析された応答からサポート チケットのリンクを抽出し、フォローアップのためにユーザーに伝えることができます。

ユーザーへのフィードバックと通知

  1. 即時の確認: アプリケーション内でのサポート リクエストの送信をすぐに確認し、レポートが受信されたことをユーザーに確認します。

  2. 通知方法: 特に固有の通知フレームワークをサポートしている iOS バージョンを実行しているデバイスで、ユーザーに通知を配信するための方法を実装します。 ローカル通知を使用して、ユーザーにレポートの状態を通知したり、問題に対処したときに更新プログラムを提供したりできます。

iOS コード サンプル

この Swift コード サンプルは、ユーザーが報告した問題をキャプチャし、処理のためにサーバーに送信する基本的な実装の概要を示しています。 この例は、ユーザー フィードバックとアプリケーション診断情報を含むサポート イベント ハンドラーの構築方法と、サーバーへの配信方法を示しています。 このコードには、スムーズなユーザー エクスペリエンスを確保するためのエラー処理とユーザー通知方法も含まれています。

次の例は、イベント ハンドラー内にインストールするフックとして設計されています。

インストール

let onUserReportedIssueHandler: (CallCompositeUserReportedIssue) -> Void = { issue in
    // Add a hook to this method, and provide it the Server endpoint + a result callback
    sendSupportEventToServer(server: self.issueUrl, event: issue) { success, result in
        if success {
            // Success: Convey the result link back to the user
        } else {
            // Error: Let the user know something has happened
        }
    }
}

ネットワーク フック

import Foundation
import UIKit
import Combine
import AzureCommunicationUICalling
import Alamofire

/// Sends a support event to a server with details from a `CallCompositeUserReportedIssue`.
/// - Parameters:
///   - server: The URL of the server where the event will be sent.
///   - event: The `CallCompositeUserReportedIssue` containing details about the issue reported by the user.
///   - callback: A closure that is called when the operation is complete.
///               It provides a `Bool` indicating success or failure, and a `String`
///               containing the server's response or an error message.
func sendSupportEventToServer(server: String,
                              event: CallCompositeUserReportedIssue,
                              callback: @escaping (Bool, String) -> Void) {
    // Construct the URL for the endpoint.
    let url = "\(server)/receiveEvent" // Ensure this is replaced with the actual server URL.

    // Extract debugging information from the event.
    let debugInfo = event.debugInfo

    // Prepare the data to be sent as key-value pairs.
    let parameters: [String: String] = [
        "user_message": event.userMessage, // User's message about the issue.
        "ui_version": debugInfo.versions.callingUIVersion, // Version of the calling UI.
        "call_history": debugInfo.callHistoryRecords
            .map { $0.callIds.joined(separator: ",") }
            .joined(separator: "\n") // Call history, formatted.
    ]

    // Define the headers for the HTTP request.
    let headers: HTTPHeaders = [
        .contentType("multipart/form-data")
    ]

    // Perform the multipart/form-data upload.
    AF.upload(multipartFormData: { multipartFormData in
        // Append each parameter as a part of the form data.
        for (key, value) in parameters {
            if let data = value.data(using: .utf8) {
                multipartFormData.append(data, withName: key)
            }
        }

        // Append log files.
        debugInfo.logFiles.forEach { fileURL in
            do {
                let fileData = try Data(contentsOf: fileURL)
                multipartFormData.append(fileData,
                                         withName: "log_files",
                                         fileName: fileURL.lastPathComponent,
                                         mimeType: "application/octet-stream")
            } catch {
                print("Error reading file data: \(error)")
            }
        }
    }, to: url, method: .post, headers: headers).response { response in
        // Handle the response from the server.
        switch response.result {
        case .success(let responseData):
            // Attempt to decode the response.
            if let data = responseData, let responseString = String(data: data, encoding: .utf8) {
                callback(true, responseString) // Success case.
            } else {
                callback(false, "Failed to decode response.") // Failed to decode.
            }
        case .failure(let error):
            // Handle any errors that occurred during the request.
            print("Error sending support event: \(error)")
            callback(false, "Error sending support event: \(error.localizedDescription)")
        }
    }
}

この Swift コードは、Azure Communication Services を使用して iOS アプリケーションからユーザーが報告した問題を送信するプロセスを示しています。 ユーザー フィードバックの収集、診断情報のパッケージ化、サーバー エンドポイントへの非同期送信を処理します。 さらに、フィードバック メカニズムを実装するための基盤が提供され、ユーザーにレポートの状態が通知され、アプリケーションの全体的な信頼性とユーザー満足度が向上します。

まとめ

Azure Communication Services (ACS) を使用してユーザー フィードバック メカニズムをアプリケーションに統合することは、応答性が高い、ユーザー重視のアプリを開発するために不可欠です。 このガイドは、Node.js を使用したサーバー側の処理と、Android および iOS アプリケーションのクライアント側のフィードバック キャプチャの両方を設定するための明確な方法を提供します。 このような統合により、開発者は、効率的なデータ管理のために Azure のクラウド サービスを利用しながら、アプリケーションの信頼性とユーザー満足度を向上させることができます。

このガイドでは、アプリケーションから直接ユーザー フィードバック、エラー ログ、サポート リクエストをキャプチャするための実用的な手順の概要について説明します。 サポート イベントを統合することで、フィードバックを安全かつ整理された方法で処理できるようになり、開発者はユーザーの問題に迅速に対処して解決できるため、全体的なユーザー エクスペリエンスの向上につながります。

このガイドで詳しく説明されている手順に従うことで、開発者はアプリケーションの応答性を向上させ、ユーザーのニーズをより適切に満たすことができます。 これらの統合は、ユーザー フィードバックをより効果的に理解するのに役立つだけでなく、クラウド サービスを利用して、スムーズで効果的なフィードバックの収集と処理を行うメカニズムを実現します。 最終的に、ユーザー満足度を優先した魅力的で信頼性の高いアプリケーションを作成するには、ユーザー フィードバック メカニズムの統合が不可欠です。