Add per-staff metrics: StaffAction event log + /stats command
Event-sourced tracking of staff/ticket lifecycle actions, plus a /stats command. Foundation for a future tickets-website analytics dashboard. Data: - StaffAction model (event log) + Ticket.game / Ticket.closedAt - STATS_ADMIN_IDS config (who may view others' stats) Recording (fire-and-forget, idempotent on real state transitions): - claim, response (channel reply + /response send), escalate, de-escalate, transfer, close (4 sites), reopen — each denormalizes ticketType, tier, priority, game, requester (senderEmail / creatorId), guildId - close events carry closerType / resolverId (claimer credit) / wasClaimed; transfer carries fromId / toId; reopen stamps resolverId - conditional close transition helper (atomic open->closed + closedAt) shared by all four close paths Query + command: - pure period parser (presets + free-text) and stats shaper (per-metric keys) - command-aware autocomplete dispatch - /stats: period (autocomplete) + member (admin-gated) + source (all/email/ discord), ManageMessages + staff-role gated, ephemeral, tier-labeled embed 288+ unit tests; timing/busiest-times data is collected but displayed later.
This commit is contained in:
30
models.js
30
models.js
@@ -20,7 +20,9 @@ const ticketSchema = new mongoose.Schema({
|
||||
claimerId: String,
|
||||
creatorId: String,
|
||||
parentCategoryId: String,
|
||||
pendingDelete: { type: Boolean, default: false }
|
||||
pendingDelete: { type: Boolean, default: false },
|
||||
game: String,
|
||||
closedAt: Date
|
||||
});
|
||||
ticketSchema.index({ status: 1, lastActivity: 1 });
|
||||
ticketSchema.index({ senderEmail: 1, status: 1 });
|
||||
@@ -61,3 +63,29 @@ mongoose.model('StaffSignature', new mongoose.Schema({
|
||||
tagline: { type: String, default: '' },
|
||||
updatedAt: { type: Date, default: Date.now }
|
||||
}));
|
||||
|
||||
const staffActionSchema = new mongoose.Schema({
|
||||
staffId: { type: String, required: true },
|
||||
type: { type: String, required: true },
|
||||
tier: { type: Number, default: 0 },
|
||||
ticketType: String,
|
||||
priority: String,
|
||||
game: String,
|
||||
senderEmail: String,
|
||||
creatorId: String,
|
||||
gmailThreadId: String,
|
||||
guildId: String,
|
||||
createdAt: { type: Date, default: Date.now },
|
||||
|
||||
// close-only
|
||||
closerType: String,
|
||||
resolverId: String,
|
||||
wasClaimed: Boolean,
|
||||
|
||||
// transfer-only
|
||||
fromId: String,
|
||||
toId: String
|
||||
});
|
||||
staffActionSchema.index({ staffId: 1, createdAt: -1 });
|
||||
staffActionSchema.index({ gmailThreadId: 1, createdAt: 1 });
|
||||
mongoose.model('StaffAction', staffActionSchema);
|
||||
|
||||
Reference in New Issue
Block a user