Dashboard Troubleshooting Guide
Diagnostic guide for common dashboard issues and their solutions.
Table of Contents
- Incorrect Overdue Habits
- Next Habit Not Calculating Correctly
- Missing Weekly Statistics
- Per-Day Counts Showing Zeros
- Configuration Not Saving
- Platform-Specific Configuration Issues
- Dashboard Endpoint Returns 401
- Dashboard Endpoint Returns 400
- Slow Dashboard Performance
- Next Task Not Appearing
Incorrect Overdue Habits
Symptoms
- Habits showing as overdue when they shouldn’t be
- Habits not showing as overdue when they should be
- Incorrect count in
overdueHabitsfield
Common Causes
1. Missing or Invalid dateTime Field
Diagnosis:
Check the habit’s dateTime in database:
SELECT id, name, frequency, date_time, done
FROM habits
WHERE user_id = ?;Solution:
- Ensure
date_timeis not NULL - Ensure format is valid ISO 8601:
YYYY-MM-DDTHH:mm:ss - If missing, update habit:
UPDATE habits
SET date_time = '2024-01-16T09:00:00'
WHERE id = ?;2. Incorrect Frequency Type
Diagnosis: Check debug logs:
=== OVERDUE HABITS DEBUG ===
Checking: Morning run (DALY) - Base: 2024-01-16 07:00
UNKNOWN frequency: DALYSolution: Fix typo in frequency field:
UPDATE habits
SET frequency = 'DAILY'
WHERE id = ? AND frequency = 'DALY';Valid frequencies: DAILY, WEEKLY, MONTHLY, YEARLY (uppercase)
3. Habit Already Marked as Done
Diagnosis: Check debug logs:
Skipping Morning workout - already doneSolution:
- Verify habit completion status is correct
- If habit should not be done, reset it:
UPDATE habits
SET done = false
WHERE id = ?;4. Timezone Mismatch
Diagnosis: Server and client may be in different timezones, causing time comparison issues.
Check server timezone:
Add to getDashboardData:
println("Server timezone: ${TimeZone.currentSystemDefault()}")
println("Server time: ${Clock.System.now()}")
println("Input time: $currentDateTime")Solution:
- Ensure all datetimes are in UTC or same timezone
- Convert client datetime to server timezone before sending
5. Weekly Habit - Wrong Day Calculation
Symptoms: Weekly habit on Wednesday shows as overdue on Tuesday
Diagnosis: Check debug logs:
WEEKLY: today day of week: 2, target day of week: 3
WEEKLY: target day is in the future this week
Final result for Gym workout: falseSolution: This is correct behavior. The habit is scheduled for Wednesday (day 3), and today is Tuesday (day 2), so it’s not overdue yet.
If the behavior is unexpected, verify the baseDate.dayOfWeek is correct:
// In test or debugging
val habit = habitService.getHabit(habitId)
val baseDateTime = habit.dateTime?.toLocalDateTime()
println("Base day of week: ${baseDateTime?.date?.dayOfWeek}")
println("Expected day: Wednesday")6. Monthly Habit - Day of Month Issue
Symptoms: Monthly habit on the 15th shows as overdue on the 10th
Diagnosis:
Check if targetDayOfMonth is correctly extracted:
MONTHLY: today is target day, time overdue: falseSolution:
Ensure habit dateTime has correct day of month:
-- Check current value
SELECT date_time FROM habits WHERE id = ?;
-- Should be something like: 2024-01-15T10:00:00
-- Day of month = 15Debugging Steps
Step 1: Enable verbose logging (already enabled in DashboardService.kt)
Step 2: Make request and check logs:
curl -X GET "http://localhost:8080/api/v1/dashboard?dateTime=2024-01-16T14:30:00" \
-H "Authorization: Bearer $TOKEN"Step 3: Analyze debug output:
=== OVERDUE HABITS DEBUG ===
Time: 2024-01-16T14:30:00, Total habits: 3
Today: 2024-01-16, Current time: 14:30
Checking: Morning workout (DAILY) - Base: 2024-01-16 07:00
DAILY: next occurrence: 2024-01-16T07:00, has passed: true
Final result for Morning workout: true
...Step 4: Write unit test to reproduce:
@Test
fun `reproduce overdue issue`() {
val habit = HabitDTO(
id = "test-id",
name = "Morning workout",
frequency = "DAILY",
dateTime = "2024-01-16T07:00:00",
done = false,
streak = 5
)
val now = "2024-01-16T14:30:00".toLocalDateTime()
val overdueHabits = service.getOverdueHabitsList(listOf(habit), now)
println("Overdue count: ${overdueHabits.size}")
assertEquals(1, overdueHabits.size) // Should be overdue
}Next Habit Not Calculating Correctly
Symptoms
nextHabitis null when habits exist- Wrong habit returned as “next”
- Habit with earlier time not prioritized
Common Causes
1. All Habits Are Overdue
Diagnosis: Check debug logs:
Non-overdue habits with occurrence: 0
No non-overdue habits found
Selected next habit: nullExplanation:
The getNextHabit algorithm filters out overdue habits. If all habits are overdue, it returns null.
Solution:
- This is expected behavior
- Complete or reschedule overdue habits
- Or modify algorithm to return first overdue habit as fallback:
// At end of getNextHabit (line 374-376)
if (nonOverdueHabitsWithOccurrence.isEmpty()) {
println("No non-overdue habits found, returning first pending habit")
return pendingHabits.firstOrNull() // Return any pending habit
}2. All Habits Are Marked as Done
Diagnosis:
Total habits: 5
Pending habits: 0Solution: Reset completed habits or create new ones:
UPDATE habits
SET done = false
WHERE user_id = ? AND done = true;3. Next Occurrence Calculation Error
Diagnosis: Check debug logs for calculation:
Filtering out overdue habit: Meditation (next occurrence: 2024-01-16T18:00)If time looks correct but still filtered, check current time comparison.
Solution:
Verify now parameter is correct:
val currentDateTime = dateTime.toLocalDateTime()
println("Current time used: $currentDateTime")4. Frequency-Specific Calculation Bug
For WEEKLY habits:
// Verify day of week matching
val targetDayOfWeek = baseDate.dayOfWeek // e.g., DayOfWeek.WEDNESDAY
if (today.dayOfWeek == targetDayOfWeek) today else { /* ... */ }For MONTHLY habits:
// Verify day of month matching
val targetDayOfMonth = baseDate.dayOfMonth // e.g., 15
if (today.dayOfMonth == targetDayOfMonth) today else { /* ... */ }Debugging Steps
Step 1: Check habit data:
SELECT id, name, frequency, date_time, done, streak
FROM habits
WHERE user_id = ?
ORDER BY date_time;Step 2: Manually test calculation:
@Test
fun `debug next habit calculation`() {
val habits = listOf(
HabitDTO(id = "1", name = "Morning", frequency = "DAILY", dateTime = "2024-01-16T07:00:00", done = false, streak = 0),
HabitDTO(id = "2", name = "Evening", frequency = "DAILY", dateTime = "2024-01-16T20:00:00", done = false, streak = 0)
)
val now = "2024-01-16T14:30:00".toLocalDateTime()
val result = service.getNextHabit(habits, now)
println("Next habit: ${result?.name}")
println("Expected: Evening (future time today)")
assertNotNull(result)
assertEquals("Evening", result?.name)
}Step 3: Check categorization:
Today's habits: 2 (future: 1, past: 1)
Future habits: 0
Selected next habit: EveningMissing Weekly Statistics
Symptoms
weeklyTaskCompletion,weeklyHabitCompletion, etc. are 0.0- Expected non-zero values
Common Causes
1. No Track Records Exist
Diagnosis: Check track tables:
-- For tasks
SELECT COUNT(*)
FROM task_tracks
WHERE done_date_time >= '2024-01-15 00:00:00'
AND done_date_time <= '2024-01-21 23:59:59';
-- For habits
SELECT COUNT(*)
FROM habit_tracks
WHERE done_date_time >= '2024-01-15 00:00:00'
AND done_date_time <= '2024-01-21 23:59:59';Solution: Ensure tasks/habits create track records when completed:
// In TaskService.completeTask
taskRepository.update(taskId) { task ->
task.done = true
}
// Create track record
taskTrackRepository.create(TaskTrack(
taskId = taskId,
doneDateTime = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
))2. Week Range Calculation Error
Diagnosis: Check week start/end calculation:
// In getDashboardData (line 47-48)
val weekStart = today.minus(DatePeriod(days = dayOfWeek - 1))
val weekEnd = weekStart.plus(DatePeriod(days = 6))
println("Week range: $weekStart to $weekEnd")Solution:
Ensure dayOfWeek is 1-7 (Mon-Sun):
val dayOfWeek = today.dayOfWeek.ordinal + 1 // 0-6 → 1-73. Track Records Outside Week Range
Diagnosis:
SELECT done_date_time
FROM task_tracks
ORDER BY done_date_time DESC
LIMIT 10;Check if datetimes fall within the calculated week range.
Solution: Verify timezone consistency when creating track records.
4. Division by Zero (should not happen)
Code: total / 7f (line 182)
This always divides by 7, even if count is 0, resulting in 0.0 (not error).
Debugging Steps
Step 1: Manually query tracks:
SELECT
DATE(done_date_time) as day,
COUNT(*) as count
FROM task_tracks
WHERE done_date_time >= '2024-01-15 00:00:00'
AND done_date_time <= '2024-01-21 23:59:59'
GROUP BY DATE(done_date_time)
ORDER BY day;Step 2: Test calculation function:
@Test
fun `weekly completion calculation`() {
// Insert mock track records
val weekStart = LocalDate(2024, 1, 15) // Monday
// Create 3 tracks on Monday, 2 on Tuesday
insertTaskTrack(weekStart.atTime(10, 0))
insertTaskTrack(weekStart.atTime(11, 0))
insertTaskTrack(weekStart.atTime(12, 0))
insertTaskTrack(weekStart.plus(DatePeriod(days = 1)).atTime(10, 0))
insertTaskTrack(weekStart.plus(DatePeriod(days = 1)).atTime(11, 0))
val result = service.calculateTaskCompletionFromTracks(weekStart, TaskTracks.doneDateTime, TaskTrack.Companion::find)
// Total: 5, divided by 7 = 0.71
assertEquals(0.71f, result, 0.01f)
}Per-Day Counts Showing Zeros
Symptoms
tasksCompletedPerDayThisWeek,habitsCompletedPerDayThisWeekare all zeros:[0, 0, 0, 0, 0, 0, 0]
Common Causes
1. Same as “Missing Weekly Statistics”
All causes from previous section apply.
2. Day Range Calculation Error
Diagnosis: Check day boundaries:
val day = weekStart.plus(DatePeriod(days = i)) // i = 0 to 6
val start = day.atTime(0, 0)
val end = day.atTime(23, 59)
println("Day $i: $start to $end")Solution: Ensure time range covers full day (00:00 to 23:59).
If you want inclusive range up to midnight:
val end = day.atTime(23, 59, 59) // Include last second3. Workout Completion Flag Not Set
Specific to workoutsCompletedPerDayThisWeek:
Diagnosis:
SELECT day_of_week, is_completed
FROM workout_days
WHERE user_id = ?;Solution: Ensure workouts are marked as completed:
workoutService.completeWorkout(userId, dayOfWeek)Debugging Steps
Step 1: Check raw data per day:
SELECT
DAYOFWEEK(done_date_time) as day_of_week,
DATE(done_date_time) as date,
COUNT(*) as count
FROM task_tracks
WHERE done_date_time >= '2024-01-15 00:00:00'
AND done_date_time <= '2024-01-21 23:59:59'
GROUP BY DATE(done_date_time)
ORDER BY date;Step 2: Test per-day calculation:
@Test
fun `per-day counts calculation`() {
val weekStart = LocalDate(2024, 1, 15)
// Insert 2 tasks on Monday, 3 on Wednesday
insertTaskTrack(weekStart.atTime(10, 0))
insertTaskTrack(weekStart.atTime(11, 0))
insertTaskTrack(weekStart.plus(DatePeriod(days = 2)).atTime(10, 0))
insertTaskTrack(weekStart.plus(DatePeriod(days = 2)).atTime(11, 0))
insertTaskTrack(weekStart.plus(DatePeriod(days = 2)).atTime(12, 0))
val result = service.getTrackCountsPerDay(weekStart, TaskTracks.doneDateTime, TaskTrack.Companion::find)
assertEquals(listOf(2, 0, 3, 0, 0, 0, 0), result)
}Configuration Not Saving
Symptoms
- POST /api/v1/dashboard/configuration returns 200 OK
- But GET /api/v1/dashboard/configuration returns 404 or old data
Common Causes
1. Platform Mismatch
Diagnosis: Check platform in save request vs. get request:
# Save with platform "DESKTOP"
curl -X POST .../configuration \
-d '{"configuration":{"platform":"DESKTOP",...}}'
# Get with platform "desktop" (lowercase)
curl -X GET .../configuration?platform=desktopSolution: Use exact same platform string (case-sensitive):
curl -X GET .../configuration?platform=DESKTOP2. Database Transaction Not Committed
Diagnosis: Check service implementation:
fun saveConfiguration(userId: Int, configuration: DashboardConfigurationDTO): Boolean {
return transaction {
// ... update logic ...
true // Return inside transaction block
}
}Solution: Ensure transaction is properly committed (Exposed handles this automatically).
3. JSON Serialization Error
Diagnosis: Check server logs for serialization exceptions:
Error serializing configuration: ...Solution: Ensure all configuration fields are serializable:
@Serializable
data class DashboardConfigurationDTO(
val widgets: List<DashboardWidgetDTO> = emptyList(), // Provide defaults
val gridColumns: Int = 3,
val platform: String = "DESKTOP"
)4. Unique Constraint Violation (should be upsert)
Diagnosis: Check for SQL errors:
Unique constraint violation on (user_id, platform)Solution: Ensure upsert logic (update if exists, insert if not):
transaction {
val existing = DashboardConfiguration.find {
(DashboardConfigurations.userId eq userId) and
(DashboardConfigurations.platform eq configuration.platform)
}.firstOrNull()
if (existing != null) {
existing.configurationJson = Json.encodeToString(configuration)
existing.updatedAt = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
} else {
DashboardConfiguration.new {
this.userId = userId
this.platform = configuration.platform
this.configurationJson = Json.encodeToString(configuration)
this.createdAt = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
this.updatedAt = this.createdAt
}
}
}Debugging Steps
Step 1: Check database directly after save:
SELECT user_id, platform, configuration_json, updated_at
FROM dashboard_configurations
WHERE user_id = ?
ORDER BY updated_at DESC;Step 2: Test save and retrieve:
@Test
fun `save and retrieve configuration`() {
val config = DashboardConfigurationDTO(
widgets = listOf(/* ... */),
gridColumns = 3,
platform = "DESKTOP"
)
val saved = repository.saveConfiguration(userId, config)
assertTrue(saved)
val retrieved = repository.getConfiguration(userId, "DESKTOP")
assertNotNull(retrieved)
assertEquals(3, retrieved.gridColumns)
}Platform-Specific Configuration Issues
Symptoms
- Configuration works on DESKTOP but not on ANDROID
- Different platforms show same widgets
Common Causes
1. Not Saving Platform-Specific Config
Solution: Save separate configurations per platform:
// Save for desktop
val desktopConfig = DashboardConfigurationDTO(
widgets = listOf(/* desktop widgets */),
platform = "DESKTOP"
)
api.saveConfiguration(desktopConfig)
// Save for Android
val androidConfig = DashboardConfigurationDTO(
widgets = listOf(/* mobile-friendly widgets */),
platform = "ANDROID"
)
api.saveConfiguration(androidConfig)2. Widget Platform Filter
Solution: Ensure widgets specify which platforms they support:
DashboardWidgetDTO(
id = "finance",
type = "FINANCE_OVERVIEW",
title = "Finances",
platforms = listOf("DESKTOP", "WEB"), // Not on mobile
// ...
)Frontend should filter:
val platformWidgets = config.widgets.filter { widget ->
widget.platforms.contains(currentPlatform) || widget.platforms.isEmpty()
}Dashboard Endpoint Returns 401
Symptoms
HTTP 401 Unauthorized
"User not authenticated"Common Causes
1. Missing Authorization Header
Solution: Include Bearer token:
curl -X GET .../dashboard?dateTime=... \
-H "Authorization: Bearer YOUR_JWT_TOKEN"2. Invalid or Expired Token
Diagnosis: Decode JWT token:
# Using jwt.io or jwt-cli
jwt decode YOUR_TOKENCheck:
exp(expiration) is in futureuserIdfield exists
Solution: Re-authenticate to get fresh token:
curl -X POST .../auth/login \
-d '{"username":"user","password":"pass"}'3. Token Missing userId
Diagnosis: Check JWT payload:
{
"username": "john",
"exp": 1234567890
// Missing: "userId" or "id"
}Solution: Ensure token generation includes userId:
val token = JWT.create()
.withClaim("userId", user.id)
.withExpiresAt(Date(System.currentTimeMillis() + validityMs))
.sign(algorithm)Dashboard Endpoint Returns 400
Symptoms
HTTP 400 Bad Request
"Date parameter is required"or
"Invalid date time format"Common Causes
1. Missing dateTime Query Parameter
Solution: Include dateTime in URL:
curl -X GET "http://localhost:8080/api/v1/dashboard?dateTime=2024-01-16T14:30:00" \
-H "Authorization: Bearer TOKEN"2. Invalid DateTime Format
Invalid formats:
2024-01-16(missing time)2024-01-16 14:30:00(space instead of T)01/16/2024 14:30(wrong format)
Valid format:
YYYY-MM-DDTHH:mm:ss
2024-01-16T14:30:00Solution: Use ISO 8601 format:
// JavaScript
const dateTime = new Date().toISOString().slice(0, 19);
// "2024-01-16T14:30:00"
// Kotlin
val dateTime = Clock.System.now()
.toLocalDateTime(TimeZone.currentSystemDefault())
.toString()Slow Dashboard Performance
Symptoms
- Dashboard endpoint takes > 2 seconds
- Timeout errors
Common Causes & Solutions
1. No Caching
See Optimizing Dashboard Query Performance in Common Tasks guide.
2. Too Many Database Queries
Diagnosis: Enable SQL logging in Exposed:
addLogger(StdOutSqlLogger)Count queries in logs.
Solution:
- Use eager loading with joins
- Batch queries where possible
- Add database indexes
3. Large Dataset
Diagnosis: Check row counts:
SELECT COUNT(*) FROM tasks WHERE user_id = ?;
SELECT COUNT(*) FROM habits WHERE user_id = ?;
SELECT COUNT(*) FROM task_tracks WHERE done_date_time >= ...;Solution:
- Add pagination (reduce limit from 100 to 20)
- Add date range filters
- Archive old data
4. N+1 Query Problem
Diagnosis: Logs show repeated similar queries:
SELECT * FROM tasks WHERE id = 1
SELECT * FROM tasks WHERE id = 2
SELECT * FROM tasks WHERE id = 3
...Solution: Use batch loading:
// Instead of
tasks.forEach { task ->
val details = taskRepository.getDetails(task.id)
}
// Use
val taskIds = tasks.map { it.id }
val allDetails = taskRepository.getBatchDetails(taskIds)Next Task Not Appearing
Symptoms
nextTaskis null when tasks exist- Completed task still showing as next
Common Causes
1. All Tasks Are Completed
Diagnosis:
SELECT COUNT(*) as total,
SUM(CASE WHEN done = true THEN 1 ELSE 0 END) as completed
FROM tasks
WHERE user_id = ?;Solution: Create new tasks or unmark completed ones.
2. All Tasks Are Future-Dated
Diagnosis:
SELECT title, due_date_time, scheduled_date_time
FROM tasks
WHERE user_id = ? AND done = false
ORDER BY COALESCE(due_date_time, scheduled_date_time);All dates > today?
Solution:
This is expected. getNextTask will return the highest priority future task.
3. Tasks Missing Due/Scheduled DateTime
Diagnosis:
SELECT COUNT(*)
FROM tasks
WHERE user_id = ?
AND done = false
AND due_date_time IS NULL
AND scheduled_date_time IS NULL;Solution:
Algorithm requires either dueDateTime or scheduledDateTime. Add dates to tasks:
UPDATE tasks
SET scheduled_date_time = NOW()
WHERE id = ? AND due_date_time IS NULL AND scheduled_date_time IS NULL;4. Priority Sorting Issue
Diagnosis: Check task priorities:
SELECT title, priority, due_date_time
FROM tasks
WHERE user_id = ? AND done = false
ORDER BY priority DESC;Solution: Ensure tasks have meaningful priorities (1-5).
General Debugging Checklist
When encountering any dashboard issue:
- Check server logs - Look for debug output and exceptions
- Verify authentication - Ensure valid JWT token with userId
- Validate input - DateTime format, platform string, etc.
- Check database - Query relevant tables directly
- Test in isolation - Create unit test reproducing the issue
- Enable verbose logging - Add println statements if needed
- Check timezone consistency - Server and client should align
- Verify data integrity - Non-null required fields, valid enum values
- Test with cURL - Eliminate frontend issues
- Review recent changes - Did recent code changes break something?