Skip to content

Commit 8dfc203

Browse files
committed
Fixed HTML table parsing and email loading
1 parent f2213c0 commit 8dfc203

2 files changed

Lines changed: 102 additions & 9 deletions

File tree

Github/Services/IssueContentGenerator.php

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ private function generateManualContent(Conversation $conversation)
194194
$customerEmail = 'No email';
195195
if ($conversation->customer) {
196196
$customerName = $conversation->customer->getFullName() ?: 'Unknown Customer';
197-
$customerEmail = $conversation->customer->email ?: 'No email';
197+
$customerEmail = $conversation->customer->getMainEmail() ?: 'No email';
198198
}
199199

200200
// Generate title
@@ -317,8 +317,8 @@ private function extractConversationText(Conversation $conversation)
317317

318318
foreach ($threads as $thread) {
319319
$sender = $thread->type === \App\Thread::TYPE_CUSTOMER ? 'Customer' : 'Support';
320-
$body = strip_tags($thread->body);
321-
$body = strlen($body) > 300 ? substr($body, 0, 300) . '...' : $body;
320+
$body = $this->extractStructuredContent($thread->body);
321+
$body = strlen($body) > 800 ? substr($body, 0, 800) . '...' : $body; // Increased limit for structured content
322322

323323
$text .= "[$sender]: $body\n\n";
324324
}
@@ -327,13 +327,101 @@ private function extractConversationText(Conversation $conversation)
327327
return $text;
328328
}
329329

330+
/**
331+
* Extract structured content from HTML, preserving form field structure
332+
*/
333+
private function extractStructuredContent($html)
334+
{
335+
// Check if this looks like a structured HTML table form
336+
if (strpos($html, '<table') !== false && strpos($html, '<strong>') !== false) {
337+
return $this->parseHTMLTable($html);
338+
}
339+
340+
// Fall back to regular strip_tags for simple content
341+
return strip_tags($html);
342+
}
343+
344+
/**
345+
* Parse HTML table structure to extract form fields
346+
*/
347+
private function parseHTMLTable($html)
348+
{
349+
try {
350+
$structured = [];
351+
352+
// Create DOMDocument to parse HTML properly
353+
$dom = new \DOMDocument();
354+
355+
// Suppress HTML parsing warnings for malformed HTML
356+
libxml_use_internal_errors(true);
357+
$dom->loadHTML('<?xml encoding="utf-8" ?>' . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
358+
libxml_clear_errors();
359+
360+
// Find all table rows
361+
$rows = $dom->getElementsByTagName('tr');
362+
$currentField = null;
363+
364+
foreach ($rows as $row) {
365+
$cells = $row->getElementsByTagName('td');
366+
367+
if ($cells->length >= 2) {
368+
$firstCell = trim($cells->item(0)->textContent);
369+
$secondCell = trim($cells->item(1)->textContent);
370+
371+
// Check if first cell contains a field label (has <strong> tag)
372+
$strongTags = $cells->item(0)->getElementsByTagName('strong');
373+
if ($strongTags->length > 0) {
374+
$currentField = trim($strongTags->item(0)->textContent);
375+
} else if (!empty($secondCell) && !empty($currentField) && $secondCell !== '&nbsp;') {
376+
// This is a value row for the current field
377+
$structured[$currentField] = $secondCell;
378+
$currentField = null;
379+
}
380+
}
381+
}
382+
383+
// Format the structured data
384+
$formatted = [];
385+
foreach ($structured as $field => $value) {
386+
if (!empty($value) && $value !== '&nbsp;') {
387+
$formatted[] = "{$field}: {$value}";
388+
}
389+
}
390+
391+
$result = implode("\n", $formatted);
392+
393+
// If we got structured data, return it, otherwise fall back to strip_tags
394+
return !empty($result) ? $result : strip_tags($html);
395+
396+
} catch (\Exception $e) {
397+
// If HTML parsing fails, fall back to strip_tags
398+
\Helper::log('github_html_parsing', 'HTML parsing failed: ' . $e->getMessage());
399+
return strip_tags($html);
400+
}
401+
}
402+
330403
/**
331404
* Build AI prompt for content generation
332405
*/
333406
private function buildPrompt($conversationText, Conversation $conversation)
334407
{
335-
$customerName = $conversation->customer ? $conversation->customer->getFullName() : 'Unknown Customer';
336-
$customerEmail = $conversation->customer ? $conversation->customer->email : 'No email';
408+
$customerName = 'Unknown Customer';
409+
$customerEmail = 'No email';
410+
411+
if ($conversation->customer) {
412+
$customerName = $conversation->customer->getFullName() ?: 'Unknown Customer';
413+
$customerEmail = $conversation->customer->getMainEmail() ?: 'No email';
414+
} else {
415+
// Try to load customer manually if the relationship didn't work
416+
if (!empty($conversation->customer_id)) {
417+
$customer = \App\Customer::find($conversation->customer_id);
418+
if ($customer) {
419+
$customerName = $customer->getFullName() ?: 'Unknown Customer';
420+
$customerEmail = $customer->getMainEmail() ?: 'No email';
421+
}
422+
}
423+
}
424+
337425
$conversationUrl = url("/conversation/" . $conversation->id);
338426
$status = ucfirst($conversation->getStatusName());
339427

@@ -342,7 +430,7 @@ private function buildPrompt($conversationText, Conversation $conversation)
342430

343431
if (!empty($customPrompt)) {
344432
// Use custom template with variable replacement
345-
return str_replace([
433+
$prompt = str_replace([
346434
'{customer_name}',
347435
'{customer_email}',
348436
'{conversation_url}',
@@ -355,10 +443,12 @@ private function buildPrompt($conversationText, Conversation $conversation)
355443
$status,
356444
$conversationText
357445
], $customPrompt);
446+
447+
return $prompt;
358448
}
359449

360450
// Default prompt template
361-
return "Create a GitHub issue from this customer support conversation.
451+
$prompt = "Create a GitHub issue from this customer support conversation.
362452
363453
Customer: $customerName
364454
Customer Email: $customerEmail
@@ -384,6 +474,8 @@ private function buildPrompt($conversationText, Conversation $conversation)
384474
\"title\": \"Issue title here\",
385475
\"body\": \"Issue body with markdown formatting\"
386476
}";
477+
478+
return $prompt;
387479
}
388480

389481
/**

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,9 +355,10 @@ This module is released under the same license as FreeScout. See the FreeScout l
355355

356356
### v1.0.2 (July 31 2025)
357357
- **Enhanced**: Refresh functionality now fetches fresh data from GitHub API with intelligent caching
358-
- **Fixed**: Customer email placeholders in AI-generated issue content now show actual email addresses
358+
- **Enhanced**: Improved support for HTML tables in tickets (i.e. from a Gravity Forms submission)
359+
- **Enhanced**: Added support for `{customer_email}` placeholder in AI-generated issue content
359360
- **Fixed**: GitHub webhook 404 errors by removing CSRF middleware from webhook route
360-
- **Fixed**: Conversation status sync behavior - removed automatic status changes per user workflow
361+
- **Fixed**: Conversation status sync behavior - removed automatic sync between closing tickets and GitHub issues
361362

362363
### v1.0.1 (July 25 2025)
363364
- **Fixed**: Modal z-index issues in production environments

0 commit comments

Comments
 (0)