Coletando comentários dos usuários na biblioteca da interface do usuário do ACS

Introdução

Este guia abrangente foi criado para ajudar os desenvolvedores a integrar o suporte avançado à Biblioteca de Interface do Usuário do ACS, usando os serviços do Azure para processamento de back-end. O guia é dividido em etapas do lado do cliente e do lado do servidor para clareza e facilidade de implementação.

Pré-requisitos

  • Assinatura do Azure: você precisa de uma assinatura ativa do Azure. Se não tiver uma, pode criar uma conta gratuita na Conta Gratuita do Azure.
  • Recurso dos Serviços de Comunicação do Azure: um recurso ACS é necessário para usar as funcionalidades de chamada e chat. Você pode criar um no portal do Azure.
  • Configuração do ambiente de desenvolvimento: certifique-se de que seu ambiente de desenvolvimento esteja configurado para uma ou mais plataformas de destino – Android, iOS ou Web.
  • Conta de Armazenamento do Azure: para armazenar comentários do usuário e dados relacionados com segurança, uma conta de Armazenamento do Azure é necessária.
  • Node.js e Express.js: O conhecimento básico de Node.js e Express.js é útil para configurar o aplicativo do lado do servidor para receber e processar solicitações de suporte.
  • Conhecimento de APIs RESTful: Compreender como criar e consumir APIs RESTful ajuda na implantação e configuração do Cliente e do Servidor.
  • Habilidades de Desenvolvimento de Clientes: Proficiência no desenvolvimento de aplicações Android ou iOS.

Ter esses pré-requisitos em vigor garante um início suave para a integração de um sistema abrangente de comentários do usuário usando os Serviços de Comunicação do Azure e outros recursos do Azure.

O que irá aprender

Neste guia, você obtém informações abrangentes sobre a integração de mecanismos de feedback do usuário em seus aplicativos dos Serviços de Comunicação do Azure (ACS). O foco é aprimorar o suporte ao cliente por meio da Biblioteca de Interface do Usuário do ACS, usando os serviços de back-end do Azure para processamento. Ao seguir este guia, os desenvolvedores aprendem a:

  • Implementar a captura de feedback do lado do cliente: saiba como capturar comentários de usuários, logs de erros e solicitações de suporte diretamente de aplicativos Android e iOS usando a biblioteca de interface do usuário do ACS.
  • Configurar um aplicativo do lado do servidor: instruções passo a passo sobre como configurar um aplicativo Node.js usando Express.js para receber, processar e armazenar solicitações de suporte no Armazenamento de Blobs do Azure. Este servidor inclui o tratamento de dados de várias partes/formulários para carregamentos de ficheiros e a gestão segura de dados do utilizador.
  • Crie tíquetes de suporte: entenda como gerar números de tíquete de suporte exclusivos, armazene o feedback do usuário juntamente com dados relevantes do aplicativo.
  • Utilize o Armazenamento de Blobs do Azure: mergulhe em como usar o Armazenamento de Blobs do Azure para armazenar comentários e dar suporte a dados de solicitação, garantindo um gerenciamento de dados seguro e estruturado que ofereça suporte à recuperação e análise eficientes.
  • Melhore a confiabilidade do aplicativo e a satisfação do usuário: os desenvolvedores podem abordar e resolver rapidamente os problemas do usuário implementando as estratégias descritas neste guia.

Configuração do lado do servidor

Configurando um aplicativo Node.js para lidar com solicitações de suporte

Objetivo da seção: O objetivo é criar um aplicativo Node.js usando Express.js que sirva como back-end para receber solicitações de suporte dos usuários. Essas solicitações podem incluir feedback textual, logs de erros, capturas de tela e outras informações relevantes que podem ajudar no diagnóstico e resolução de problemas do usuário. O aplicativo armazena esses dados no Armazenamento de Blob do Azure para acesso organizado e seguro.

Framework & Ferramentas

  • Express.js: Uma estrutura Node.js para a criação de aplicações Web e APIs. Ele serve como base para a configuração do nosso servidor e tratamento de solicitações.
  • Formidável: uma biblioteca para análise de dados de formulário, especialmente projetada para lidar com dados de várias partes/formulários, que é frequentemente usada para uploads de arquivos.
  • Armazenamento de Blobs do Azure: um serviço do Microsoft Azure para o armazenamento de grandes quantidades de dados não estruturados.

Etapa 1: Configuração do ambiente

Antes de começar, verifique se o ambiente de desenvolvimento está pronto com Node.js instalado. Você também precisa acessar uma conta de Armazenamento do Azure para armazenar os dados enviados.

  1. Instalar Node.js: Certifique-se de que Node.js está instalado no seu sistema. Você pode baixá-lo do Node.js.

  2. Criar uma Conta de Armazenamento de Blob do Azure: se ainda não o fez, crie uma conta de Armazenamento do Azure através do portal do Azure. Essa conta é usada para armazenar os dados da solicitação de suporte.

  3. Reunir credenciais necessárias: verifique se você tem a cadeia de conexão para sua conta de Armazenamento de Blob do Azure.

Etapa 2: Configuração do aplicativo

  1. Inicializar um novo projeto de Node.js:

    • Crie um novo diretório para seu projeto e inicialize-o com npm init para criar um package.json arquivo.

    • Instale o Express.js, o Formidable, o SDK do Blob de Armazenamento do Azure e outras bibliotecas necessárias usando o npm.

      npm install express formidable @azure/storage-blob uuid
      
  2. Implementação do servidor:

    • Use Express.js para configurar um servidor Web básico que escuta solicitações POST em um ponto de extremidade específico.
    • Use Formidable para analisar dados de formulário de entrada, manipulando conteúdo de dados de várias partes/formulários.
    • Gere um número de tíquete exclusivo para cada solicitação de suporte, que pode ser usado para organizar dados no Armazenamento de Blobs do Azure e fornecer uma referência para os usuários.
    • Armazene dados estruturados, como mensagens do usuário e metadados do arquivo de log, em um arquivo JSON dentro do Armazenamento de Blobs. Armazene arquivos de log reais e quaisquer capturas de tela ou anexos em blobs separados dentro do diretório do mesmo ticket.
    • Forneça um ponto de extremidade para recuperar detalhes de suporte, que envolvem buscar e exibir dados do Armazenamento de Blobs do Azure.
  3. Considerações de segurança:

    • Certifique-se de que seu aplicativo valide os dados recebidos para proteger contra cargas maliciosas.
    • Use variáveis de ambiente para armazenar com segurança informações confidenciais, como sua cadeia de conexão do Armazenamento do Azure.

Etapa 3: Executando e testando o aplicativo

  1. Variáveis de ambiente:

    • Configure variáveis de ambiente para sua cadeia de conexão de Armazenamento de Blobs do Azure e quaisquer outras informações confidenciais. Por exemplo, você pode usar um .env arquivo (e o dotenv pacote npm para carregar essas variáveis).
  2. Executando o servidor:

    • Inicie o aplicativo Node.js executando node <filename>.js, onde <filename> é o nome do arquivo do servidor principal.
    • Valide o servidor com uma ferramenta adequada para desenvolvimento web.

Código do servidor:

Desde que aqui esteja uma implementação de trabalho para começar. Esse código é uma implementação básica adaptada para demonstrar a criação de tíquetes a partir dos aplicativos de exemplo da interface do usuário do ACS.

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}`);
});

Configuração do lado do cliente

Esta seção aborda a configuração do lado do cliente e como atingir as seguintes metas:

  1. Registre-se para problemas relatados pelo usuário.
  2. Serialize os dados.
  3. Encaminhe-o para o servidor.
  4. Receba uma resposta.
  5. Apresente a resposta ao utilizador.

Habilitar comentários do usuário na Biblioteca de Interface do Usuário dos Serviços de Comunicação do Azure (ACS) requer ação por parte dos desenvolvedores. Ao utilizar o onUserReportedIssueEventHandler na integração da biblioteca, os desenvolvedores podem habilitar o formulário de suporte integrado, permitindo que os usuários relatem problemas diretamente. Esta seção o orienta na configuração do formulário de feedback do lado do cliente.

Implementando a captura de feedback do lado do cliente no Android

Ativando o formulário de suporte

  1. Registro do manipulador de eventos:

    • Para ativar o formulário de suporte dentro do seu aplicativo Android, registre-o onUserReportedIssueEventHandler em um ponto apropriado no ciclo de vida do seu aplicativo. Este registo não só permite o formulário, mas também garante que se torna visível e acessível aos utilizadores.
  2. Visibilidade e acessibilidade do formulário:

    • A presença do cadastrado onUserReportedIssueEventHandler afeta diretamente a visibilidade do formulário de suporte. Sem esse manipulador, o formulário permanece oculto da interface do usuário, tornando-o inacessível para relatórios de problemas.

Capturando e processando eventos de suporte

  1. Emissão de eventos após a emissão de relatórios:

    • Quando os usuários relatam problemas por meio do formulário de suporte habilitado, as onUserReportedIssueEventHandler capturas emitem eventos. Esses eventos encapsulam todos os detalhes necessários relacionados ao problema relatado pelo usuário, como descrições, logs de erros e, potencialmente, capturas de tela.
  2. Preparação de dados para submissão:

    • Depois que um usuário relata um problema, a próxima etapa envolve a preparação dos dados do problema relatado para o envio do servidor. Essa preparação inclui a estruturação das informações capturadas em um formato adequado para transmissão HTTP, aderindo às expectativas do servidor.

Enviando dados de problema para o servidor

  1. Transmissão assíncrona de dados:

    • Utilize mecanismos assíncronos para transmitir os dados preparados para o ponto de extremidade do servidor designado. Essa abordagem garante que o aplicativo permaneça responsivo, proporcionando uma experiência de usuário suave enquanto os dados estão sendo enviados em segundo plano.
  2. Tratamento de resposta do servidor:

    • Após o envio de dados, é crucial lidar com as respostas do servidor adequadamente. Esse tratamento pode envolver a análise de comentários do servidor para confirmar a transmissão de dados bem-sucedida e, possivelmente, extrair uma referência ao problema enviado (como um número de tíquete ou URL) que possa ser comunicada de volta ao usuário.

Fornecendo comentários e notificações do usuário

  1. Feedback imediato do usuário:

    • Notifique os usuários imediatamente sobre o status do envio do relatório de problemas por meio da interface do usuário do aplicativo. Para envios bem-sucedidos, considere fornecer uma referência ao problema enviado que permita aos usuários acompanhar o progresso do relatório.
  2. Estratégia de notificação para Android O e mais recentes:

    • Para dispositivos com Android O (nível de API 26) e mais recentes, certifique-se da implementação de um canal de notificação específico para envios de relatórios. Esta configuração é essencial para entregar notificações de forma eficaz e é um requisito nestas versões do Android.

Seguindo essas etapas, os desenvolvedores podem integrar um mecanismo robusto de feedback do usuário em seus aplicativos Android, usando o onUserReportedIssueEventHandler para relatórios e rastreamento de problemas eficientes. Esse processo não só facilita a resolução oportuna de problemas do usuário, mas também contribui significativamente para melhorar a experiência geral do usuário e a satisfação com o aplicativo.

Exemplo de código Android

O trecho de código Kotlin demonstra o processo de integração de um sistema para lidar com problemas relatados pelo usuário em um aplicativo Android usando os Serviços de Comunicação do Azure. Esta integração visa agilizar o processo de suporte, permitindo a comunicação direta entre utilizadores e equipas de suporte. Aqui está uma visão geral das etapas envolvidas:

  1. Captura de eventos: o sistema escuta os problemas relatados pelo usuário por meio da biblioteca de interface do usuário do ACS. Ele utiliza o onUserReportedIssueEventHandler para capturar feedback da interface do usuário do aplicativo, incluindo erros e preocupações do usuário.

  2. Transmissão de dados para o servidor: Quando um problema é relatado, o sistema empacota os dados relevantes, incluindo mensagens do usuário, logs de erros, versões e informações de diagnóstico. Esses dados são enviados para um ponto de extremidade do servidor usando uma solicitação POST assíncrona, garantindo que o processo não prejudique o desempenho do aplicativo.

  3. Feedback e notificação do usuário: Após o envio, os usuários são imediatamente informados sobre o status de sua denúncia por meio de notificações no aplicativo. Para envios bem-sucedidos, uma notificação inclui um link ou referência ao tíquete enviado, permitindo que os usuários acompanhem o progresso da resolução.

Essa configuração não só ajuda a resolver rapidamente os problemas do usuário, mas também contribui significativamente para melhorar a satisfação do usuário e a confiabilidade do aplicativo, fornecendo um canal claro para suporte e feedback.

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)
    }
}

Depois de ter um manipulador para seu evento, você pode registrá-lo ao criar seu composto de chamada.

            callComposite.addOnUserReportedEventHandler(userReportedIssueEventHandler)

Visão geral do suporte do iOS

Para integrar a coleta de comentários do usuário em aplicativos iOS usando a Biblioteca de Interface do Usuário dos Serviços de Comunicação do Azure (ACS), os desenvolvedores precisam seguir uma abordagem estruturada. Esse processo envolve a captura de comentários do usuário, incluindo logs de erros e informações do usuário. Uma vez concluídas, essas informações são enviadas a um servidor para processamento. Nesta seção, detalhamos as etapas necessárias para realizar essa tarefa.

Neste exemplo, estamos usando a biblioteca Alamofire para lidar com o envio de um formulário de várias partes, incluindo arquivos de log, para o servidor.

Implementando o formulário de suporte

  1. Registro do manipulador de eventos: comece registrando um manipulador de eventos que escuta os problemas relatados pelo usuário. Esse manipulador é crucial para capturar feedback diretamente da interface do seu aplicativo iOS, usando os recursos da ACS UI Library.

  2. Visibilidade e acessibilidade do formulário: Certifique-se de que o formulário de suporte seja prontamente acessível e visível para os usuários dentro do aplicativo. A ativação do formulário está diretamente ligada à implementação do manipulador de eventos, que dispara sua aparência na interface do usuário, permitindo que os usuários relatem problemas.

Captura e processamento de solicitações de suporte

  1. Emissão de Eventos na Ação do Usuário: Quando um usuário relata um problema por meio do formulário de suporte, o manipulador de eventos captura essa ação. As informações, como a descrição do problema pelo usuário, logs de erros e quaisquer IDs de chamada, devem ser preparadas para serem enviadas ao servidor.

  2. Estruturação de Dados para Submissão: Organize as informações capturadas em um formato estruturado adequado para transmissão. Prepare os dados de forma alinhada com o formato esperado do ponto de extremidade do servidor que recebe e processa a solicitação de suporte.

Enviando dados para o servidor

  1. Envio assíncrono: utilize chamadas de rede assíncronas para enviar os dados estruturados para o servidor. Essa abordagem garante que o aplicativo permaneça responsivo, proporcionando uma experiência perfeita para o usuário enquanto os dados são transmitidos em segundo plano.

  2. Manipulando respostas do servidor: Após o envio, processe eficientemente as respostas do servidor. Receba e analise a resposta para confirmar o recebimento bem-sucedido dos dados. Extraia o link do tíquete de suporte da resposta analisada, que pode ser comunicada de volta ao usuário para acompanhamento.

Feedback e notificações aos usuários

  1. Confirmação imediata: Reconhecer imediatamente o envio de uma solicitação de suporte dentro do aplicativo, fornecendo aos usuários a confirmação de que sua denúncia foi recebida.

  2. Estratégia de notificação: implemente uma estratégia para entregar notificações aos usuários, especialmente em dispositivos que executam versões do iOS que suportam estruturas de notificação específicas. Você pode usar notificações locais para informar os usuários sobre o status do relatório ou fornecer atualizações à medida que o problema é resolvido.

Exemplo de código do iOS

Este exemplo de código Swift descreve uma implementação básica para capturar problemas relatados pelo usuário e enviá-los a um servidor para processamento. Este exemplo mostra como construir um manipulador de eventos de suporte, incluindo comentários do usuário e informações de diagnóstico do aplicativo e entrega ao servidor. O código também inclui estratégias de tratamento de erros e notificação do usuário para garantir uma experiência de usuário suave.

O exemplo a seguir foi projetado para ser um gancho a ser instalado no manipulador de eventos.

Instalação

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
        }
    }
}

Gancho de rede

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)")
        }
    }
}

Este código Swift demonstra o processo de envio de problemas relatados pelo usuário de um aplicativo iOS usando os Serviços de Comunicação do Azure. Ele lida com a coleta de comentários do usuário, empacotamento de informações de diagnóstico e envio assíncrono para um ponto de extremidade do servidor. Além disso, fornece a base para a implementação de mecanismos de feedback, garantindo que os usuários sejam informados sobre o status de seus relatórios e aumentando a confiabilidade geral do aplicativo e a satisfação do usuário.

Conclusão

A integração de mecanismos de feedback do usuário em aplicativos que usam os Serviços de Comunicação do Azure (ACS) é crucial para o desenvolvimento de aplicativos responsivos e focados no usuário. Este guia fornece um caminho claro para configurar o processamento do lado do servidor com captura de feedback Node.js e do lado do cliente para aplicativos Android e iOS. Por meio dessa integração, os desenvolvedores podem melhorar a confiabilidade do aplicativo e a satisfação do usuário enquanto utilizam os serviços de nuvem do Azure para um gerenciamento de dados eficiente.

O guia descreve etapas práticas para capturar comentários de usuários, logs de erros e solicitações de suporte diretamente de aplicativos. A integração de eventos de suporte garante uma maneira segura e organizada de lidar com o feedback, permitindo que os desenvolvedores abordem e resolvam rapidamente os problemas do usuário, levando a uma melhor experiência geral do usuário.

Seguindo as instruções detalhadas neste guia, os desenvolvedores podem melhorar a capacidade de resposta de seus aplicativos e atender melhor às necessidades do usuário. Essas integrações não só ajudam a entender o feedback do usuário de forma mais eficaz, mas também utilizam serviços em nuvem para garantir um mecanismo de coleta e processamento de feedback suave e eficaz. Em última análise, a integração de mecanismos de feedback do usuário é essencial para criar aplicativos atraentes e confiáveis que priorizam a satisfação do usuário.