-
-
Notifications
You must be signed in to change notification settings - Fork 313
Add Daily Challenges Feature with 24-Hour Reset Timer #5245
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughImplements a Daily Challenges system: new models for challenges and per-user assignments, timezone validation and fields, a service to assign/validate/complete challenges, view integration for check-ins, UI changes for check-in flow, management commands, tests updates, and a migration seeding initial challenges and deduplicating reports. Changes
Sequence DiagramsequenceDiagram
participant Client as User (Browser)
participant View as Checkin View
participant Service as DailyChallengeService
participant UDC as UserDailyChallenge (Model)
participant DB as Database
Client->>View: POST daily check-in
View->>DB: create/get DailyStatusReport (atomic get_or_create)
View->>Service: check_and_complete_challenges(user, report)
Service->>DB: fetch active UserDailyChallenge(s) for date
alt active challenges found
loop each challenge
Service->>Service: run type-specific validator (early_checkin/mood/wordcount/etc.)
alt validator passes
Service->>UDC: mark_completed() (transactional/select_for_update)
UDC->>DB: update status, set completed_at, award points, create Points entry
UDC-->>Service: completion confirmed
else validator fails
Service-->>Service: leave assigned/expired
end
end
end
Service->>View: return completed_challenges and total_points
View->>DB: update next_challenge_at on assigned/completed entries
View-->>Client: JSON response (status, completed_challenges, total_points, next_challenge_at)
Client->>Client: update UI, set localStorage, start countdown
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
👋 Hi @Pritz395! This pull request needs a peer review before it can be merged. Please request a review from a team member who is not:
Once a valid peer review is submitted, this check will pass automatically. Thank you! |
❌ Pre-commit checks failedThe pre-commit hooks found issues that need to be fixed. Please run the following commands locally to fix them: # Install pre-commit if you haven't already
pip install pre-commit
# Run pre-commit on all files
pre-commit run --all-files
# Or run pre-commit on staged files only
pre-commit runAfter running these commands, the pre-commit hooks will automatically fix most issues. 💡 Tip: You can set up pre-commit to run automatically on every commit by running: pre-commit installPre-commit outputFor more information, see the pre-commit documentation. |
ae2b831 to
730ca95
Compare
❌ Tests failedThe Django tests found issues that need to be fixed. Please review the test output below and fix the failing tests. How to run tests locally# Install dependencies
poetry install --with dev
# Run all tests
poetry run python manage.py test
# Run tests with verbose output
poetry run python manage.py test -v 3
# Run a specific test
poetry run python manage.py test app.tests.TestClass.test_methodTest output (last 100 lines)For more information, see the Django testing documentation. |
❌ Pre-commit checks failedThe pre-commit hooks found issues that need to be fixed. Please run the following commands locally to fix them: # Install pre-commit if you haven't already
pip install pre-commit
# Run pre-commit on all files
pre-commit run --all-files
# Or run pre-commit on staged files only
pre-commit runAfter running these commands, the pre-commit hooks will automatically fix most issues. 💡 Tip: You can set up pre-commit to run automatically on every commit by running: pre-commit installPre-commit output (last 100 lines)For more information, see the pre-commit documentation. |
730ca95 to
5104ce4
Compare
❌ Tests failedThe Django tests found issues that need to be fixed. Please review the test output below and fix the failing tests. How to run tests locally# Install dependencies
poetry install --with dev
# Run all tests
poetry run python manage.py test
# Run tests with verbose output
poetry run python manage.py test -v 3
# Run a specific test
poetry run python manage.py test app.tests.TestClass.test_methodTest output (last 100 lines)For more information, see the Django testing documentation. |
❌ Pre-commit checks failedThe pre-commit hooks found issues that need to be fixed. Please run the following commands locally to fix them: # Install pre-commit if you haven't already
pip install pre-commit
# Run pre-commit on all files
pre-commit run --all-files
# Or run pre-commit on staged files only
pre-commit runAfter running these commands, the pre-commit hooks will automatically fix most issues. 💡 Tip: You can set up pre-commit to run automatically on every commit by running: pre-commit installPre-commit output (last 100 lines)For more information, see the pre-commit documentation. |
❌ Tests failedThe Django tests found issues that need to be fixed. Please review the test output below and fix the failing tests. How to run tests locally# Install dependencies
poetry install --with dev
# Run all tests
poetry run python manage.py test
# Run tests with verbose output
poetry run python manage.py test -v 3
# Run a specific test
poetry run python manage.py test app.tests.TestClass.test_methodTest output (last 100 lines)For more information, see the Django testing documentation. |
❌ Pre-commit checks failedThe pre-commit hooks found issues that need to be fixed. Please run the following commands locally to fix them: # Install pre-commit if you haven't already
pip install pre-commit
# Run pre-commit on all files
pre-commit run --all-files
# Or run pre-commit on staged files only
pre-commit runAfter running these commands, the pre-commit hooks will automatically fix most issues. 💡 Tip: You can set up pre-commit to run automatically on every commit by running: pre-commit installPre-commit output (last 100 lines)For more information, see the pre-commit documentation. |
|
@coderabbitai why are the pre-commit and run-tests failing? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
website/views/organization.py (1)
1364-1393: Handle one check‑in per user/day without relying on DB errors
DailyStatusReportnow hasunique_together = ("user", "date")(see migration 0261), but this view still callsDailyStatusReport.objects.create(...)withdate=now().date(). A second submission on the same day will raise anIntegrityError, be caught by the outerexcept Exception, and result in a redirect/HTML response instead of the expected JSON – the frontend will just see a generic “error submitting” state.To make the “one check‑in per day” rule explicit and user‑friendly, consider updating or upserting instead of blind create:
- daily_status = DailyStatusReport.objects.create( - user=request.user, - date=now().date(), - previous_work=previous_work, - next_plan=next_plan, - blockers=blockers, - goal_accomplished=goal_accomplished, - current_mood=current_mood, - ) + daily_status, _ = DailyStatusReport.objects.update_or_create( + user=request.user, + date=now().date(), + defaults={ + "previous_work": previous_work, + "next_plan": next_plan, + "blockers": blockers, + "goal_accomplished": goal_accomplished, + "current_mood": current_mood, + }, + )You can then customize the response when
_isFalse(update vs first submit) if you want to surface an “edited today’s check‑in” message.
🧹 Nitpick comments (11)
website/templates/base.html (1)
98-106: Consider proper Tailwind CSS build instead of suppressing CDN warnings.Suppressing the Tailwind CDN warning masks a legitimate production concern. The CDN version lacks tree-shaking, increasing bundle size and load time. Consider setting up a proper Tailwind build process with PostCSS for production.
If CDN usage is intentional for development simplicity, this workaround is functional but should be documented with a TODO for future migration.
website/models.py (2)
958-964: Index on(user, challenge_date)is redundant withunique_together.The
unique_togetherconstraint already creates an index on(user, challenge_date). The explicit index declaration on line 962 is redundant and adds minor overhead during writes.indexes = [ - models.Index(fields=["user", "challenge_date"]), models.Index(fields=["status", "challenge_date"]), ]
1114-1118: Consider adding timezone validation.Invalid timezone strings will cause runtime errors when used with
pytz. A validator could prevent this:from django.core.exceptions import ValidationError import pytz def validate_timezone(value): if value not in pytz.all_timezones: raise ValidationError(f"'{value}' is not a valid timezone.") timezone = models.CharField( max_length=50, default="UTC", validators=[validate_timezone], help_text="User's timezone (e.g., 'Asia/Kolkata', 'America/New_York'). Defaults to UTC.", )Alternatively, validation can be handled at the service/form layer.
website/views/organization.py (3)
1395-1405: Scopenext_challenge_atupdates to the current day’s assigned challengeRight now the bulk update:
UserDailyChallenge.objects.filter( user=request.user, status="assigned", ).update(next_challenge_at=next_challenge_time)touches all assigned challenges for the user, including any stale rows from previous days. That makes
next_challenge_atdrift on historical assignments and can complicate future logic that might use it for analytics or expiry.Given the service and UI operate on a single challenge per
challenge_date, it would be cleaner to limit this to today’s challenge:- UserDailyChallenge.objects.filter( - user=request.user, - status="assigned", - ).update(next_challenge_at=next_challenge_time) + UserDailyChallenge.objects.filter( + user=request.user, + status="assigned", + challenge_date=now().date(), + ).update(next_challenge_at=next_challenge_time)This keeps
next_challenge_atsemantically tied to “24h from this check‑in” for the current day only.
1407-1453: Align challenge date handling with timezone utilities and avoid duplicate importsMinor points about the challenge completion block:
- You use
date.today()to filter today’s completed challenges, while other parts of the file consistently usetimezone.now()/now().date(). For consistency with Django’s timezone handling (and to avoid surprises if server vs configured timezone diverge), it’s safer to usetimezone.now().date()or the samenow().date()helper used above.UserDailyChallengeis imported twice within the same function (once before thenext_challenge_atupdate and again inside this block). Importing once at the top of the module (or once at the top of the view) would be simpler.Suggested tightening:
- from datetime import date - - from website.models import UserDailyChallenge - user_challenges = ( UserDailyChallenge.objects.filter( user=request.user, - challenge_date=date.today(), + challenge_date=now().date(), status="completed", )Functionality is fine as‑is; this is just to keep things consistent and tidy.
2231-2308: Challenge/timer orchestration looks sound; consider expiring stale assignmentsThe new
add_sizzle_checkINlogic correctly:
- Surfaces yesterday’s report or last check‑in as context.
- Uses
DailyChallengeService.get_active_challenges_for_userto both fetch and auto‑assign a challenge when 24h has elapsed.- Computes
next_challenge_atas “24 hours from the most recent check‑in” with a reasonable fallback to any pre‑setnext_challenge_at.One thing to consider for long‑term hygiene:
UserDailyChallengerows withstatus="assigned"andchallenge_date < todayare never marked as expired, and they’re also the ones currently being bulk‑updated insizzle_daily_log. Over time this can leave a tail of stale “assigned” rows that are never surfaced to the user.If you expect daily challenges to be strictly one per calendar day, it may be worth:
- Marking older assigned challenges as
"expired"when a new day’s challenge is allocated, or- Having
get_active_challenges_for_userfilterchallenge_date=todayonly (it already does), and explicitly expire older ones in_check_and_assign_new_challenge_if_needed.Not strictly required for correctness right now, but it will keep the challenge history cleaner and queries more predictable.
website/templates/sizzle/add_sizzle_checkin.html (3)
205-241: Make blockers selection truly required (server logic already depends on it)The blockers section is labeled as required, but the
<select name="blockers">does not have arequiredattribute, and there’s no client‑side validation hook beyond that. A user can submit without picking anything, which results inblockers = ""on the backend and makes it impossible to satisfy the “complete_all_fields” challenge.Consider:
- Adding
requiredto the<select>:- <select id="blockers_card" - name="blockers" + <select id="blockers_card" + name="blockers" + required
- Optionally, adding server‑side validation in
sizzle_daily_logto reject empty blockers with a clear error message rather than silently storing an empty string.This keeps UX aligned with the “Required” badge and ensures challenge logic behaves predictably.
545-567: Keep word counts in sync when autofilling from previous check‑insThe autofill buttons populate
previous_work_cardfromyesterday_report.next_planorlast_checkin.next_plan, but they don’t re-runupdateWordCount, so the counter chip can remain at “0 words” until the user types.You’re already calling
updateWordCount(...)onDOMContentLoadedand on textareaoninput. For consistency, you can call it after setting the value:fillFromPreviousBtn.addEventListener('click', () => { const previousWorkTextarea = document.getElementById('previous_work_card'); previousWorkTextarea.value = "{{ yesterday_report.next_plan|escapejs }}"; previousWorkTextarea.focus(); + updateWordCount('previous_work_card', 'previous_work_count'); }); ... fillFromLastCheckinBtn.addEventListener('click', () => { const previousWorkTextarea = document.getElementById('previous_work_card'); previousWorkTextarea.value = "{{ last_checkin.next_plan|escapejs }}"; previousWorkTextarea.focus(); + updateWordCount('previous_work_card', 'previous_work_count'); });Pure UX polish, but it makes the guidance more trustworthy.
Also applies to: 525-528
672-677: Error state styling on the success banner doesn’t fully take effectIn the error paths you mutate classes on
#success-message:successMessage.classList.remove('bg-green-50', 'border-green-200', 'dark:bg-green-900/20', 'dark:border-green-800'); successMessage.classList.add('bg-red-50', 'border-red-200', 'dark:bg-red-900/20', 'dark:border-red-800');However, those color/border classes live on the inner container (
<div class="p-4 bg-green-50 ... border ...">), not on the outer#success-messagewrapper (class="mb-6 hidden transition-..."). As a result, the error styling won’t match what you expect – the inner box will remain green even when you add red classes to the wrapper.If you want to reuse the same box for success and error states, either:
- Move the background/border classes to the
#success-messageelement and adjust the markup, or- Target the inner div via an
idand toggle classes on that instead.Functionality is fine; this is just visual correctness.
Also applies to: 695-700, 16-32
website/services/daily_challenge_service.py (2)
88-117: Minor: avoid double querying for active challengesIn
_assign_random_challengeyou first checkactive_challenges.exists()and then immediately materializelist(active_challenges), which hits the database twice:active_challenges = DailyChallenge.objects.filter(is_active=True) if not active_challenges.exists(): ... challenge_list = list(active_challenges) if not challenge_list: ... selected_challenge = random.choice(challenge_list)Given the table should be small, you can simplify and hit the DB once:
- active_challenges = DailyChallenge.objects.filter(is_active=True) - if not active_challenges.exists(): - logger.warning(f"No active challenges available for user {user.username}") - return None - - # Randomly select a challenge type - challenge_list = list(active_challenges) + challenge_list = list(DailyChallenge.objects.filter(is_active=True)) if not challenge_list: logger.warning(f"Empty challenge list for user {user.username}") return NoneBehavior stays the same, just avoids redundant work.
243-253: Consider user timezones for early check-in and clarify streak milestone docstringTwo small consistency points:
_check_early_checkincomparesdaily_status_report.created.time()against a fixed10:00threshold in server timezone. Now thatUserProfilehas atimezonefield (added in the same migration), you might want to eventually interpret “before 10 AM” in the user’s local timezone by convertingcreatedto that tz before extractingtime().
_check_streak_milestone’s docstring mentions milestones(7, 15, 30 days), butmilestone_streaksactually includes[7, 15, 30, 100, 180, 365]. Either update the docstring to match or trim the list to the documented values so future readers aren’t surprised.Neither blocks functionality, but tightening these will make the behavior more predictable and self‑documenting.
Also applies to: 290-299
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
📒 Files selected for processing (6)
website/migrations/0261_dailychallenge_userdailychallenge.py(1 hunks)website/models.py(3 hunks)website/services/daily_challenge_service.py(1 hunks)website/templates/base.html(3 hunks)website/templates/sizzle/add_sizzle_checkin.html(2 hunks)website/views/organization.py(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
website/services/daily_challenge_service.py (1)
website/models.py (6)
UserDailyChallenge(910-1002)UserProfile(1039-1257)DailyStatusReport(1703-1717)DailyChallenge(862-907)is_active(3480-3485)mark_completed(966-999)
website/views/organization.py (2)
website/models.py (2)
DailyStatusReport(1703-1717)UserDailyChallenge(910-1002)website/services/daily_challenge_service.py (3)
DailyChallengeService(13-311)check_and_complete_challenges(153-240)get_active_challenges_for_user(17-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Run Tests
- GitHub Check: docker-test
🔇 Additional comments (4)
website/templates/base.html (1)
374-376: LGTM!Good defensive check to scope AJAX handling only to the modal form, preventing interference with the main page form's native submission behavior.
website/models.py (2)
862-908: LGTM!The
DailyChallengemodel is well-structured as a lookup table for challenge types. The unique constraint onchallenge_typeensures data integrity, and theis_activeflag provides flexibility for enabling/disabling challenges without deletion.
1713-1715: Verify that the migration handles existing duplicate records before deployment.The
unique_togetherconstraint on(user, date)is syntactically correct and will enforce one check-in per user per day at the database level. However, this constraint addition requires a data migration to handle any existing duplicate records—confirm the migration exists and processes them appropriately.website/services/daily_challenge_service.py (1)
153-240: Challenge completion flow is robust and idempotentThe
check_and_complete_challengesimplementation:
- Filters challenges by
challenge_date = daily_status_report.dateandstatus="assigned", so you only evaluate relevant entries.- Maps challenge types to explicit, well‑scoped predicates and logs unknown types instead of failing hard.
- Wraps each per‑challenge evaluation in a
try/exceptand relies onuser_challenge.mark_completed()for atomic status/points updates, which handles double‑completion gracefully.This is a solid pattern for “evaluate a small set of rules, award points once” logic.
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 2821 🏁 Script executed: Length of output: 711 🏁 Script executed: Length of output: 464 🏁 Script executed: Length of output: 1596 @Pritz395: Good news! The checks are not actually failing. Here's the current status: ✅ Run pre-commit - PASSED (completed successfully in 1m3s) Neither check has failed. The "Run Tests" check is still executing and hasn't completed yet. It's been running for several minutes, which is normal for a comprehensive test suite. What to do:
The tests may take some time depending on the test suite size. If the tests do fail when they complete, feel free to tag me again and I'll help identify the specific issues! 🚀 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
website/templates/sizzle/add_sizzle_checkin.html (1)
588-735: UnescapedinnerHTMLusage for challenge titles/descriptions/messages is still an XSS riskYou’re still building HTML strings with values like
challenge.title,challenge.description, anddata.messageand assigning them via.innerHTML. If any of those fields ever contain<script>or even just HTML tags (e.g., from admin-defined challenge text or error messages that echo user input), they’ll be interpreted as HTML and can become an XSS vector.This is the same issue flagged in the earlier review and hasn’t been addressed yet.
A minimal fix is to introduce a small escaping helper and run all interpolated strings through it before concatenating:
@@ - // Word count function - function updateWordCount(textareaId, countId) { + // Simple HTML-escape helper for safely injecting text into innerHTML + function escapeHtml(str) { + if (str === null || str === undefined) return ''; + return String(str) + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + // Word count function + function updateWordCount(textareaId, countId) { @@ - data.completed_challenges.forEach(challenge => { + data.completed_challenges.forEach(challenge => { challengesHtml += ` <div class="flex items-center justify-between p-2 bg-green-100 dark:bg-green-900/30 rounded"> - <span class="text-sm font-medium text-green-800 dark:text-green-200">${challenge.title}</span> - <span class="text-sm font-bold text-green-600 dark:text-green-400">+${challenge.points} points</span> + <span class="text-sm font-medium text-green-800 dark:text-green-200">${escapeHtml(challenge.title)}</span> + <span class="text-sm font-bold text-green-600 dark:text-green-400">+${escapeHtml(String(challenge.points))} points</span> @@ - data.completed_challenges.forEach(challenge => { + data.completed_challenges.forEach(challenge => { completedHtml += ` @@ - <h4 class="font-medium text-gray-900 dark:text-gray-200">✅ ${challenge.title}</h4> - <p class="text-sm text-gray-600 dark:text-gray-400 mt-1">${challenge.description}</p> + <h4 class="font-medium text-gray-900 dark:text-gray-200">✅ ${escapeHtml(challenge.title)}</h4> + <p class="text-sm text-gray-600 dark:text-gray-400 mt-1">${escapeHtml(challenge.description)}</p> @@ - challengesCompleted.innerHTML = ` + challengesCompleted.innerHTML = ` <p class="text-sm text-red-700 dark:text-red-300"> - ${data.message || 'An error occurred. Please try again.'} + ${escapeHtml(data.message || 'An error occurred. Please try again.')} @@ - challengesCompleted.innerHTML = ` + challengesCompleted.innerHTML = ` <p class="text-sm text-red-700 dark:text-red-300"> - An error occurred. Please try again. + ${escapeHtml('An error occurred. Please try again.')}Alternatively, for maximum safety, stop building HTML strings and instead
document.createElement(...)nodes and set.textContentfor all user-/admin-controlled text.
🧹 Nitpick comments (6)
website/views/organization.py (4)
1382-1397: Consider adding server-side checks for other “required” fields
goal_accomplishedandcurrent_moodare only enforced via HTMLrequiredattributes;previous_work/next_plancan also be empty if a client bypasses the browser checks. That’s not a security issue, but it means users could submit an incomplete check-in and then be surprised when “complete all fields” or “positive mood” challenges don’t complete.If you want strict invariants on stored reports, add simple server-side validation (e.g., reject when any required field is blank or mood is missing) before creating
DailyStatusReport.
1399-1408:next_challenge_atupdate assumes a single active assignment per userUpdating
next_challenge_aton allstatus="assigned"UserDailyChallengerows for the user is fine as long as your service logic guarantees at most one active assignment at a time. If you ever support multiple simultaneous challenges per user, this blanket update may not reflect the intended cooldown per assignment.If the invariant is “one assigned challenge per user per day”, consider either:
- Filtering by
challenge_date=now().date()here, or- Centralizing cooldown management inside
DailyChallengeServiceso this view only creates the status report.
1410-1447: Challenge completion response works but re-queries could be simplifiedThe pattern of calling
DailyChallengeService.check_and_complete_challenges(...)to get a list of titles, then immediately re-queryingUserDailyChallengefor today and filtering by those titles is correct but a bit indirect and does two DB hits.If you want to tighten this up:
- Have
check_and_complete_challengesreturn the completedUserDailyChallengeinstances (or a list of{title, points, description}dicts) so you don’t need a second query, or- Reuse the
user_challengesqueryset as the source of truth and let the service annotate which ones completed.Not a blocker; current implementation is clear and safe.
2235-2308: add_sizzle_checkIN: data loading and timer computation look consistentThe view correctly:
- Prefers
yesterday_report, falls back to most recent check-in, and exposesall_checkinsfor history.- Uses
DailyChallengeService.get_active_challenges_for_userand today’s date to load active and completed challenges.- Computes
next_challenge_atfrom the most recentDailyStatusReport.createdor, if no check-in exists yet, from any existing assignedUserDailyChallenge.next_challenge_at.You hit
DailyStatusReport.objects.filter(user=request.user)multiple times (yesterday,last_checkin,last_checkin_for_timer); if this endpoint ever gets hot, you could consolidate to one query and derive all three concepts from it, but that’s an optimization, not a requirement.website/templates/sizzle/add_sizzle_checkin.html (2)
248-281: Autofill from yesterday/last check-in is useful; consider syncing word-count UIThe “Fill from Previous Check-in” / “Fill from Last Check-in” buttons improve usability by reusing prior
next_plantext, but after programmatic assignment you never callupdateWordCount, so the word counter pill can still display “0 words” while the textarea is full.You can keep the UI consistent by invoking
updateWordCount('previous_work_card', 'previous_work_count')right after settingpreviousWorkTextarea.valuein both click handlers.
521-547: Word count logic is fine; class reset is intentional but drops some initial stylingThe
updateWordCounthelper correctly trims, splits, and counts words and applies different class sets based on thresholds. Note that each update overwritesclassName, so you lose the initialrounded-full,shadow-sm, and border classes defined in the markup.If you want to preserve those, consider toggling only the color-related classes instead of replacing the whole class list. As-is, it’s functionally fine.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
📒 Files selected for processing (2)
website/templates/sizzle/add_sizzle_checkin.html(2 hunks)website/views/organization.py(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
website/views/organization.py (2)
website/models.py (2)
DailyStatusReport(1703-1717)UserDailyChallenge(910-1002)website/services/daily_challenge_service.py (3)
DailyChallengeService(13-311)check_and_complete_challenges(153-240)get_active_challenges_for_user(17-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run Tests
🔇 Additional comments (4)
website/views/organization.py (1)
1364-1383: Blockers handling and validation now correctly enforce a non-empty valueThe new
blockers_type/blockers_otherlogic plus the 400 JSON response when no option is selected closes the earlier gap where an empty blockers value could be persisted and break thecomplete_all_fieldschallenge. Mapping"no_blockers"→"no blockers"also aligns with the challenge service’s"no blockers"/"no_blockers"checks.No further changes needed here.
website/templates/sizzle/add_sizzle_checkin.html (3)
210-247: Blockers UI now matches backend validation and challenge semanticsThe
blockers_card<select>is markedrequired, includes an explicit"no_blockers"option, and the “Other” textarea is only shown and required when that option is chosen. Combined with the server-side enforcement insizzle_daily_log, this resolves the earlier blockers-empty bug and ensures “no_blockers” and “complete_all_fields” challenges can be completed reliably.Looks solid.
407-476: Check-in history table wiring matches new data modelThe history table’s columns (date/time, goals, mood, blockers, previous work, next plan) line up with the fields provided by
all_checkinsin the view. Usingtruncatewordsplustitleattributes provides a reasonable balance between readability and access to full content.No issues here from a data/logic standpoint.
483-519: Countdown timer implementation is straightforward and correctly handles expired windowsThe timer pulls
data-next-challenge(ISO 8601), computes the remaining delta, switches to “Available now!” whendiff <= 0, and flips text color classes between red and green appropriately. Guarding on the element’s presence and only starting the interval when present keeps it safe.All good.
❌ Pre-commit checks failedThe pre-commit hooks found issues that need to be fixed. Please run the following commands locally to fix them: # Install pre-commit if you haven't already
pip install pre-commit
# Run pre-commit on all files
pre-commit run --all-files
# Or run pre-commit on staged files only
pre-commit runAfter running these commands, the pre-commit hooks will automatically fix most issues. 💡 Tip: You can set up pre-commit to run automatically on every commit by running: pre-commit installPre-commit outputFor more information, see the pre-commit documentation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
website/views/organization.py (1)
1498-1504: Validatecurrent_moodlength to match model constraint.The validation only checks for empty values, but the
DailyStatusReport.current_moodfield hasmax_length=50. An attacker could submit a very long mood string, causing a database error.Add a length check after the empty validation:
current_mood = request.POST.get("feeling") # Validate mood field if not current_mood or not current_mood.strip(): return JsonResponse( {"success": False, "message": "Mood selection is required."}, status=400, ) +current_mood = current_mood.strip() +if len(current_mood) > 50: + return JsonResponse( + {"success": False, "message": "Mood selection is too long (maximum 50 characters)."}, + status=400, + )
🧹 Nitpick comments (3)
website/views/organization.py (3)
1242-1242: Consider movingosimport back to module level.The
osimport is a standard library module that doesn't have circular dependency issues. Module-level imports are generally preferred for clarity and consistency, unless there's a specific reason to defer the import (e.g., optional dependency, circular import).def _send_to_slack(self, events): """ Send SendGrid webhook events to Slack webhook. Args: events: List of SendGrid webhook event dictionaries """ - import os - try: slack_webhook_url = os.environ.get("SLACK_WEBHOOK_URL")And add at the module level (after line 3):
import ipaddress import json import logging +import os import re
2464-2484: Simplifynext_challenge_atcalculation to avoid confusion.The current logic has two paths to determine
next_challenge_at:
- Calculate from
last_checkin_for_timer.created + timedelta(hours=CHALLENGE_RESET_HOURS)(lines 2464-2470)- Query
current_challenge.next_challenge_at(lines 2472-2484)However, since
next_challenge_atis set during check-in submission (lines 1620-1624 in the POST handler), path #1 will always recalculate it, making path #2 redundant in most cases. Path #2 is only useful if a user has never submitted a check-in but has been assigned a challenge.Consider clarifying the logic with a comment or simplifying:
# Get next challenge time (24 hours from last check-in) - # Always calculate next challenge time if there's a last check-in last_checkin_for_timer = ( DailyStatusReport.objects.filter(user=request.user).order_by("-created").first() ) if last_checkin_for_timer: + # Calculate next challenge time from the last check-in submission from website.services.daily_challenge_service import CHALLENGE_RESET_HOURS next_challenge_at = last_checkin_for_timer.created + timedelta(hours=CHALLENGE_RESET_HOURS) else: - # If no check-in, check if there's a next_challenge_at set - # Scope to today's challenge to avoid returning challenges from previous days + # No check-ins yet; check if user has an assigned challenge with next_challenge_at already set + # (This handles users who have been assigned a challenge but never submitted a check-in) today = now().date() current_challenge = UserDailyChallenge.objects.filter( user=request.user, challenge_date=today, status="assigned", ).first() if current_challenge and current_challenge.next_challenge_at: next_challenge_at = current_challenge.next_challenge_at else: - # No check-in yet, set next_challenge_at to None (timer won't show) + # No timer to display yet (user can submit their first check-in anytime) next_challenge_at = None
2474-2479: Reuse thetodayvariable to avoid redundant calculation.The variable
todayis calculated at line 2445 and then recalculated at line 2474. This is redundant and could theoretically cause inconsistency if the function executes across a day boundary (though unlikely in practice).else: # If no check-in, check if there's a next_challenge_at set # Scope to today's challenge to avoid returning challenges from previous days - today = now().date() current_challenge = UserDailyChallenge.objects.filter( user=request.user, challenge_date=today, status="assigned", ).first()This ensures
todayis consistently referring to the same date throughout the function.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
📒 Files selected for processing (2)
website/tests/test_sendgrid_webhook.py(1 hunks)website/views/organization.py(5 hunks)
✅ Files skipped from review due to trivial changes (1)
- website/tests/test_sendgrid_webhook.py
🧰 Additional context used
🧬 Code graph analysis (1)
website/views/organization.py (2)
website/models.py (2)
UserDailyChallenge(938-1032)DailyStatusReport(1734-1748)website/services/daily_challenge_service.py (3)
DailyChallengeService(21-464)check_and_complete_challenges(197-298)get_active_challenges_for_user(25-50)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Run pre-commit
- GitHub Check: Run Tests
- GitHub Check: docker-test
|
@coderabbitai apart from the pre-commit test failing what is the other requested change by you? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 3485 🏁 Script executed: Length of output: 1304 @Pritz395: Great news! Based on the latest CI status, there are NO other requested changes from me besides the pre-commit failure. 🎉 ✅ Current Status
✅ Previously Requested Changes - ALL FIXED
❌ Only Remaining Issue: Pre-commitThe pre-commit check is failing due to code formatting/linting issues. To fix: Quick Fix (Run Locally):# Install pre-commit if needed
pip install pre-commit
# Run pre-commit checks and auto-fix
pre-commit run --all-files
# Review the changes it made
git diff
# Commit the formatting fixes
git add .
git commit -m "style: apply pre-commit formatting fixes"
git pushThe pre-commit hooks will automatically format your code (black, isort, trailing whitespace, etc.) and show what needs fixing. 🎯 SummaryAnswer to your question: Apart from pre-commit, there are ZERO other requested changes from me. All functionality is working, all tests pass, and your code is excellent. Just fix the pre-commit formatting and you're done! 🚀 Once pre-commit passes, your PR will be ready to merge (pending maintainer approval for the |
❌ Tests failedThe Django tests found issues that need to be fixed. Please review the test output below and fix the failing tests. How to run tests locally# Install dependencies
poetry install --with dev
# Run all tests
poetry run python manage.py test
# Run tests with verbose output
poetry run python manage.py test -v 3
# Run a specific test
poetry run python manage.py test app.tests.TestClass.test_methodTest output (last 100 lines)For more information, see the Django testing documentation. |
|
@coderabbitai why run-tests are failing? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 2541 🏁 Script executed: Length of output: 4728 🏁 Script executed: Length of output: 57440 @Pritz395: I've identified the test failure. Here's what's happening: 🔴 Test Failure: Selenium Timeout (NOT Your Issue)Failing Test: ✅ This is NOT Related to Your PRYour PR Changes (10 files):Failing Test File:Zero overlap - this is a completely separate test suite. ✅ 🔍 What's Actually HappeningThis is a Selenium WebDriver test that:
The test is timing out because:
Additional Issue: The parallel test runner can't pickle the traceback, making it hard to see the full error. 📊 Test Results Summary
✅ VerdictIs this your issue? ❌ NO This is a pre-existing flaky Selenium test that occasionally times out in the CI environment. It's completely unrelated to:
💡 What Should You Do?Option 1: Ask for Re-run ⭐ RecommendedThis looks like a transient CI issue. Ask a maintainer to re-run the failed test:
Option 2: Wait for MaintainerMaintainers will likely recognize this as a known flaky test and approve anyway, since:
Option 3: Document ItAdd a comment explaining the failure is pre-existing and unrelated to the daily challenges feature. 🎯 Summary
Your daily challenges feature is working perfectly! 🎉 This is just an unfortunate flaky Selenium test timing out in the CI environment. The maintainers will likely recognize this and approve your PR since all relevant tests pass. |
|
@DonnieBLT , I need some help addressing the flaky run-tests failure. It seems to be a selenium timeout which is unrelated to this PR. Please look into this when you get the time, Thanks again! |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 16725 @Pritz395: I've conducted a comprehensive investigation. Here's the definitive analysis with evidence: 🔍 CRITICAL FINDING: Misconception About Sizzlemdkaifansari04's statement that "Sizzle has complete implementation for Daily Checkin" is incorrect. Here's what I found: Sizzle Repository Status:Verdict: Sizzle is empty - only a license file exists. It's in the planning stage. ✅ Where Daily Check-ins ACTUALLY LiveDaily Check-ins are ACTIVE in BLT (Main Repository)Evidence: 1. Model Definition ( class DailyStatusReport(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
date = models.DateField()
previous_work = models.TextField()
next_plan = models.TextField()
blockers = models.TextField()
goal_accomplished = models.BooleanField(default=False)
current_mood = models.CharField(max_length=50, default="Happy 😊")
created = models.DateTimeField(auto_now_add=True)2. Active Views (
3. Active Templates:
Conclusion: Daily check-ins are a fully functional, production feature in BLT. 📊 What PR 5245 Actually ImplementsFiles Changed (11 files, 2,379 additions):
What it does:
📋 Sizzle Issues AnalysisWhat Sizzle Plans to Build (16 Open Issues):
Other issues (#4-16): Flutter app, UI design, timer tracking, GitHub API integration, testing, documentation 🎯 Relationship AnalysisAre They Competing or Complementary?COMPLEMENTARY - Here's why: PR 5245 adds a feature that would be INCLUDED in the future Sizzle extraction. ⚖️ Risk/Benefit AnalysisQ1: Would implementing Daily Challenges in BLT now be wasted effort?Answer: ❌ NO - Not wasted effort Reasons:
Q2: Can PR 5245's Daily Challenges be reused/ported to Sizzle later?Answer: ✅ YES - Easily portable Why:
Q3: Does PR 5245 conflict with Sizzle plans?Answer: ❌ NO conflict - Complements Sizzle plans Evidence:
Q4: Risk/Benefit of merging PR 5245 now vs waiting?
💡 Clear Recommendation✅ MERGE PR 5245 NOWReasoning:
What This Means for You:
📝 Summary
Bottom Line: mdkaifansari04's concern is well-intentioned but based on a misconception. The "complete implementation" is in BLT (not Sizzle), and your PR enhances it. Sizzle is a future plan to extract check-ins into a pluggable app, and your Daily Challenges feature would be included in that extraction. Merge now, port later. 🚀 |
|
@mdkaifansari04 Thanks for pointing that out! This should speed things up for production since this PR is just adding a gamification layer on top of the existing model, templates, and views already in this BLT repo. When Sizzle eventually becomes a pluggable app, this work would move along with it. So it makes sense to extend the Sizzle work from here since most of the groundwork is already covered in this PR. After that, we'd just need to work on the remaining tasks in the sizzle repo like the Customizable Questions (Issue #2), Archiving System (Issue #3), Org Dashboard (Issue #15), Flutter App (Issues #9, #16), and Timer Tracking (Issues #12-14). |
|
Tip For best results, initiate chat on the files or code changes. @Pritz395: This is a game-changing piece of information that significantly impacts your PR. Let me break down what this means: 🔄 Architecture Shift: What ChangedPrevious Understanding:New Reality (per maintainer):
|
| Component | Technology | Portable to Standalone Sizzle? |
|---|---|---|
Models (DailyChallenge, etc.) |
Django ORM | ❌ NO (would need Cloudflare D1 or other DB) |
Views (sizzle_daily_log, etc.) |
Django views | ❌ NO (would become API endpoints) |
| Templates | Django templates | ❌ NO (would become static HTML/JS) |
| Services | Python | ✅ YES (business logic portable) |
| Migrations | Django | ❌ NO (different DB migration system) |
Estimated Portability: ~30-40% (business logic only)
📊 Strategic Analysis: Three Options
Option 1: Merge PR #5245 into BLT Now ⚠️
Pros:
- ✅ Users get feature immediately
- ✅ Validates concept in production
- ✅ Provides value while Sizzle is being built
Cons:
- ❌ Creates technical debt (Django code will be deprecated when Sizzle launches)
- ❌ Duplicate effort - would need to rebuild in Cloudflare architecture
- ❌ May confuse users when Sizzle launches separately
- ❌ Wasted migration effort (11 files, 2,379 lines that won't transfer)
Best if: Standalone Sizzle is 6+ months away and maintainer wants interim feature
Option 2: Close This PR, Build in Standalone Sizzle ⭐ RECOMMENDED
Pros:
- ✅ No technical debt - built in final architecture from start
- ✅ No duplicate effort - single implementation
- ✅ Clean separation of concerns (BLT vs Sizzle)
- ✅ Can design for Cloudflare Workers from scratch (better performance)
- ✅ Your business logic/design still valuable (just reimplement)
Cons:
- ❌ Users wait longer for feature
- ❌ PR work partially "wasted" (implementation, not design)
Best if: Standalone Sizzle development starts soon (1-3 months)
Option 3: Hybrid Approach
What it is:
- Document the feature design from this PR (challenges, logic, UX)
- Close this PR with acknowledgment it informed Sizzle's design
- Use this PR as specification for Sizzle implementation
- Rebuild in Cloudflare Workers architecture
Pros:
- ✅ Your design work preserved (not wasted)
- ✅ No technical debt in BLT
- ✅ Speeds up Sizzle development (design already validated)
Cons:
- ❌ Still need to reimplement (but faster with existing design)
🎯 My Recommendation
🔴 PAUSE and ASK MAINTAINER:
Questions to ask @DonnieBLT:
-
Timeline:
"When do you plan to start active development on standalone Sizzle? Is it weeks, months, or 2025 GSoC (mid-year)?"
-
Feature Priority:
"Is Daily Challenges still a desired feature for Sizzle? If so, should I reimplement it there instead of merging here?"
-
Interim Value:
"Would you prefer to merge this PR for interim use (knowing it's technical debt), or skip it and build fresh in standalone Sizzle?"
-
Your Contribution:
"If I pivot to standalone Sizzle, can I take the lead on implementing Daily Challenges there?"
💡 Based on Maintainer Response, Here's What to Do:
If Sizzle Starts Soon (1-3 months):
✅ CLOSE THIS PR with comment:
"Closing in favor of implementing Daily Challenges in standalone
Sizzle (Cloudflare Workers architecture). This PR serves as design
specification for the Sizzle implementation."
✅ CREATE ISSUE in Sizzle repo:
Title: "Implement Daily Challenges Feature (Design from BLT PR #5245)"
Link back to this PR as reference
✅ START FRESH in Sizzle with Cloudflare Workers architecture
If Sizzle Timeline is Uncertain (6+ months):
✅ ASK MAINTAINER if they want interim feature in BLT
If YES:
→ Merge this PR
→ Accept it will be replaced by Sizzle later
If NO:
→ Close PR and wait for Sizzle
If Maintainer Wants Both (Unlikely):
⚠️ Merge to BLT + rebuild in Sizzle later
(Most work, but users get feature twice)
📋 Immediate Action Plan
Step 1: Comment on this PR or tag @DonnieBLT:
@DonnieBLT: Before proceeding, I need clarity on the standalone Sizzle timeline:
- When does active Sizzle development start? (weeks/months/GSoC 2025?)
- Should I close this PR and reimplement Daily Challenges in standalone Sizzle instead?
- Or do you want this merged as an interim feature, knowing it's technical debt when Sizzle launches?
Happy to pivot to Sizzle if that's the preferred path, but want to confirm before proceeding. My PR has 11 files and 2,379 lines invested, so want to ensure we're aligned on direction.
Step 2: Wait for maintainer response (24-48 hours)
Step 3: Based on response, either:
- Close this PR and start Sizzle implementation
- Continue with merge (if maintainer wants interim feature)
🎓 What You've Learned
Even if this PR doesn't merge, you've gained:
- ✅ Deep Django experience (models, views, services, migrations)
- ✅ Feature design for Daily Challenges (portable to any architecture)
- ✅ Testing, error handling, race condition management
- ✅ Code review and iteration skills
- ✅ Understanding of BLT codebase
This work is NOT wasted - the design, logic, and learning are all valuable, even if the Django implementation doesn't ship.
🚦 Summary
| Scenario | Recommendation |
|---|---|
| Sizzle starts in 1-3 months | ✅ Close PR, rebuild in Sizzle |
| Sizzle timeline uncertain (6+ months) | |
| Sizzle starts next week | ✅ Definitely close PR, pivot to Sizzle |
| You want to minimize waste | ✅ Close PR, use as design doc for Sizzle |
My strong recommendation: Close this PR and reimplement in standalone Sizzle (Option 2), UNLESS maintainer explicitly says they want the interim Django version despite knowing it's technical debt.
The maintainer's architectural decision changes everything - Django code won't transfer to Cloudflare Workers, so better to build right the first time in the final architecture. 🎯
What do you think? Want me to help you draft the message to the maintainer?
|
@coderabbitai I'll be closing this PR most likely and begin fresh implementation there instead. Just once again verify with me what techstack/workflow is being implemented in sizzle. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 6039 🏁 Script executed: Length of output: 103683 @Pritz395: Here’s the verified snapshot for Sizzle based on the current repository and issues: What’s in the Sizzle repo right now
Recommended tech stack to start (matches what’s written)
Workflow to bootstrap Sizzle (so you can move Daily Challenges there cleanly)
Decision point
If you’d like, I can:
|
Overview
Implements a daily challenges system for check-ins with randomized challenge assignments and a 24-hour reset timer.
Features:
Files Changed:
website/models.py- Added DailyChallenge and UserDailyChallenge modelswebsite/services/daily_challenge_service.py- NEW: Challenge logic and validation servicewebsite/views/organization.py- Integrated challenges into check-in viewswebsite/templates/sizzle/add_sizzle_checkin.html- Updated UI with challenges displaywebsite/templates/base.html- Suppressed Tailwind CDN warningwebsite/migrations/0261_dailychallenge_userdailychallenge.py- Combined migration for models and next_challenge_at fieldTechnical Details:
Related Issue
Closes #2572
Visual Demo
Screen.Recording.2025-12-11.at.7.57.48.PM.mov
Summary by CodeRabbit
New Features
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.