File: D:/web/hyperflight/apps/search-log-analysis/index-v1.asp
<%
Option Explicit
Response.Buffer = True
Response.CodePage = 65001
Response.CharSet = "utf-8"
' ======================================================================
' Search Log Analysis
' Bootstrap 5 / Classic ASP / MySQL
' ======================================================================
Const adCmdText = 1
Const adParamInput = 1
Const adVarChar = 200
Dim conn, cmd
Dim rsSummary, rsSources, rsDaily, rsTerms, rsProducts, rsVisitors, rsRecent
Dim startDate, endDate, sourceFilter, keyword, topLimit, recentLimit
Dim whereSql, sourceSql, keywordSql
Dim totalSearches, uniqueSessions, uniqueTerms
Dim incrementalSearches, productSelections, distinctProducts, selectionRate
Dim dateRangeDays, validationMessage
Dim sourceName, sourceLabel, sourceBadge, sourceCount, sourceSessions, sourceSelections
Dim sourcePercent, dayValue, searchTerm, productCode, productName
Dim searchCount, sessionCount, selectedCount, lastUsed, visitorIP
Dim eventID, sessionID, searchSource, lastUpdated
Dim dateLabel, rangeLabel
Function IsoDate(ByVal value)
IsoDate = Year(value) & "-" & Right("0" & Month(value), 2) & "-" & Right("0" & Day(value), 2)
End Function
Function IsValidIsoDate(ByVal value)
Dim parts, testDate
IsValidIsoDate = False
If Len(value) <> 10 Then Exit Function
parts = Split(value, "-")
If UBound(parts) <> 2 Then Exit Function
If Not IsNumeric(parts(0)) Or Not IsNumeric(parts(1)) Or Not IsNumeric(parts(2)) Then Exit Function
On Error Resume Next
testDate = DateSerial(CInt(parts(0)), CInt(parts(1)), CInt(parts(2)))
If Err.Number = 0 Then
If Year(testDate) = CInt(parts(0)) And Month(testDate) = CInt(parts(1)) And Day(testDate) = CInt(parts(2)) Then
IsValidIsoDate = True
End If
End If
Err.Clear
On Error GoTo 0
End Function
Function ParseIsoDate(ByVal value)
Dim parts
parts = Split(value, "-")
ParseIsoDate = DateSerial(CInt(parts(0)), CInt(parts(1)), CInt(parts(2)))
End Function
Function Html(ByVal value)
If IsNull(value) Then
Html = ""
Else
Html = Server.HTMLEncode(CStr(value))
End If
End Function
Function Number0(ByVal value)
If IsNull(value) Or value = "" Then
Number0 = "0"
Else
Number0 = FormatNumber(CDbl(value), 0)
End If
End Function
Function SourceDescription(ByVal value)
Select Case LCase(value)
Case "button"
SourceDescription = "Search button"
Case "incremental"
SourceDescription = "Incremental selection"
Case "url"
SourceDescription = "URL/query-string"
Case Else
SourceDescription = "Legacy"
End Select
End Function
Function SourceBadgeClass(ByVal value)
Select Case LCase(value)
Case "button"
SourceBadgeClass = "text-bg-primary"
Case "incremental"
SourceBadgeClass = "text-bg-success"
Case "url"
SourceBadgeClass = "text-bg-warning"
Case Else
SourceBadgeClass = "text-bg-secondary"
End Select
End Function
Sub AddCommonParameters(ByRef commandObject, ByVal dateFrom, ByVal dateTo, ByVal searchText)
commandObject.Parameters.Append commandObject.CreateParameter("@DateFrom", adVarChar, adParamInput, 10, dateFrom)
commandObject.Parameters.Append commandObject.CreateParameter("@DateTo", adVarChar, adParamInput, 10, dateTo)
If searchText <> "" Then
commandObject.Parameters.Append commandObject.CreateParameter("@Keyword", adVarChar, adParamInput, 110, "%" & searchText & "%")
End If
End Sub
' ----------------------------------------------------------------------
' Filters and defaults
' ----------------------------------------------------------------------
startDate = Trim(Request.QueryString("StartDate"))
endDate = Trim(Request.QueryString("EndDate"))
sourceFilter = LCase(Trim(Request.QueryString("Source")))
keyword = Trim(Request.QueryString("Keyword"))
topLimit = Trim(Request.QueryString("TopLimit"))
recentLimit = Trim(Request.QueryString("RecentLimit"))
If startDate = "" Then startDate = IsoDate(DateAdd("d", -6, Date()))
If endDate = "" Then endDate = IsoDate(Date())
If sourceFilter = "" Then sourceFilter = "human"
If topLimit = "" Or Not IsNumeric(topLimit) Then topLimit = 25
If recentLimit = "" Or Not IsNumeric(recentLimit) Then recentLimit = 100
topLimit = CInt(topLimit)
recentLimit = CInt(recentLimit)
If topLimit <> 10 And topLimit <> 25 And topLimit <> 50 And topLimit <> 100 Then topLimit = 25
If recentLimit <> 50 And recentLimit <> 100 And recentLimit <> 250 And recentLimit <> 500 Then recentLimit = 100
Select Case sourceFilter
Case "human", "all", "legacy", "button", "incremental", "url"
' Valid.
Case Else
sourceFilter = "human"
End Select
validationMessage = ""
If Not IsValidIsoDate(startDate) Or Not IsValidIsoDate(endDate) Then
validationMessage = "Please enter valid start and end dates."
ElseIf ParseIsoDate(startDate) > ParseIsoDate(endDate) Then
validationMessage = "The start date must not be later than the end date."
Else
dateRangeDays = DateDiff("d", ParseIsoDate(startDate), ParseIsoDate(endDate)) + 1
If dateRangeDays > 366 Then
validationMessage = "Please select a date range of 366 days or fewer."
End If
End If
sourceSql = ""
Select Case sourceFilter
Case "human"
sourceSql = " AND sl.SearchSource IN ('legacy','button','incremental') "
Case "legacy", "button", "incremental", "url"
sourceSql = " AND sl.SearchSource = '" & sourceFilter & "' "
End Select
keywordSql = ""
If keyword <> "" Then keywordSql = " AND sl.Search LIKE ? "
whereSql = " WHERE sl.LastUpdated >= ? " & _
"AND sl.LastUpdated < DATE_ADD(?, INTERVAL 1 DAY) " & _
"AND TRIM(IFNULL(sl.Search, '')) <> '' " & _
sourceSql & keywordSql
rangeLabel = startDate & " to " & endDate
' ----------------------------------------------------------------------
' Run report queries
' ----------------------------------------------------------------------
If validationMessage = "" Then
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open "DSN=MySQL_hyperflight;"
' Summary
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandType = adCmdText
cmd.CommandText = _
"SELECT " & _
"COUNT(*) AS TotalSearches, " & _
"COUNT(DISTINCT sl.SessionID) AS UniqueSessions, " & _
"COUNT(DISTINCT TRIM(sl.Search)) AS UniqueTerms, " & _
"SUM(CASE WHEN sl.SearchSource = 'incremental' THEN 1 ELSE 0 END) AS IncrementalSearches, " & _
"SUM(CASE WHEN sl.ProductCode IS NOT NULL AND sl.ProductCode <> '' THEN 1 ELSE 0 END) AS ProductSelections, " & _
"COUNT(DISTINCT CASE WHEN sl.ProductCode IS NOT NULL AND sl.ProductCode <> '' THEN sl.ProductCode END) AS DistinctProducts " & _
"FROM searchlog sl " & whereSql
AddCommonParameters cmd, startDate, endDate, keyword
Set rsSummary = cmd.Execute()
totalSearches = 0
uniqueSessions = 0
uniqueTerms = 0
incrementalSearches = 0
productSelections = 0
distinctProducts = 0
If Not rsSummary.EOF Then
If Not IsNull(rsSummary("TotalSearches")) Then totalSearches = CLng(rsSummary("TotalSearches"))
If Not IsNull(rsSummary("UniqueSessions")) Then uniqueSessions = CLng(rsSummary("UniqueSessions"))
If Not IsNull(rsSummary("UniqueTerms")) Then uniqueTerms = CLng(rsSummary("UniqueTerms"))
If Not IsNull(rsSummary("IncrementalSearches")) Then incrementalSearches = CLng(rsSummary("IncrementalSearches"))
If Not IsNull(rsSummary("ProductSelections")) Then productSelections = CLng(rsSummary("ProductSelections"))
If Not IsNull(rsSummary("DistinctProducts")) Then distinctProducts = CLng(rsSummary("DistinctProducts"))
End If
rsSummary.Close
Set rsSummary = Nothing
Set cmd = Nothing
selectionRate = 0
If incrementalSearches > 0 Then selectionRate = (productSelections / incrementalSearches) * 100
' Source breakdown
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandType = adCmdText
cmd.CommandText = _
"SELECT sl.SearchSource, COUNT(*) AS SearchCount, " & _
"COUNT(DISTINCT sl.SessionID) AS SessionCount, " & _
"SUM(CASE WHEN sl.ProductCode IS NOT NULL AND sl.ProductCode <> '' THEN 1 ELSE 0 END) AS SelectedCount " & _
"FROM searchlog sl " & whereSql & _
"GROUP BY sl.SearchSource ORDER BY SearchCount DESC"
AddCommonParameters cmd, startDate, endDate, keyword
Set rsSources = cmd.Execute()
Set cmd = Nothing
' Daily activity
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandType = adCmdText
cmd.CommandText = _
"SELECT DATE(sl.LastUpdated) AS SearchDate, COUNT(*) AS SearchCount, " & _
"COUNT(DISTINCT sl.SessionID) AS SessionCount, " & _
"SUM(CASE WHEN sl.ProductCode IS NOT NULL AND sl.ProductCode <> '' THEN 1 ELSE 0 END) AS SelectedCount " & _
"FROM searchlog sl " & whereSql & _
"GROUP BY DATE(sl.LastUpdated) ORDER BY SearchDate DESC"
AddCommonParameters cmd, startDate, endDate, keyword
Set rsDaily = cmd.Execute()
Set cmd = Nothing
' Top search terms
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandType = adCmdText
cmd.CommandText = _
"SELECT TRIM(sl.Search) AS SearchTerm, COUNT(*) AS SearchCount, " & _
"COUNT(DISTINCT sl.SessionID) AS SessionCount, " & _
"SUM(CASE WHEN sl.ProductCode IS NOT NULL AND sl.ProductCode <> '' THEN 1 ELSE 0 END) AS SelectedCount, " & _
"MAX(sl.LastUpdated) AS LastUsed " & _
"FROM searchlog sl " & whereSql & _
"GROUP BY TRIM(sl.Search) " & _
"ORDER BY SearchCount DESC, SessionCount DESC, SearchTerm ASC " & _
"LIMIT " & topLimit
AddCommonParameters cmd, startDate, endDate, keyword
Set rsTerms = cmd.Execute()
Set cmd = Nothing
' Most selected products
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandType = adCmdText
cmd.CommandText = _
"SELECT sl.ProductCode, MAX(p.ProductName) AS ProductName, COUNT(*) AS SearchCount, " & _
"COUNT(DISTINCT sl.SessionID) AS SessionCount, MAX(sl.LastUpdated) AS LastUsed " & _
"FROM searchlog sl " & _
"LEFT JOIN products p ON p.ProductCode = sl.ProductCode " & _
whereSql & _
"AND sl.ProductCode IS NOT NULL AND sl.ProductCode <> '' " & _
"GROUP BY sl.ProductCode " & _
"ORDER BY SearchCount DESC, sl.ProductCode ASC " & _
"LIMIT " & topLimit
AddCommonParameters cmd, startDate, endDate, keyword
Set rsProducts = cmd.Execute()
Set cmd = Nothing
' Highest-volume visitor IPs
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandType = adCmdText
cmd.CommandText = _
"SELECT IFNULL(NULLIF(sl.VisitorIP, ''), '(blank)') AS VisitorIP, COUNT(*) AS SearchCount, " & _
"COUNT(DISTINCT sl.SessionID) AS SessionCount, " & _
"COUNT(DISTINCT TRIM(sl.Search)) AS UniqueTerms, MAX(sl.LastUpdated) AS LastUsed " & _
"FROM searchlog sl " & whereSql & _
"GROUP BY IFNULL(NULLIF(sl.VisitorIP, ''), '(blank)') " & _
"ORDER BY SearchCount DESC, VisitorIP ASC LIMIT " & topLimit
AddCommonParameters cmd, startDate, endDate, keyword
Set rsVisitors = cmd.Execute()
Set cmd = Nothing
' Recent detail
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandType = adCmdText
cmd.CommandText = _
"SELECT sl.ID, sl.LastUpdated, sl.SearchSource, sl.Search, sl.ProductCode, " & _
"sl.SessionID, sl.VisitorIP, p.ProductName " & _
"FROM searchlog sl " & _
"LEFT JOIN products p ON p.ProductCode = sl.ProductCode " & _
whereSql & _
"ORDER BY sl.LastUpdated DESC, sl.ID DESC LIMIT " & recentLimit
AddCommonParameters cmd, startDate, endDate, keyword
Set rsRecent = cmd.Execute()
Set cmd = Nothing
End If
%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Search Log Analysis</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background: #f5f6f8; }
.report-wrap { max-width: 1500px; }
.metric-value { font-size: 1.8rem; font-weight: 700; line-height: 1.1; }
.metric-label { color: #6c757d; font-size: 0.9rem; }
.table th { white-space: nowrap; }
.search-text { min-width: 260px; max-width: 620px; overflow-wrap: anywhere; }
.product-name { min-width: 240px; }
.small-note { font-size: 0.875rem; color: #6c757d; }
.source-progress { min-width: 170px; }
.sticky-table-head thead th { position: sticky; top: 0; z-index: 2; }
.card-header h2 { font-size: 1.05rem; margin: 0; }
</style>
</head>
<body class="pb-5">
<div class="container-fluid report-wrap py-4 px-3 px-lg-4">
<div class="card shadow-sm mb-4">
<div class="card-header bg-primary text-white py-3">
<div class="d-flex flex-column flex-lg-row justify-content-between gap-2 align-items-lg-center">
<div>
<h1 class="h4 mb-1">Search Log Analysis</h1>
<div class="small opacity-75">Search behaviour, source usage and incremental product selections</div>
</div>
<span class="badge text-bg-light"><%=Html(rangeLabel)%></span>
</div>
</div>
<div class="card-body">
<form method="get" action="" class="row g-3 align-items-end">
<div class="col-sm-6 col-lg-2">
<label for="StartDate" class="form-label fw-semibold">Start date</label>
<input type="date" class="form-control" id="StartDate" name="StartDate" value="<%=Html(startDate)%>" required>
</div>
<div class="col-sm-6 col-lg-2">
<label for="EndDate" class="form-label fw-semibold">End date</label>
<input type="date" class="form-control" id="EndDate" name="EndDate" value="<%=Html(endDate)%>" required>
</div>
<div class="col-sm-6 col-lg-2">
<label for="Source" class="form-label fw-semibold">Search source</label>
<select class="form-select" id="Source" name="Source">
<option value="human" <% If sourceFilter = "human" Then Response.Write "selected" %>>Non-URL searches</option>
<option value="all" <% If sourceFilter = "all" Then Response.Write "selected" %>>All sources</option>
<option value="button" <% If sourceFilter = "button" Then Response.Write "selected" %>>Search button</option>
<option value="incremental" <% If sourceFilter = "incremental" Then Response.Write "selected" %>>Incremental selection</option>
<option value="legacy" <% If sourceFilter = "legacy" Then Response.Write "selected" %>>Legacy</option>
<option value="url" <% If sourceFilter = "url" Then Response.Write "selected" %>>URL/query-string</option>
</select>
</div>
<div class="col-sm-6 col-lg-3">
<label for="Keyword" class="form-label fw-semibold">Search contains</label>
<input type="search" class="form-control" id="Keyword" name="Keyword" value="<%=Html(keyword)%>" maxlength="100" placeholder="Optional term">
</div>
<div class="col-sm-6 col-lg-1">
<label for="TopLimit" class="form-label fw-semibold">Top</label>
<select class="form-select" id="TopLimit" name="TopLimit">
<option value="10" <% If topLimit = 10 Then Response.Write "selected" %>>10</option>
<option value="25" <% If topLimit = 25 Then Response.Write "selected" %>>25</option>
<option value="50" <% If topLimit = 50 Then Response.Write "selected" %>>50</option>
<option value="100" <% If topLimit = 100 Then Response.Write "selected" %>>100</option>
</select>
</div>
<div class="col-sm-6 col-lg-2 d-grid">
<input type="hidden" name="RecentLimit" value="<%=recentLimit%>">
<button type="submit" class="btn btn-primary fw-semibold">Run report</button>
</div>
</form>
</div>
</div>
<% If validationMessage <> "" Then %>
<div class="alert alert-warning"><%=Html(validationMessage)%></div>
<% Else %>
<% If sourceFilter = "all" Or sourceFilter = "url" Then %>
<div class="alert alert-warning shadow-sm">
<strong>URL searches need separate interpretation.</strong>
The sample data shows that many are crawler-generated or unrelated query-string requests, so they should not normally be treated as customer search demand.
</div>
<% End If %>
<div class="alert alert-info shadow-sm">
<strong>Data note:</strong> Search source and selected product tracking were added on 23 July 2026. Earlier records may therefore appear as <em>legacy</em>, and product selection rates are most meaningful from that date onward.
</div>
<div class="row g-3 mb-4">
<div class="col-6 col-xl">
<div class="card shadow-sm h-100"><div class="card-body">
<div class="metric-value"><%=Number0(totalSearches)%></div>
<div class="metric-label">Search events</div>
</div></div>
</div>
<div class="col-6 col-xl">
<div class="card shadow-sm h-100"><div class="card-body">
<div class="metric-value"><%=Number0(uniqueSessions)%></div>
<div class="metric-label">Sessions</div>
</div></div>
</div>
<div class="col-6 col-xl">
<div class="card shadow-sm h-100"><div class="card-body">
<div class="metric-value"><%=Number0(uniqueTerms)%></div>
<div class="metric-label">Distinct terms</div>
</div></div>
</div>
<div class="col-6 col-xl">
<div class="card shadow-sm h-100"><div class="card-body">
<div class="metric-value"><%=Number0(productSelections)%></div>
<div class="metric-label">Product selections</div>
</div></div>
</div>
<div class="col-6 col-xl">
<div class="card shadow-sm h-100"><div class="card-body">
<div class="metric-value"><%=Number0(distinctProducts)%></div>
<div class="metric-label">Products selected</div>
</div></div>
</div>
<div class="col-6 col-xl">
<div class="card shadow-sm h-100"><div class="card-body">
<div class="metric-value"><%=FormatNumber(selectionRate, 1)%>%</div>
<div class="metric-label">Incremental selection rate</div>
</div></div>
</div>
</div>
<div class="row g-4 mb-4">
<div class="col-xl-6">
<div class="card shadow-sm h-100">
<div class="card-header bg-dark text-white"><h2>Search source breakdown</h2></div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr><th>Source</th><th class="text-end">Searches</th><th class="text-end">Sessions</th><th class="text-end">Selections</th><th class="source-progress">Share</th></tr>
</thead>
<tbody>
<% If rsSources.EOF Then %>
<tr><td colspan="5" class="text-center text-muted py-4">No searches found.</td></tr>
<% Else %>
<% Do While Not rsSources.EOF
sourceName = rsSources("SearchSource") & ""
sourceCount = CLng(rsSources("SearchCount"))
sourceSessions = CLng(rsSources("SessionCount"))
sourceSelections = 0
If Not IsNull(rsSources("SelectedCount")) Then sourceSelections = CLng(rsSources("SelectedCount"))
sourcePercent = 0
If totalSearches > 0 Then sourcePercent = CInt((sourceCount / totalSearches) * 100)
%>
<tr>
<td><span class="badge <%=SourceBadgeClass(sourceName)%>"><%=Html(SourceDescription(sourceName))%></span></td>
<td class="text-end"><%=Number0(sourceCount)%></td>
<td class="text-end"><%=Number0(sourceSessions)%></td>
<td class="text-end"><%=Number0(sourceSelections)%></td>
<td>
<div class="progress" role="progressbar" aria-label="Source share" aria-valuenow="<%=sourcePercent%>" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar" style="width: <%=sourcePercent%>%"><%=sourcePercent%>%</div>
</div>
</td>
</tr>
<% rsSources.MoveNext : Loop %>
<% End If %>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-xl-6">
<div class="card shadow-sm h-100">
<div class="card-header bg-dark text-white"><h2>Daily activity</h2></div>
<div class="table-responsive" style="max-height: 360px;">
<table class="table table-hover mb-0 sticky-table-head">
<thead class="table-light"><tr><th>Date</th><th class="text-end">Searches</th><th class="text-end">Sessions</th><th class="text-end">Selections</th></tr></thead>
<tbody>
<% If rsDaily.EOF Then %>
<tr><td colspan="4" class="text-center text-muted py-4">No daily activity found.</td></tr>
<% Else %>
<% Do While Not rsDaily.EOF %>
<tr>
<td><%=Html(rsDaily("SearchDate"))%></td>
<td class="text-end"><%=Number0(rsDaily("SearchCount"))%></td>
<td class="text-end"><%=Number0(rsDaily("SessionCount"))%></td>
<td class="text-end"><%=Number0(rsDaily("SelectedCount"))%></td>
</tr>
<% rsDaily.MoveNext : Loop %>
<% End If %>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="row g-4 mb-4">
<div class="col-xl-7">
<div class="card shadow-sm h-100">
<div class="card-header bg-primary text-white"><h2>Top search terms</h2></div>
<div class="table-responsive">
<table class="table table-striped table-hover align-middle mb-0">
<thead class="table-light"><tr><th>#</th><th>Search term</th><th class="text-end">Searches</th><th class="text-end">Sessions</th><th class="text-end">Selections</th><th>Last used</th></tr></thead>
<tbody>
<% If rsTerms.EOF Then %>
<tr><td colspan="6" class="text-center text-muted py-4">No search terms found.</td></tr>
<% Else %>
<% Dim termPosition : termPosition = 0
Do While Not rsTerms.EOF
termPosition = termPosition + 1
%>
<tr>
<td class="text-muted"><%=termPosition%></td>
<td class="search-text fw-semibold"><%=Html(rsTerms("SearchTerm"))%></td>
<td class="text-end"><%=Number0(rsTerms("SearchCount"))%></td>
<td class="text-end"><%=Number0(rsTerms("SessionCount"))%></td>
<td class="text-end"><%=Number0(rsTerms("SelectedCount"))%></td>
<td class="text-nowrap"><%=Html(rsTerms("LastUsed"))%></td>
</tr>
<% rsTerms.MoveNext : Loop %>
<% End If %>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-xl-5">
<div class="card shadow-sm h-100">
<div class="card-header bg-success text-white"><h2>Most selected products</h2></div>
<div class="table-responsive">
<table class="table table-striped table-hover align-middle mb-0">
<thead class="table-light"><tr><th>Product</th><th class="text-end">Selections</th><th class="text-end">Sessions</th><th>Last selected</th></tr></thead>
<tbody>
<% If rsProducts.EOF Then %>
<tr><td colspan="4" class="text-center text-muted py-4">No incremental product selections found.</td></tr>
<% Else %>
<% Do While Not rsProducts.EOF
productCode = rsProducts("ProductCode") & ""
productName = rsProducts("ProductName") & ""
%>
<tr>
<td>
<a href="/products.asp?code=<%=Server.URLEncode(productCode)%>" target="_blank" rel="noopener" class="fw-semibold"><%=Html(productCode)%></a>
<% If productName <> "" Then %><div class="small text-muted product-name"><%=Html(productName)%></div><% End If %>
</td>
<td class="text-end"><%=Number0(rsProducts("SearchCount"))%></td>
<td class="text-end"><%=Number0(rsProducts("SessionCount"))%></td>
<td class="text-nowrap"><%=Html(rsProducts("LastUsed"))%></td>
</tr>
<% rsProducts.MoveNext : Loop %>
<% End If %>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="card shadow-sm mb-4">
<div class="card-header bg-secondary text-white"><h2>Highest-volume visitor IPs</h2></div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light"><tr><th>Visitor IP</th><th class="text-end">Searches</th><th class="text-end">Sessions</th><th class="text-end">Distinct terms</th><th>Last used</th></tr></thead>
<tbody>
<% If rsVisitors.EOF Then %>
<tr><td colspan="5" class="text-center text-muted py-4">No visitor IP data found.</td></tr>
<% Else %>
<% Do While Not rsVisitors.EOF %>
<tr>
<td class="font-monospace"><%=Html(rsVisitors("VisitorIP"))%></td>
<td class="text-end"><%=Number0(rsVisitors("SearchCount"))%></td>
<td class="text-end"><%=Number0(rsVisitors("SessionCount"))%></td>
<td class="text-end"><%=Number0(rsVisitors("UniqueTerms"))%></td>
<td class="text-nowrap"><%=Html(rsVisitors("LastUsed"))%></td>
</tr>
<% rsVisitors.MoveNext : Loop %>
<% End If %>
</tbody>
</table>
</div>
</div>
<div class="card shadow-sm">
<div class="card-header bg-dark text-white">
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
<h2>Recent search detail</h2>
<form method="get" action="" class="d-flex align-items-center gap-2">
<input type="hidden" name="StartDate" value="<%=Html(startDate)%>">
<input type="hidden" name="EndDate" value="<%=Html(endDate)%>">
<input type="hidden" name="Source" value="<%=Html(sourceFilter)%>">
<input type="hidden" name="Keyword" value="<%=Html(keyword)%>">
<input type="hidden" name="TopLimit" value="<%=topLimit%>">
<label for="RecentLimit" class="small text-nowrap">Rows:</label>
<select class="form-select form-select-sm" id="RecentLimit" name="RecentLimit" onchange="this.form.submit()">
<option value="50" <% If recentLimit = 50 Then Response.Write "selected" %>>50</option>
<option value="100" <% If recentLimit = 100 Then Response.Write "selected" %>>100</option>
<option value="250" <% If recentLimit = 250 Then Response.Write "selected" %>>250</option>
<option value="500" <% If recentLimit = 500 Then Response.Write "selected" %>>500</option>
</select>
</form>
</div>
</div>
<div class="table-responsive" style="max-height: 720px;">
<table class="table table-striped table-hover align-middle mb-0 sticky-table-head">
<thead class="table-light">
<tr><th>Date/time</th><th>Source</th><th>Search</th><th>Selected product</th><th>Session</th><th>Visitor IP</th></tr>
</thead>
<tbody>
<% If rsRecent.EOF Then %>
<tr><td colspan="6" class="text-center text-muted py-4">No searches found.</td></tr>
<% Else %>
<% Do While Not rsRecent.EOF
searchSource = rsRecent("SearchSource") & ""
productCode = rsRecent("ProductCode") & ""
productName = rsRecent("ProductName") & ""
%>
<tr>
<td class="text-nowrap"><%=Html(rsRecent("LastUpdated"))%></td>
<td><span class="badge <%=SourceBadgeClass(searchSource)%>"><%=Html(SourceDescription(searchSource))%></span></td>
<td class="search-text"><%=Html(rsRecent("Search"))%></td>
<td>
<% If productCode <> "" Then %>
<a href="/products.asp?code=<%=Server.URLEncode(productCode)%>" target="_blank" rel="noopener" class="fw-semibold"><%=Html(productCode)%></a>
<% If productName <> "" Then %><div class="small text-muted"><%=Html(productName)%></div><% End If %>
<% Else %>
<span class="text-muted">—</span>
<% End If %>
</td>
<td class="font-monospace"><%=Html(rsRecent("SessionID"))%></td>
<td class="font-monospace"><%=Html(rsRecent("VisitorIP"))%></td>
</tr>
<% rsRecent.MoveNext : Loop %>
<% End If %>
</tbody>
</table>
</div>
</div>
<div class="small-note mt-3">
Incremental selection rate is calculated as product selections divided by incremental search events. This report cannot currently identify searches returning no results because result count is not stored in <code>searchlog</code>.
</div>
<%
rsSources.Close : Set rsSources = Nothing
rsDaily.Close : Set rsDaily = Nothing
rsTerms.Close : Set rsTerms = Nothing
rsProducts.Close : Set rsProducts = Nothing
rsVisitors.Close : Set rsVisitors = Nothing
rsRecent.Close : Set rsRecent = Nothing
conn.Close : Set conn = Nothing
End If
%>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>