File: D:/web/hyperflight/apps/search-log-analysis/index.asp
<%
Option Explicit
Response.Buffer = True
Response.CodePage = 65001
Response.CharSet = "utf-8"
' ======================================================================
' Search Log Analysis
' Bootstrap 5 / Classic ASP / MySQL
' ResultCount reporting added 24 July 2026
' ======================================================================
Const adCmdText = 1
Const adParamInput = 1
Const adVarChar = 200
Dim conn, cmd
Dim rsSummary, rsSources, rsDaily, rsTerms, rsNoResults, rsProducts, rsVisitors, rsRecent
Dim startDate, endDate, sourceFilter, resultStatus, keyword, topLimit, recentLimit
Dim whereSql, sourceSql, resultSql, keywordSql
Dim totalSearches, uniqueSessions, uniqueTerms
Dim incrementalSearches, productSelections, distinctProducts, selectionRate
Dim resultRecordedCount, noResultSearches, withResultSearches, noResultRate, averageResultCount
Dim groupRecordedCount, groupNoResultCount, groupNoResultRate
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, resultValue
Dim dateLabel, rangeLabel, noResultPosition
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
Function ResultCountBadge(ByVal value)
If IsNull(value) Then
ResultCountBadge = "<span class=""text-muted"">—</span>"
ElseIf CLng(value) = 0 Then
ResultCountBadge = "<span class=""badge text-bg-danger"">0</span>"
Else
ResultCountBadge = "<span class=""badge text-bg-success"">" & Number0(value) & "</span>"
End If
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")))
resultStatus = LCase(Trim(Request.QueryString("ResultStatus")))
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 resultStatus = "" Then resultStatus = "all"
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
Select Case resultStatus
Case "all", "recorded", "zero", "positive", "unrecorded"
' Valid.
Case Else
resultStatus = "all"
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
resultSql = ""
Select Case resultStatus
Case "recorded"
resultSql = " AND sl.ResultCount IS NOT NULL "
Case "zero"
resultSql = " AND sl.ResultCount = 0 "
Case "positive"
resultSql = " AND sl.ResultCount > 0 "
Case "unrecorded"
resultSql = " AND sl.ResultCount IS NULL "
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 & resultSql & 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, " & _
"SUM(CASE WHEN sl.ResultCount IS NOT NULL THEN 1 ELSE 0 END) AS ResultRecordedCount, " & _
"SUM(CASE WHEN sl.ResultCount = 0 THEN 1 ELSE 0 END) AS NoResultSearches, " & _
"SUM(CASE WHEN sl.ResultCount > 0 THEN 1 ELSE 0 END) AS WithResultSearches, " & _
"AVG(sl.ResultCount) AS AverageResultCount " & _
"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
resultRecordedCount = 0
noResultSearches = 0
withResultSearches = 0
noResultRate = 0
averageResultCount = Null
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"))
If Not IsNull(rsSummary("ResultRecordedCount")) Then resultRecordedCount = CLng(rsSummary("ResultRecordedCount"))
If Not IsNull(rsSummary("NoResultSearches")) Then noResultSearches = CLng(rsSummary("NoResultSearches"))
If Not IsNull(rsSummary("WithResultSearches")) Then withResultSearches = CLng(rsSummary("WithResultSearches"))
If Not IsNull(rsSummary("AverageResultCount")) Then averageResultCount = CDbl(rsSummary("AverageResultCount"))
End If
rsSummary.Close
Set rsSummary = Nothing
Set cmd = Nothing
selectionRate = 0
If incrementalSearches > 0 Then selectionRate = (productSelections / incrementalSearches) * 100
noResultRate = 0
If resultRecordedCount > 0 Then noResultRate = (noResultSearches / resultRecordedCount) * 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, " & _
"SUM(CASE WHEN sl.ResultCount IS NOT NULL THEN 1 ELSE 0 END) AS ResultRecordedCount, " & _
"SUM(CASE WHEN sl.ResultCount = 0 THEN 1 ELSE 0 END) AS NoResultCount " & _
"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, " & _
"SUM(CASE WHEN sl.ResultCount IS NOT NULL THEN 1 ELSE 0 END) AS ResultRecordedCount, " & _
"SUM(CASE WHEN sl.ResultCount = 0 THEN 1 ELSE 0 END) AS NoResultCount " & _
"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, " & _
"SUM(CASE WHEN sl.ResultCount IS NOT NULL THEN 1 ELSE 0 END) AS ResultRecordedCount, " & _
"SUM(CASE WHEN sl.ResultCount = 0 THEN 1 ELSE 0 END) AS NoResultCount, " & _
"AVG(sl.ResultCount) AS AverageResultCount, " & _
"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 common searches returning no results
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandType = adCmdText
cmd.CommandText = _
"SELECT TRIM(sl.Search) AS SearchTerm, COUNT(*) AS NoResultCount, " & _
"COUNT(DISTINCT sl.SessionID) AS SessionCount, MAX(sl.LastUpdated) AS LastUsed " & _
"FROM searchlog sl " & whereSql & _
"AND sl.ResultCount = 0 " & _
"GROUP BY TRIM(sl.Search) " & _
"ORDER BY NoResultCount DESC, SessionCount DESC, SearchTerm ASC " & _
"LIMIT " & topLimit
AddCommonParameters cmd, startDate, endDate, keyword
Set rsNoResults = 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, " & _
"SUM(CASE WHEN sl.ResultCount IS NOT NULL THEN 1 ELSE 0 END) AS ResultRecordedCount, " & _
"SUM(CASE WHEN sl.ResultCount = 0 THEN 1 ELSE 0 END) AS NoResultCount, " & _
"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.ResultCount, 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-breakdown-table { font-size: 0.925rem; }
.source-breakdown-table th,
.source-breakdown-table td { padding-left: 0.55rem; padding-right: 0.55rem; }
.source-breakdown-table .source-cell { min-width: 130px; }
.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, result counts, no-result searches 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-2">
<label for="ResultStatus" class="form-label fw-semibold">Result status</label>
<select class="form-select" id="ResultStatus" name="ResultStatus">
<option value="all" <% If resultStatus = "all" Then Response.Write "selected" %>>All statuses</option>
<option value="recorded" <% If resultStatus = "recorded" Then Response.Write "selected" %>>Count recorded</option>
<option value="zero" <% If resultStatus = "zero" Then Response.Write "selected" %>>No results</option>
<option value="positive" <% If resultStatus = "positive" Then Response.Write "selected" %>>One or more results</option>
<option value="unrecorded" <% If resultStatus = "unrecorded" Then Response.Write "selected" %>>Not recorded</option>
</select>
</div>
<div class="col-sm-6 col-lg-2">
<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-1 d-grid">
<input type="hidden" name="RecentLimit" value="<%=recentLimit%>">
<button type="submit" class="btn btn-primary fw-semibold">Run</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. Result count tracking was added on 24 July 2026. A result count of <strong>0</strong> means no products were found; a positive number is the number of products found; and a blank value means the count was not recorded. Incremental selections and legacy records normally have no result count.
</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-3 mb-4">
<div class="col-6 col-xl">
<div class="card shadow-sm h-100 border-primary"><div class="card-body">
<div class="metric-value"><%=Number0(resultRecordedCount)%></div>
<div class="metric-label">Result counts recorded</div>
</div></div>
</div>
<div class="col-6 col-xl">
<div class="card shadow-sm h-100 border-danger"><div class="card-body">
<div class="metric-value text-danger"><%=Number0(noResultSearches)%></div>
<div class="metric-label">Searches returning no results</div>
</div></div>
</div>
<div class="col-6 col-xl">
<div class="card shadow-sm h-100 border-success"><div class="card-body">
<div class="metric-value text-success"><%=Number0(withResultSearches)%></div>
<div class="metric-label">Searches returning results</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(noResultRate, 1)%>%</div>
<div class="metric-label">No-result rate</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"><% If IsNull(averageResultCount) Then Response.Write "—" Else Response.Write FormatNumber(averageResultCount, 1) %></div>
<div class="metric-label">Average results returned</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-sm table-hover align-middle mb-0 source-breakdown-table">
<thead class="table-light">
<tr><th class="source-cell">Source</th><th class="text-end">Searches</th><th class="text-end">Sessions</th><th class="text-end" title="Result counts recorded">Recorded</th><th class="text-end">No results</th><th class="text-end" title="No-result rate">Rate</th><th class="text-end">Selections</th><th class="text-end">Share</th></tr>
</thead>
<tbody>
<% If rsSources.EOF Then %>
<tr><td colspan="8" 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"))
groupRecordedCount = 0
If Not IsNull(rsSources("ResultRecordedCount")) Then groupRecordedCount = CLng(rsSources("ResultRecordedCount"))
groupNoResultCount = 0
If Not IsNull(rsSources("NoResultCount")) Then groupNoResultCount = CLng(rsSources("NoResultCount"))
groupNoResultRate = 0
If groupRecordedCount > 0 Then groupNoResultRate = (groupNoResultCount / groupRecordedCount) * 100
sourcePercent = 0
If totalSearches > 0 Then sourcePercent = CInt((sourceCount / totalSearches) * 100)
%>
<tr>
<td class="source-cell"><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(groupRecordedCount)%></td>
<td class="text-end"><%=Number0(groupNoResultCount)%></td>
<td class="text-end"><%=FormatNumber(groupNoResultRate, 1)%>%</td>
<td class="text-end"><%=Number0(sourceSelections)%></td>
<td class="text-end"><%=sourcePercent%>%</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">Counts recorded</th><th class="text-end">No results</th><th class="text-end">No-result rate</th><th class="text-end">Selections</th></tr></thead>
<tbody>
<% If rsDaily.EOF Then %>
<tr><td colspan="7" class="text-center text-muted py-4">No daily activity found.</td></tr>
<% Else %>
<% Do While Not rsDaily.EOF
groupRecordedCount = 0
If Not IsNull(rsDaily("ResultRecordedCount")) Then groupRecordedCount = CLng(rsDaily("ResultRecordedCount"))
groupNoResultCount = 0
If Not IsNull(rsDaily("NoResultCount")) Then groupNoResultCount = CLng(rsDaily("NoResultCount"))
groupNoResultRate = 0
If groupRecordedCount > 0 Then groupNoResultRate = (groupNoResultCount / groupRecordedCount) * 100
%>
<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(groupRecordedCount)%></td>
<td class="text-end"><%=Number0(groupNoResultCount)%></td>
<td class="text-end"><%=FormatNumber(groupNoResultRate, 1)%>%</td>
<td class="text-end"><%=Number0(rsDaily("SelectedCount"))%></td>
</tr>
<% rsDaily.MoveNext : Loop %>
<% End If %>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="card shadow-sm mb-4">
<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">Counts recorded</th><th class="text-end">No results</th><th class="text-end">No-result rate</th><th class="text-end">Average results</th><th class="text-end">Selections</th><th>Last used</th></tr></thead>
<tbody>
<% If rsTerms.EOF Then %>
<tr><td colspan="10" 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
groupRecordedCount = 0
If Not IsNull(rsTerms("ResultRecordedCount")) Then groupRecordedCount = CLng(rsTerms("ResultRecordedCount"))
groupNoResultCount = 0
If Not IsNull(rsTerms("NoResultCount")) Then groupNoResultCount = CLng(rsTerms("NoResultCount"))
groupNoResultRate = 0
If groupRecordedCount > 0 Then groupNoResultRate = (groupNoResultCount / groupRecordedCount) * 100
%>
<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(groupRecordedCount)%></td>
<td class="text-end"><%=Number0(groupNoResultCount)%></td>
<td class="text-end"><%=FormatNumber(groupNoResultRate, 1)%>%</td>
<td class="text-end"><% If IsNull(rsTerms("AverageResultCount")) Then Response.Write "—" Else Response.Write FormatNumber(rsTerms("AverageResultCount"), 1) %></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 class="row g-4 mb-4">
<div class="col-xl-7">
<div class="card shadow-sm h-100 border-danger">
<div class="card-header bg-danger text-white"><h2>Most common searches returning no results</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">No-result searches</th><th class="text-end">Sessions</th><th>Last no-result search</th></tr></thead>
<tbody>
<% If rsNoResults.EOF Then %>
<tr><td colspan="5" class="text-center text-muted py-4">No searches returning no results match the current filters.</td></tr>
<% Else %>
<% noResultPosition = 0
Do While Not rsNoResults.EOF
noResultPosition = noResultPosition + 1
%>
<tr>
<td class="text-muted"><%=noResultPosition%></td>
<td class="search-text fw-semibold"><%=Html(rsNoResults("SearchTerm"))%></td>
<td class="text-end fw-semibold text-danger"><%=Number0(rsNoResults("NoResultCount"))%></td>
<td class="text-end"><%=Number0(rsNoResults("SessionCount"))%></td>
<td class="text-nowrap"><%=Html(rsNoResults("LastUsed"))%></td>
</tr>
<% rsNoResults.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 class="text-end">Counts recorded</th><th class="text-end">No results</th><th class="text-end">No-result rate</th><th>Last used</th></tr></thead>
<tbody>
<% If rsVisitors.EOF Then %>
<tr><td colspan="8" class="text-center text-muted py-4">No visitor IP data found.</td></tr>
<% Else %>
<% Do While Not rsVisitors.EOF
groupRecordedCount = 0
If Not IsNull(rsVisitors("ResultRecordedCount")) Then groupRecordedCount = CLng(rsVisitors("ResultRecordedCount"))
groupNoResultCount = 0
If Not IsNull(rsVisitors("NoResultCount")) Then groupNoResultCount = CLng(rsVisitors("NoResultCount"))
groupNoResultRate = 0
If groupRecordedCount > 0 Then groupNoResultRate = (groupNoResultCount / groupRecordedCount) * 100
%>
<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-end"><%=Number0(groupRecordedCount)%></td>
<td class="text-end"><%=Number0(groupNoResultCount)%></td>
<td class="text-end"><%=FormatNumber(groupNoResultRate, 1)%>%</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="ResultStatus" value="<%=Html(resultStatus)%>">
<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 class="text-end">Results</th><th>Selected product</th><th>Session</th><th>Visitor IP</th></tr>
</thead>
<tbody>
<% If rsRecent.EOF Then %>
<tr><td colspan="7" 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 class="text-end"><%=ResultCountBadge(rsRecent("ResultCount"))%></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">
The no-result rate is calculated only from records where <code>ResultCount</code> is populated. A value of 0 means no products were found; a positive value is the number of products found; and a blank value means the count was not recorded. Incremental selection rate is calculated as product selections divided by incremental search events.
</div>
<%
rsSources.Close : Set rsSources = Nothing
rsDaily.Close : Set rsDaily = Nothing
rsTerms.Close : Set rsTerms = Nothing
rsNoResults.Close : Set rsNoResults = 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>