Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions web/migrations/0003_lookupdata_date_time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 4.2.27 on 2025-12-28 00:20

from django.db import migrations, models
import django.utils.timezone


class Migration(migrations.Migration):

dependencies = [
('web', '0002_lookupdata_version1_lookupdata_version2'),
]

operations = [
migrations.AddField(
model_name='lookupdata',
name='date_time',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
]
25 changes: 25 additions & 0 deletions web/migrations/0004_missinglookup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.2.27 on 2025-12-28 00:33

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('web', '0003_lookupdata_date_time'),
]

operations = [
migrations.CreateModel(
name='MissingLookup',
fields=[
('id', models.BigAutoField(primary_key=True, serialize=False)),
('date_time', models.DateTimeField(auto_now_add=True)),
('item_type', models.CharField(max_length=20)),
('item_value', models.CharField(max_length=100)),
('language_context', models.CharField(blank=True, max_length=50, null=True)),
('site_visit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.sitevisit')),
],
),
]
11 changes: 10 additions & 1 deletion web/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,10 +377,19 @@ class SiteVisit(models.Model):

class LookupData(models.Model):
id = models.BigAutoField(primary_key=True)
date_time = models.DateTimeField
date_time = models.DateTimeField(auto_now_add=True)
language1 = models.CharField(max_length=50)
version1 = models.CharField(max_length=20, default='')
language2 = models.CharField(max_length=50)
version2 = models.CharField(max_length=20, default='')
structure = models.CharField(max_length=50)
site_visit = models.ForeignKey(SiteVisit, on_delete=models.CASCADE)


class MissingLookup(models.Model):
id = models.BigAutoField(primary_key=True)
date_time = models.DateTimeField(auto_now_add=True)
item_type = models.CharField(max_length=20) # 'language', 'structure', 'concept'
item_value = models.CharField(max_length=100)
language_context = models.CharField(max_length=50, blank=True, null=True)
site_visit = models.ForeignKey(SiteVisit, on_delete=models.CASCADE)
16 changes: 16 additions & 0 deletions web/static/js/contributors.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,20 @@ document.addEventListener("DOMContentLoaded", function () {
contributorsRequest.onerror = function () {
document.querySelector("#contributors").innerHTML = "multiple";
};

var repoRequest = new XMLHttpRequest();
repoRequest.open(
"GET",
"https://api.github.com/repos/codethesaurus/codethesaur.us"
);
repoRequest.send();

repoRequest.onload = function () {
if (repoRequest.status === 200) {
let repoData = JSON.parse(repoRequest.responseText);
let lastUpdate = new Date(repoData.pushed_at);
let options = { year: 'numeric', month: 'long', day: 'numeric' };
document.querySelector("#last-update").innerHTML = lastUpdate.toLocaleDateString(undefined, options);
}
};
});
4 changes: 4 additions & 0 deletions web/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
<li class="nav-item">
<a class="nav-link" href="/about">About</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/statistics">Statistics</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://docs.codethesaur.us">Docs</a>
</li>
Expand All @@ -79,6 +82,7 @@
<div class="row">
<div class="col-8 justify-content-start">
<p>Made with &#x2764; by Sarah Withee and <a href="https://github.com/codethesaurus/codethesaur.us/graphs/contributors" target="_blank" rel="noreferrer"><span id="contributors"></span> contributors</a>.</p> <!-- x2764 the heart emoji code -->
<p>Last GitHub update: <span id="last-update"></span></p>
<p>Want to help out? Check the project out on <a href="http://github.com/codethesaurus/" target="_blank" rel="noopener">GitHub</a>.</p>
</div>
<div class="col-4 order-sm-2 d-flex justify-content-end align-self-center">
Expand Down
273 changes: 273 additions & 0 deletions web/templates/statistics.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
{% extends 'base.html' %}

{% block content %}
<div class="container mt-4">
<div class="row">
<div class="col-12">
<h1>Site Statistics</h1>
<p class="lead">Insights into how developers are using Code Thesaurus.</p>
</div>
</div>

<div class="row mt-4">
<div class="col-md-4">
<div class="card mb-4 shadow-sm h-100">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="fas fa-users"></i> Overall Activity</h5>
</div>
<div class="card-body">
<div class="row text-center mb-3">
<div class="col-6">
<h2 class="display-4">{{ total_visits }}</h2>
<p class="text-muted">Total Visits</p>
</div>
<div class="col-6">
<h2 class="display-4">{{ total_lookups }}</h2>
<p class="text-muted">Total Lookups</p>
</div>
</div>
<hr>
<div class="row text-center">
<div class="col-6 border-end">
<h4 class="mb-0">{{ unique_comparisons_count }}</h4>
<small class="text-muted">Unique Lang Comparisons</small>
</div>
<div class="col-6">
<h4 class="mb-0">{{ unique_structures_count }}</h4>
<small class="text-muted">Unique Category Lookups</small>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-8">
<div class="card mb-4 shadow-sm h-100">
<div class="card-header bg-success text-white">
<h5 class="mb-0"><i class="fas fa-chart-bar"></i> Language Popularity</h5>
</div>
<div class="card-body">
<canvas id="languagesChart" height="100"></canvas>
</div>
</div>
</div>
</div>

<div class="row">
<div class="col-12">
<div class="card mb-4 shadow-sm">
<div class="card-header bg-dark text-white">
<h5 class="mb-0"><i class="fas fa-chart-line"></i> Most Popular Specific Concept Lookups</h5>
</div>
<div class="card-body">
<p class="text-muted small">Comparing how often specific concepts are looked up for each language (e.g., "Javascript functions").</p>
<canvas id="conceptLangsChart" height="80"></canvas>
</div>
</div>
</div>
</div>

<div class="row">
<div class="col-12">
<div class="card mb-4 shadow-sm">
<div class="card-header bg-secondary text-white">
<h5 class="mb-0"><i class="fas fa-history"></i> Recent Lookups</h5>
</div>
<div class="card-body">
{% if recent_lookups %}
<table class="table table-hover table-sm">
<thead>
<tr>
<th>Time</th>
<th>Language 1</th>
<th>Language 2</th>
<th>Concept</th>
</tr>
</thead>
<tbody>
{% for lookup in recent_lookups %}
<tr>
<td>{{ lookup.date_time|date:"Y-m-d H:i" }}</td>
<td>{{ lookup.lang1 }}</td>
<td>{% if lookup.lang2 %}{{ lookup.lang2 }}{% else %}<span class="text-muted">-</span>{% endif %}</td>
<td>{{ lookup.structure }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="text-center">No recent lookups recorded.</p>
{% endif %}
</div>
</div>
</div>
</div>

<div class="row">
<div class="col-12">
<div class="card mb-4 shadow-sm">
<div class="card-header bg-warning text-dark">
<h5 class="mb-0"><i class="fas fa-exchange-alt"></i> Top Comparisons</h5>
</div>
<div class="card-body">
{% if popular_comparisons %}
<table class="table table-hover table-sm">
<thead>
<tr>
<th>Language 1</th>
<th>Language 2</th>
<th class="text-end">Count</th>
</tr>
</thead>
<tbody>
{% for comp in popular_comparisons %}
<tr>
<td>{{ comp.lang1 }}</td>
<td>{{ comp.lang2 }}</td>
<td class="text-end">{{ comp.count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="text-center">No comparisons made yet.</p>
{% endif %}
</div>
</div>
</div>
</div>

<div class="row">
<div class="col-12">
<div class="card mb-4 shadow-sm border-danger">
<div class="card-header bg-danger text-white">
<h5 class="mb-0"><i class="fas fa-exclamation-triangle"></i> Most Requested Missing Items</h5>
</div>
<div class="card-body">
<p class="text-muted small">Tracking languages, structures, or concepts that were requested but are currently missing from the site.</p>
{% if missing_items %}
<table class="table table-hover table-sm">
<thead>
<tr>
<th>Item</th>
<th class="text-end">Requests</th>
</tr>
</thead>
<tbody>
{% for item in missing_items %}
<tr>
<td>
{% if item.type == 'language' %}
<span class="badge bg-primary">Language</span>
{% elif item.type == 'structure' %}
<span class="badge bg-info">Structure</span>
{% else %}
<span class="badge bg-secondary">Concept</span>
{% endif %}
{{ item.label }}
</td>
<td class="text-end">{{ item.count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="text-center">No missing items requested yet.</p>
{% endif %}
</div>
</div>
</div>
</div>

<div class="row">
<div class="col-md-6">
<div class="card mb-4 shadow-sm">
<div class="card-header bg-info text-white">
<h5 class="mb-0"><i class="fas fa-chart-pie"></i> Category Popularity</h5>
</div>
<div class="card-body">
<canvas id="structuresChart"></canvas>
</div>
</div>
</div>
</div>
</div>

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const langData = {{ popular_languages_json|safe }};
const structData = {{ popular_structures_json|safe }};
const conceptLangData = {{ popular_concept_langs_json|safe }};

if (langData.length > 0) {
new Chart(document.getElementById('languagesChart'), {
type: 'bar',
data: {
labels: langData.map(item => item.name),
datasets: [{
label: 'Lookups',
data: langData.map(item => item.count),
backgroundColor: 'rgba(40, 167, 69, 0.7)',
borderColor: 'rgba(40, 167, 69, 1)',
borderWidth: 1
}]
},
options: {
indexAxis: 'y',
scales: {
x: { beginAtZero: true }
},
plugins: {
legend: { display: false }
}
}
});
}

if (structData.length > 0) {
new Chart(document.getElementById('structuresChart'), {
type: 'doughnut',
data: {
labels: structData.map(item => item.name),
datasets: [{
data: structData.map(item => item.count),
backgroundColor: [
'#17a2b8', '#28a745', '#ffc107', '#dc3545', '#007bff',
'#6610f2', '#e83e8c', '#fd7e14', '#20c997', '#6c757d'
]
}]
},
options: {
plugins: {
legend: { position: 'right' }
}
}
});
}

if (conceptLangData.length > 0) {
new Chart(document.getElementById('conceptLangsChart'), {
type: 'bar',
data: {
labels: conceptLangData.map(item => item.label),
datasets: [{
label: 'Lookups',
data: conceptLangData.map(item => item.count),
backgroundColor: 'rgba(52, 58, 64, 0.7)',
borderColor: 'rgba(52, 58, 64, 1)',
borderWidth: 1
}]
},
options: {
scales: {
y: { beginAtZero: true }
},
plugins: {
legend: { display: false }
}
}
});
}
});
</script>
{% endblock %}
Loading
Loading