Skip to content

Commit 1fdd1b7

Browse files
author
Archith
committed
Add comprehensive activity data storage and privacy consent
Features: - Activity data saved to Firebase in multiple locations: - activity_log: Individual events - activity_summary: Periodic summaries (every 30s) - activity_final_summary: Final report at session end - New Activity tab in session details modal showing: - Activity score with visual indicators - Tab switches, idle periods, total idle time - Suspicious patterns detected - Beautiful UI with gradients and cards - Privacy consent checkbox for candidates: - Required before joining session - Links to Atomtickets privacy policy - Explains what is being monitored - Consent status saved to Firebase - Activity data included in Slack exports - Floating activity indicator for interviewers The system now properly saves all activity data for later review and requires explicit consent from candidates before monitoring begins.
1 parent 6a15520 commit 1fdd1b7

File tree

3 files changed

+235
-7
lines changed

3 files changed

+235
-7
lines changed

index.html

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,33 @@ <h2>Join Interview Session</h2>
171171
<small>Your interviewer will provide this code</small>
172172
</div>
173173

174+
<div style="margin: 20px 0; padding: 15px; background: rgba(66, 165, 245, 0.05); border: 1px solid rgba(66, 165, 245, 0.2); border-radius: 8px;">
175+
<div style="display: flex; align-items: center; margin-bottom: 10px;">
176+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#42a5f5" stroke-width="2" style="margin-right: 8px;">
177+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
178+
</svg>
179+
<strong style="color: #42a5f5; font-size: 14px;">Privacy & Monitoring Notice</strong>
180+
</div>
181+
<label style="display: flex; align-items: flex-start; cursor: pointer; font-size: 13px; color: #555;">
182+
<input type="checkbox" id="candidatePrivacyConsent" style="margin-right: 10px; margin-top: 2px; cursor: pointer;">
183+
<span>
184+
I understand that during this interview session, the following may be monitored for evaluation purposes:
185+
<ul style="margin: 8px 0 8px 20px; padding: 0; font-size: 12px; color: #666; line-height: 1.5;">
186+
<li>Tab switching and window focus events</li>
187+
<li>Activity patterns and idle time</li>
188+
<li>General interaction metrics</li>
189+
</ul>
190+
<span style="font-size: 11px; color: #888;">
191+
This data is used solely for interview assessment and is handled in accordance with our privacy policy.
192+
</span>
193+
<br>
194+
<a href="https://www.atomtickets.com/privacy" target="_blank" style="color: #42a5f5; text-decoration: none; margin-top: 8px; display: inline-block; font-size: 12px;">
195+
📄 View Atomtickets Privacy Policy →
196+
</a>
197+
</span>
198+
</label>
199+
</div>
200+
174201
<button id="candidateJoinBtn" class="primary-btn" disabled>Join Session</button>
175202
</div>
176203
</div>
@@ -375,6 +402,7 @@ <h3>Session: <span id="detail-session-code"></span></h3>
375402
<button class="detail-tab" data-tab="code">💻 Code</button>
376403
<button class="detail-tab" data-tab="info">ℹ️ Info</button>
377404
<button class="detail-tab" data-tab="security">🔒 Security</button>
405+
<button class="detail-tab" data-tab="activity">📊 Activity</button>
378406
</div>
379407

380408
<div class="tab-content" id="notes-tab" style="display: block;">
@@ -447,6 +475,15 @@ <h4>🔍 Detailed Login & Device Information</h4>
447475
</div>
448476
</div>
449477
</div>
478+
479+
<div class="tab-content" id="activity-tab" style="display: none;">
480+
<div class="activity-info-display" style="padding: 20px;">
481+
<h4>📊 Candidate Activity Analysis</h4>
482+
<div id="activity-tracking-data">
483+
<p class="loading-message">Loading activity data...</p>
484+
</div>
485+
</div>
486+
</div>
450487
</div>
451488
</div>
452489
</div>

scripts/activity-monitor.js

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,23 @@
5252
.ref(`sessions/${sessionCode}/activity_summary`)
5353
.on('value', function(snapshot) {
5454
const summary = snapshot.val();
55-
if (summary && window.updateActivityDashboard) {
56-
window.updateActivityDashboard(summary);
55+
if (summary) {
56+
// Cache for later use
57+
window.lastActivitySummary = summary;
58+
if (window.updateActivityDashboard) {
59+
window.updateActivityDashboard(summary);
60+
}
61+
}
62+
});
63+
64+
// Also listen for final summary
65+
firebase.database()
66+
.ref(`sessions/${sessionCode}/activity_final_summary`)
67+
.on('value', function(snapshot) {
68+
const finalSummary = snapshot.val();
69+
if (finalSummary) {
70+
window.lastActivitySummary = finalSummary;
71+
console.log('Final activity summary received:', finalSummary);
5772
}
5873
});
5974

@@ -363,16 +378,50 @@
363378

364379
// 9. Get summary for export
365380
window.getActivitySummary = function() {
366-
if (!monitoring) return null;
381+
// For candidates, return their metrics
382+
if (monitoring) {
383+
return {
384+
tabSwitches: metrics.tabSwitches,
385+
idlePeriods: metrics.idlePeriods,
386+
totalIdleSeconds: Math.round(metrics.totalIdleTime / 1000),
387+
suspiciousPatterns: metrics.suspiciousPatterns,
388+
activityScore: calculateActivityScore(),
389+
sessionDurationMinutes: Math.round((Date.now() - metrics.sessionStart) / 60000)
390+
};
391+
}
392+
// For interviewers, get the last saved summary from Firebase
393+
else if (window.lastActivitySummary) {
394+
return window.lastActivitySummary;
395+
}
396+
397+
return null;
398+
};
399+
400+
// Save final activity summary when session ends
401+
window.saveActivitySummary = function() {
402+
if (!monitoring) return;
367403

368-
return {
404+
const finalSummary = {
369405
tabSwitches: metrics.tabSwitches,
370406
idlePeriods: metrics.idlePeriods,
371407
totalIdleSeconds: Math.round(metrics.totalIdleTime / 1000),
372408
suspiciousPatterns: metrics.suspiciousPatterns,
373409
activityScore: calculateActivityScore(),
374-
sessionDurationMinutes: Math.round((Date.now() - metrics.sessionStart) / 60000)
410+
sessionDurationMinutes: Math.round((Date.now() - metrics.sessionStart) / 60000),
411+
finalReport: true,
412+
timestamp: Date.now()
375413
};
414+
415+
// Save to Firebase
416+
if (window.firebase && sessionCode) {
417+
firebase.database()
418+
.ref(`sessions/${sessionCode}/activity_final_summary`)
419+
.set(finalSummary);
420+
421+
console.log('Activity summary saved:', finalSummary);
422+
}
423+
424+
return finalSummary;
376425
};
377426

378427
// 10. Update interviewer UI

scripts/app.js

Lines changed: 144 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,24 @@
4343

4444
// Enable/disable join button
4545
function updateJoinButton() {
46+
const privacyConsent = document.getElementById('candidatePrivacyConsent');
4647
candidateJoinBtn.disabled =
4748
!candidateName.value.trim() ||
48-
candidateSessionCode.value.length !== 6;
49+
candidateSessionCode.value.length !== 6 ||
50+
!privacyConsent.checked;
4951
}
5052

5153
candidateName.addEventListener('input', updateJoinButton);
5254
candidateSessionCode.addEventListener('input', function() {
5355
this.value = this.value.replace(/[^0-9]/g, '').slice(0, 6);
5456
updateJoinButton();
5557
});
58+
59+
// Privacy consent checkbox
60+
const privacyConsent = document.getElementById('candidatePrivacyConsent');
61+
if (privacyConsent) {
62+
privacyConsent.addEventListener('change', updateJoinButton);
63+
}
5664

5765
// Join session
5866
candidateJoinBtn.addEventListener('click', async function() {
@@ -81,10 +89,21 @@
8189
// Remove the security check message - just proceed
8290
}
8391

84-
// Initialize activity monitoring for candidates
92+
// Initialize activity monitoring for candidates (with consent)
8593
if (window.initActivityMonitor) {
8694
console.log('Starting activity monitoring for candidate:', name);
8795
window.initActivityMonitor(sessionCode, name, 'candidate');
96+
97+
// Log consent status
98+
if (window.firebase) {
99+
firebase.database()
100+
.ref(`sessions/${sessionCode}/privacy_consent/${name}`)
101+
.set({
102+
consented: true,
103+
timestamp: Date.now(),
104+
consentedTo: 'Activity monitoring during interview session'
105+
});
106+
}
88107
}
89108

90109
Auth.joinAsCandidate(name);
@@ -1135,6 +1154,123 @@
11351154
});
11361155
}
11371156

1157+
// Display activity tab data
1158+
function displayActivityTab(sessionCode) {
1159+
const container = document.getElementById('activity-tracking-data');
1160+
if (!container) return;
1161+
1162+
// Start loading
1163+
container.innerHTML = '<p class="loading-message">Loading activity data...</p>';
1164+
1165+
// Get activity data from Firebase
1166+
if (window.firebase) {
1167+
// Try to get final summary first, then regular summary
1168+
firebase.database()
1169+
.ref(`sessions/${sessionCode}/activity_final_summary`)
1170+
.once('value')
1171+
.then(snapshot => {
1172+
let activityData = snapshot.val();
1173+
1174+
// If no final summary, try regular summary
1175+
if (!activityData) {
1176+
return firebase.database()
1177+
.ref(`sessions/${sessionCode}/activity_summary`)
1178+
.once('value');
1179+
}
1180+
return snapshot;
1181+
})
1182+
.then(snapshot => {
1183+
const activityData = snapshot.val();
1184+
1185+
if (!activityData) {
1186+
container.innerHTML = `
1187+
<div style="padding: 20px; text-align: center; color: #666;">
1188+
<p>No activity data available for this session.</p>
1189+
<p style="font-size: 12px; margin-top: 10px;">Activity tracking may not have been enabled for this session.</p>
1190+
</div>
1191+
`;
1192+
return;
1193+
}
1194+
1195+
// Display activity data
1196+
const scoreColor = activityData.activityScore > 80 ? '#4caf50' :
1197+
activityData.activityScore > 60 ? '#ff9800' : '#ff4444';
1198+
1199+
container.innerHTML = `
1200+
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; border-radius: 8px; color: white; margin-bottom: 20px;">
1201+
<div style="display: flex; justify-content: space-between; align-items: center;">
1202+
<div>
1203+
<h3 style="margin: 0; font-size: 24px;">Activity Score</h3>
1204+
<div style="font-size: 48px; font-weight: bold; color: ${scoreColor}; text-shadow: 0 2px 4px rgba(0,0,0,0.2);">
1205+
${activityData.activityScore || 0}%
1206+
</div>
1207+
</div>
1208+
<div style="text-align: right;">
1209+
<div style="font-size: 14px; opacity: 0.9;">Session Duration</div>
1210+
<div style="font-size: 20px; font-weight: bold;">${activityData.sessionDurationMinutes || 0} min</div>
1211+
</div>
1212+
</div>
1213+
</div>
1214+
1215+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 20px;">
1216+
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; border-left: 4px solid #42a5f5;">
1217+
<div style="color: #666; font-size: 12px; text-transform: uppercase; margin-bottom: 5px;">Tab Switches</div>
1218+
<div style="font-size: 24px; font-weight: bold; color: ${activityData.tabSwitches > 10 ? '#ff9800' : '#333'};">
1219+
${activityData.tabSwitches || 0}
1220+
</div>
1221+
</div>
1222+
1223+
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; border-left: 4px solid #66bb6a;">
1224+
<div style="color: #666; font-size: 12px; text-transform: uppercase; margin-bottom: 5px;">Idle Periods</div>
1225+
<div style="font-size: 24px; font-weight: bold; color: #333;">
1226+
${activityData.idlePeriods || 0}
1227+
</div>
1228+
</div>
1229+
1230+
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; border-left: 4px solid #ffa726;">
1231+
<div style="color: #666; font-size: 12px; text-transform: uppercase; margin-bottom: 5px;">Total Idle Time</div>
1232+
<div style="font-size: 24px; font-weight: bold; color: #333;">
1233+
${activityData.totalIdleSeconds || 0}s
1234+
</div>
1235+
</div>
1236+
</div>
1237+
1238+
${activityData.suspiciousPatterns && activityData.suspiciousPatterns.length > 0 ? `
1239+
<div style="background: rgba(255,152,0,0.1); border: 1px solid rgba(255,152,0,0.3); border-radius: 8px; padding: 15px; margin-top: 20px;">
1240+
<h4 style="color: #ff9800; margin-top: 0;">⚠️ Suspicious Patterns Detected</h4>
1241+
<ul style="margin: 10px 0;">
1242+
${activityData.suspiciousPatterns.map(p => `
1243+
<li style="color: #666; margin: 5px 0;">
1244+
${p.type.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
1245+
${p.duration ? `(${Math.round(p.duration/1000)}s)` : ''}
1246+
${p.size ? `(${p.size} chars)` : ''}
1247+
</li>
1248+
`).join('')}
1249+
</ul>
1250+
</div>
1251+
` : ''}
1252+
1253+
${activityData.finalReport ? `
1254+
<div style="margin-top: 20px; padding: 10px; background: #e8f5e9; border-radius: 4px; text-align: center; color: #2e7d32;">
1255+
✅ Final activity report saved at session end
1256+
</div>
1257+
` : ''}
1258+
`;
1259+
})
1260+
.catch(error => {
1261+
console.error('Error loading activity data:', error);
1262+
container.innerHTML = `
1263+
<div style="padding: 20px; text-align: center; color: #f44336;">
1264+
<p>Error loading activity data.</p>
1265+
<p style="font-size: 12px; margin-top: 10px;">${error.message}</p>
1266+
</div>
1267+
`;
1268+
});
1269+
} else {
1270+
container.innerHTML = '<p>Firebase not initialized</p>';
1271+
}
1272+
}
1273+
11381274
// View session details with notes
11391275
function viewSessionDetails(sessionCode, sessionData) {
11401276
console.log('viewSessionDetails called with:', sessionCode, sessionData);
@@ -1337,6 +1473,12 @@
13371473
const sessionCode = document.getElementById('detail-session-code').textContent;
13381474
window.SessionTracking.displaySecurityTab(sessionCode);
13391475
}
1476+
1477+
// If activity tab, load activity data
1478+
if (tabName === 'activity') {
1479+
const sessionCode = document.getElementById('detail-session-code').textContent;
1480+
displayActivityTab(sessionCode);
1481+
}
13401482
}
13411483
});
13421484
});

0 commit comments

Comments
 (0)