Skip to content

feat: voice event persistence and Discord gateway data lake#259

Merged
danielhe4rt merged 6 commits into
4.xfrom
feat/voice-and-event-log
May 19, 2026
Merged

feat: voice event persistence and Discord gateway data lake#259
danielhe4rt merged 6 commits into
4.xfrom
feat/voice-and-event-log

Conversation

@danielhe4rt
Copy link
Copy Markdown
Contributor

@danielhe4rt danielhe4rt commented May 19, 2026

Summary

  • Voice join/leave persistence: The Discord bot was receiving VOICE_STATE_UPDATE events but only updating Redis cache — voice activity stopped being recorded in the database since March 2026. Added PersistVoiceStateAction that resolves tenant/identity and writes joined/left records to voice_messages, following the same pattern as the ETL importer.
  • Discord event data lake: New discord_event_logs table (jsonb payload) that captures every raw Discord gateway event via the raw listener, registered through Laracord's AFTER_BOOT hook. Acts as a generic data lake for future analytics.

Files changed

File Change
bot-discord/Actions/VoiceChannel/PersistVoiceStateAction.php New — persists voice join/leave
bot-discord/Events/DynamicVoiceEvent.php Modified — calls PersistVoiceStateAction
bot-discord/Events/RawGatewayEvent.php New — raw gateway listener
bot-discord/BotDiscordServiceProvider.php Modified — registers AFTER_BOOT hook
integration-discord/Models/DiscordEventLog.php New — Eloquent model
integration-discord/migrations/create_discord_event_logs_table.php New — migration

Test plan

  • 7 tests for PersistVoiceStateAction (join, leave, channel switch, mute skip, missing tenant/identity)
  • 4 tests for RawGatewayEvent (dispatch persist, author fallback, non-dispatch skip, heartbeat skip)
  • 2 tests for DiscordEventLog model (persist, nullable fields)
  • PHPStan passes with 0 errors
  • Pint formatting clean
  • Verified live: voice events and raw gateway events appearing in database

Description

Adds persistence for Discord voice join/leave events via PersistVoiceStateAction (fixes regression that stopped DB writes) and introduces a Discord gateway data lake by logging raw gateway events to a new discord_event_logs table via a RawGatewayEvent listener registered in Laracord's AFTER_BOOT hook.

References

Dependencies & Requirements

  • No new external composer/runtime dependencies.
  • Requires running the migration: app-modules/integration-discord/database/migrations/2026_05_19_155732_create_discord_event_logs_table.php (uses jsonb; PostgreSQL JSONB required).
  • No environment variable changes.

Contributor Summary

Contributor Lines Added Lines Removed Files Changed
danielhe4rt 432 1 9

Changes Summary

File Path Change Description
app-modules/bot-discord/src/Actions/VoiceChannel/PersistVoiceStateAction.php New action to resolve tenant/identity and persist voice joined/left events to Voice model
app-modules/bot-discord/src/Events/DynamicVoiceEvent.php Now invokes PersistVoiceStateAction and logs exceptions from persistence
app-modules/bot-discord/src/Events/RawGatewayEvent.php New handler that records every raw Discord gateway event into discord_event_logs with fallback extraction for user_id/channel_id
app-modules/bot-discord/src/BotDiscordServiceProvider.php Registers RawGatewayEvent listener via Laracord AFTER_BOOT hook (wire-up)
app-modules/bot-discord/tests/Feature/Actions/VoiceChannel/PersistVoiceStateActionTest.php Tests for join/leave/switch and identity/tenant edge cases (7 tests)
app-modules/bot-discord/tests/Feature/Events/RawGatewayEventTest.php Tests for raw event persistence and fallback extraction paths (4 tests)
app-modules/integration-discord/src/Models/DiscordEventLog.php New Eloquent model for discord_event_logs (payload cast to array)
app-modules/integration-discord/database/migrations/2026_05_19_155732_create_discord_event_logs_table.php Migration creating discord_event_logs (event_type, guild_id, user_id, channel_id, payload jsonb, timestamps using created_at/updated_at)
app-modules/integration-discord/tests/Feature/Models/DiscordEventLogTest.php Model tests covering persistence and nullable ID fields

Review Change Stack

Voice events stopped being ingested since 2026-03-23 because the
real-time handler only updated Redis cache without persisting to
the voice_messages table. Adds PersistVoiceStateAction that resolves
tenant and identity from Discord guild/user IDs and writes joined/left
records, following the same pattern as the ETL importer.
…ta lake

Adds a discord_event_logs table and a raw gateway listener that
persists every Discord event (MESSAGE_CREATE, VOICE_STATE_UPDATE,
GUILD_MEMBER_ADD, etc.) with full payload for future analytics.
Registered via Laracord AFTER_BOOT hook on the Discord client.
Suppress false-positive property.notFound on VoiceStateUpdate magic
properties and fix unnecessary nullsafe access in RawGatewayEvent.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Warning

Rate limit exceeded

@danielhe4rt has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 56 minutes and 42 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 46b80194-bd57-4081-a54c-ef6931006f8e

📥 Commits

Reviewing files that changed from the base of the PR and between 86e08e4 and ea96a3a.

📒 Files selected for processing (1)
  • app-modules/bot-discord/src/Actions/VoiceChannel/PersistVoiceStateAction.php
📝 Walkthrough

Walkthrough

This PR adds Discord gateway event logging and voice channel activity tracking to the bot. The infrastructure captures raw Discord events by registering a gateway event handler that validates dispatch payloads and persists them to a new discord_event_logs database table. In parallel, voice state transitions are detected by comparing voice state updates, resolving tenant and user identities, and creating Voice activity records for join, leave, and channel-switch events. The implementation includes error handling in the event handler and comprehensive test coverage for all state transition scenarios.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: voice event persistence and Discord event logging infrastructure.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
app-modules/bot-discord/tests/Feature/Actions/VoiceChannel/PersistVoiceStateActionTest.php (1)

26-48: ⚡ Quick win

Add a regression test for tenant lookup provider mismatch.

Given tenant resolution uses ExternalIdentity, add a case where the same guild_id exists under a different provider and assert no Voice is persisted (or correct Discord provider mapping is used). This prevents cross-provider mis-attribution regressions.

Also applies to: 112-126

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app-modules/bot-discord/tests/Feature/Actions/VoiceChannel/PersistVoiceStateActionTest.php`
around lines 26 - 48, Add a regression test in PersistVoiceStateActionTest
(inside the existing beforeEach/setup or a new it/test case) that ensures tenant
lookup uses the provider: create an ExternalIdentity with the same guildId value
but with a different provider (e.g., IdentityProvider::Slack) for the tenant,
then trigger the PersistVoiceStateAction (or the test flow that persists Voice)
using the Discord provider and assert that no Voice record is persisted for that
mismatched provider (or that the persisted Voice is only created when an
ExternalIdentity for IdentityProvider::Discord exists). Reference the test setup
variables ($this->tenant, $this->guildId, ExternalIdentity factory,
IdentityProvider constants, and the PersistVoiceStateAction) so the new case
mirrors the existing setup but swaps the provider to verify provider-specific
tenant resolution.
app-modules/bot-discord/src/Events/RawGatewayEvent.php (1)

22-22: Plan retention/pruning for raw payload growth.

Persisting every dispatch payload will grow discord_event_logs quickly; add a retention/pruning strategy (or partitioning) before production scale.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app-modules/bot-discord/src/Events/RawGatewayEvent.php` at line 22, The raw
payload is being persisted as-is ('payload' =>
json_decode(json_encode($payload->d), true) in RawGatewayEvent.php) so you must
add a retention/pruning strategy before production: add a created_at timestamp
to the discord_event_logs records (via migration), make created_at indexed,
introduce a configurable retention_days setting, and implement either a periodic
cleanup task (cron/queue job) that deletes records older than retention_days or
use DB partitioning by date with automated partition drops; update the
RawGatewayEvent insert flow to populate created_at and ensure the cleanup job or
partitioning is documented and enabled in deployment config.
app-modules/bot-discord/tests/Feature/Events/RawGatewayEventTest.php (1)

14-21: ⚡ Quick win

Use a realistic GUILD_MEMBER_ADD payload shape in this fixture.

For this event type, asserting via nested d.user.id prevents false confidence around user_id extraction.

Suggested test update
-        'd' => (object) [
-            'guild_id' => '123456789',
-            'user_id' => '987654321',
-            'channel_id' => null,
-            'roles' => [],
-        ],
+        'd' => (object) [
+            'guild_id' => '123456789',
+            'user' => (object) ['id' => '987654321'],
+            'channel_id' => null,
+            'roles' => [],
+        ],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app-modules/bot-discord/tests/Feature/Events/RawGatewayEventTest.php` around
lines 14 - 21, Update the GUILD_MEMBER_ADD test fixture in RawGatewayEventTest
to use a realistic payload shape: replace the top-level/flat 'user_id' field
under 'd' with a nested 'user' object (e.g., 'd' => (object) ['guild_id' => ...,
'user' => (object) ['id' => '987654321', 'username' => 'botuser', ...], 'roles'
=> [], ...]) and remove the legacy 'user_id' key; then update any assertions in
the test to assert against the nested d.user.id (or equivalent extraction logic)
instead of d.user_id so the test validates the real Discord payload shape for
GUILD_MEMBER_ADD.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app-modules/bot-discord/src/Actions/VoiceChannel/PersistVoiceStateAction.php`:
- Around line 23-26: The ExternalIdentity lookup in PersistVoiceStateAction is
missing a provider filter, so add a condition to the ExternalIdentity::query()
that also matches the Discord provider (e.g., ->where('provider', 'discord') or
use the project constant like ExternalIdentity::PROVIDER_DISCORD) alongside the
existing where('model_type', (new Tenant)->getMorphClass()) and
where('external_account_id', (string) $state->guild_id) to ensure only Discord
identities resolve to the tenant.

In `@app-modules/bot-discord/src/Events/RawGatewayEvent.php`:
- Line 20: The 'user_id' extraction currently only checks $payload->d->user_id
and $payload->d->author->id, so events where the user is nested at
$payload->d->user->id (e.g., GUILD_MEMBER_ADD) are missed; update the 'user_id'
mapping expression (the array key 'user_id' that reads from $payload) to also
check for $payload->d->user->id—use isset() or null-coalescing checks in the
same order (d->user_id, d->user->id, d->author->id) so all Discord event shapes
produce a user_id.

---

Nitpick comments:
In `@app-modules/bot-discord/src/Events/RawGatewayEvent.php`:
- Line 22: The raw payload is being persisted as-is ('payload' =>
json_decode(json_encode($payload->d), true) in RawGatewayEvent.php) so you must
add a retention/pruning strategy before production: add a created_at timestamp
to the discord_event_logs records (via migration), make created_at indexed,
introduce a configurable retention_days setting, and implement either a periodic
cleanup task (cron/queue job) that deletes records older than retention_days or
use DB partitioning by date with automated partition drops; update the
RawGatewayEvent insert flow to populate created_at and ensure the cleanup job or
partitioning is documented and enabled in deployment config.

In
`@app-modules/bot-discord/tests/Feature/Actions/VoiceChannel/PersistVoiceStateActionTest.php`:
- Around line 26-48: Add a regression test in PersistVoiceStateActionTest
(inside the existing beforeEach/setup or a new it/test case) that ensures tenant
lookup uses the provider: create an ExternalIdentity with the same guildId value
but with a different provider (e.g., IdentityProvider::Slack) for the tenant,
then trigger the PersistVoiceStateAction (or the test flow that persists Voice)
using the Discord provider and assert that no Voice record is persisted for that
mismatched provider (or that the persisted Voice is only created when an
ExternalIdentity for IdentityProvider::Discord exists). Reference the test setup
variables ($this->tenant, $this->guildId, ExternalIdentity factory,
IdentityProvider constants, and the PersistVoiceStateAction) so the new case
mirrors the existing setup but swaps the provider to verify provider-specific
tenant resolution.

In `@app-modules/bot-discord/tests/Feature/Events/RawGatewayEventTest.php`:
- Around line 14-21: Update the GUILD_MEMBER_ADD test fixture in
RawGatewayEventTest to use a realistic payload shape: replace the top-level/flat
'user_id' field under 'd' with a nested 'user' object (e.g., 'd' => (object)
['guild_id' => ..., 'user' => (object) ['id' => '987654321', 'username' =>
'botuser', ...], 'roles' => [], ...]) and remove the legacy 'user_id' key; then
update any assertions in the test to assert against the nested d.user.id (or
equivalent extraction logic) instead of d.user_id so the test validates the real
Discord payload shape for GUILD_MEMBER_ADD.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 7182c5b7-4807-4b18-85e7-de848ad1efeb

📥 Commits

Reviewing files that changed from the base of the PR and between 3c7acdf and 6a7b2fb.

📒 Files selected for processing (9)
  • app-modules/bot-discord/src/Actions/VoiceChannel/PersistVoiceStateAction.php
  • app-modules/bot-discord/src/BotDiscordServiceProvider.php
  • app-modules/bot-discord/src/Events/DynamicVoiceEvent.php
  • app-modules/bot-discord/src/Events/RawGatewayEvent.php
  • app-modules/bot-discord/tests/Feature/Actions/VoiceChannel/PersistVoiceStateActionTest.php
  • app-modules/bot-discord/tests/Feature/Events/RawGatewayEventTest.php
  • app-modules/integration-discord/database/migrations/2026_05_19_155732_create_discord_event_logs_table.php
  • app-modules/integration-discord/src/Models/DiscordEventLog.php
  • app-modules/integration-discord/tests/Feature/Models/DiscordEventLogTest.php

Comment thread app-modules/bot-discord/src/Events/RawGatewayEvent.php Outdated
Copy link
Copy Markdown

@davicbtoliveira davicbtoliveira left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM ✍🏻

Clintonrocha98
Clintonrocha98 previously approved these changes May 19, 2026
The created_at timestamp already captures when the event was recorded,
making occurred_at redundant.
Add fallback chain for user_id (user_id → author.id → user.id) and
channel_id (channel_id → id) to cover events like GUILD_MEMBER_UPDATE
and VOICE_CHANNEL_STATUS_UPDATE that use non-standard field names.
…eStateAction

Ensures only Discord identities are matched when resolving the tenant
from guild_id, preventing false matches from other providers.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app-modules/bot-discord/src/Events/RawGatewayEvent.php`:
- Line 22: The current line 'payload' => (array) $payload in RawGatewayEvent.php
persists the entire gateway payload and risks storing PII; modify the code that
builds the payload (e.g., the RawGatewayEvent constructor or its
toArray/serialize method) to apply a sanitize/redaction step before persisting:
implement and call a helper like sanitizePayload/redactSensitiveFields that
strips or hashes sensitive keys (e.g., message.content, author, member, token,
email, username, avatar, IPs, webhook_id) or applies an explicit allowlist of
safe fields, and add metadata for retention (e.g., retention_ttl or
redaction_version) so downstream storage can enforce a TTL rather than keeping
raw data indefinitely.
- Around line 17-23: Wrap the DiscordEventLog::query()->create([...]) call in a
try/catch so a transient DB failure doesn't abort gateway processing; catch the
exception thrown when creating the record (use the same surrounding
scope/variable names: $payload, $payload->t, $payload->d->guild_id), log an
error containing the exception message and contextual fields event_type
($payload->t) and guild_id ($payload->d->guild_id ?? null), and do not
rethrow—fail silently for persistence while allowing the gateway listener to
continue.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 9eb63bf8-134a-492b-8ba3-dfb517925724

📥 Commits

Reviewing files that changed from the base of the PR and between 6a7b2fb and 86e08e4.

📒 Files selected for processing (5)
  • app-modules/bot-discord/src/Events/RawGatewayEvent.php
  • app-modules/bot-discord/tests/Feature/Events/RawGatewayEventTest.php
  • app-modules/integration-discord/database/migrations/2026_05_19_155732_create_discord_event_logs_table.php
  • app-modules/integration-discord/src/Models/DiscordEventLog.php
  • app-modules/integration-discord/tests/Feature/Models/DiscordEventLogTest.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • app-modules/integration-discord/database/migrations/2026_05_19_155732_create_discord_event_logs_table.php
  • app-modules/integration-discord/tests/Feature/Models/DiscordEventLogTest.php

Comment thread app-modules/bot-discord/src/Events/RawGatewayEvent.php
Comment thread app-modules/bot-discord/src/Events/RawGatewayEvent.php
@danielhe4rt danielhe4rt merged commit 42918f4 into 4.x May 19, 2026
6 checks passed
@danielhe4rt danielhe4rt deleted the feat/voice-and-event-log branch May 19, 2026 20:41
danielhe4rt added a commit that referenced this pull request May 19, 2026
## Summary

- Add **Marketing cluster** in admin panel sidebar with dedicated
sub-navigation
- Add **Discord Dashboard** page with real-time community analytics
(messages, voice, users)
- Move **Meeting Showcase** page into Marketing cluster
- Add **6 query classes** (`ActivityPerDay`, `VoicePerDay`,
`MessageHeatmap`, `VoiceHeatmap`, `TopChannels`, `PeriodStats`) with
proper UTC→BRT timezone handling
- Extend `x-he4rt::dashboard.bar-chart` component with
backward-compatible stacked mode
- Add `CONTEXT.md` and `ADR-0001` for panel-admin module

### Dashboard widgets
- **Stats overview** — Filament StatsOverviewWidget with sparklines and
rolling comparison
- **Period breakdown** — 5 switchable views (summary with line chart +
narrative, table, cards, stacked bars, donut)
- **Activity timeline** — Chart.js line chart (messages + voice + users)
- **Heatmap** — SVG day-of-week × hour matrix
- **Activity by DOW** — horizontal bar chart with All/Msgs/Voice toggle
(stacked mode)
- **Top Channels** — ranked horizontal bar chart

### Performance
- PeriodStats uses `CASE WHEN` conditional aggregates to fetch all
sub-periods in 2 queries instead of N×3
- `once()` memoization prevents duplicate query execution within a
request
- Heatmap data queried once and shared between heatmap + DOW widgets

## Test plan
- [ ] Navigate to `/admin/marketing/discord` and verify all widgets
render with real data
- [ ] Switch range selector (7d, 14d, 30d) and confirm data updates
- [ ] Verify heatmap shows correct day/hour assignments (Mon 22h SP
activity should show on Mon, not Tue)
- [ ] Toggle Activity by DOW between All/Msgs/Voice modes
- [ ] Switch period breakdown between all 5 views (summary, table,
cards, bars, donut)
- [ ] Verify Meeting Showcase still works at
`/admin/marketing/meeting-showcase`
- [ ] Check sidebar navigation shows Marketing cluster with back button
when inside cluster

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Description

This PR adds a Marketing cluster to the admin panel with a comprehensive
Discord Dashboard providing real-time community analytics across
messages, voice activity, and user engagement. Six query classes handle
activity metrics with UTC↔BRT timezone conversion, the bar-chart
component is extended with backward-compatible stacked mode support, and
two new pages deliver Discord Dashboard analytics and Meeting Showcase
participant visualization. Includes documentation,
English/Portuguese-Brazil translations, and performance optimizations
using conditional aggregates and memoization.

## References

- Related: feat: voice event persistence and Discord gateway data lake
(`#259`)
- Related commit: edda785 (wire Discord Dashboard to real queries and
fix PHPStan)

## Dependencies & Requirements

- **Timezone**: America/Sao_Paulo (BRT/UTC) for all dashboard queries
- **JavaScript**: Chart.js via CDN for timeline and chart visualization
- **Frontend**: Alpine.js for reactive dashboard state management
- **Export**: html2canvas library for PNG export functionality
- **Database**: Added `occurred_at` timestamp (indexed) to
discord_event_logs table
- **Framework**: Filament admin panel components and pages

## Contributor Summary

| Contributor | Lines Added | Lines Removed | Files Changed |
|---|---|---|---|
| PR Author | 2,250 | 49 | 25 |

## Changes Summary

| File Path | Change Description |
|---|---|
| `.gitignore` | Added `/storage/private/dumps` directory to ignore
patterns |
| `CONTEXT-MAP.md` | Registered Panel Admin bounded context entry |
| `app-modules/bot-discord/src/Events/RawGatewayEvent.php` | Refactored
user_id extraction and payload JSON encoding to flatten structure |
| `app-modules/bot-discord/tests/Feature/Events/RawGatewayEventTest.php`
| Updated payload assertions for flattened structure; removed obsolete
fallback tests |
|
`app-modules/he4rt/resources/views/components/dashboard/bar-chart.blade.php`
| Extended with stacked bar support: added `stacked`, `legend`,
`segments` props with conditional rendering |
|
`app-modules/integration-discord/database/migrations/2026_05_19_155732_create_discord_event_logs_table.php`
| Added indexed `occurred_at` timestamp column |
| `app-modules/integration-discord/src/Models/DiscordEventLog.php` |
Added `occurred_at` to fillable and datetime casting |
|
`app-modules/integration-discord/tests/Feature/Models/DiscordEventLogTest.php`
| Updated tests to populate `occurred_at` during record creation |
| `app-modules/panel-admin/CONTEXT.md` | Added 64-line context document
with module boundaries, glossary, and navigation rules |
|
`app-modules/panel-admin/docs/adr/0001-discord-dashboard-architecture.md`
| Added ADR documenting dashboard design, query layer, widget
architecture, and alternatives |
| `app-modules/panel-admin/lang/en/marketing.php` | Added English
translations for Marketing cluster navigation |
| `app-modules/panel-admin/lang/pt_BR/marketing.php` | Added
Portuguese-Brazil translations for Marketing cluster navigation |
|
`app-modules/panel-admin/resources/views/marketing/discord-dashboard.blade.php`
| Created 666-line dashboard template with stats, timeline chart,
heatmap, DOW activity, and top channels |
|
`app-modules/panel-admin/resources/views/pages/meeting-showcase.blade.php`
| Created 471-line showcase page with customizable grid, presets, and
PNG export functionality |
| `app-modules/panel-admin/src/Marketing/MarketingCluster.php` | Created
Filament cluster class with navigation configuration |
|
`app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/DiscordDashboard.php`
| Created page controller managing dashboard state, data loading, and
formatting logic |
|
`app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/ActivityPerDay.php`
| Created query class for daily message count and unique user
aggregation |
|
`app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/MessageHeatmap.php`
| Created query class for 7-day × 24-hour message heatmap with DOW/hour
grouping |
|
`app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/PeriodStats.php`
| Created query class with conditional aggregates for multi-period
statistics and sparklines |
|
`app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/TopChannels.php`
| Created query class for top 10 channels by message volume and distinct
users |
|
`app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/VoiceHeatmap.php`
| Created query class for voice join activity heatmap with DOW/hour
breakdown |
|
`app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/VoicePerDay.php`
| Created query class for daily voice join counts converted to hours
(0.75 multiplier) |
| `app-modules/panel-admin/src/Marketing/Pages/MeetingShowcasePage.php`
| Created page with participant loader, Discord metadata extraction, and
avatar URL fallback logic |
| `app-modules/panel-admin/src/Marketing/Widgets/DiscordStatsWidget.php`
| Created stats widget with metrics, trend indicators, and sparkline
charts |
| `app-modules/panel-admin/src/PanelAdminServiceProvider.php` | Updated
service provider to register Marketing cluster pages and navigation
builder |

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/he4rt/heartdevs.com/pull/260?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants