fix/call-status-and-video-recording #22
@ -32,6 +32,15 @@ public function index()
|
|||||||
}
|
}
|
||||||
|
|
||||||
$interviews = $user->getAccessibleInterviews();
|
$interviews = $user->getAccessibleInterviews();
|
||||||
|
|
||||||
|
// Auto-terminate call session if interview timeline has expired
|
||||||
|
foreach ($interviews as $interview) {
|
||||||
|
if ($interview->isExpired() && $interview->call_status === 'active') {
|
||||||
|
$interview->update(['call_status' => 'ended', 'active_peers' => []]);
|
||||||
|
$interview->call_status = 'ended';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$interviewers = User::orderBy('name', 'asc')->get();
|
$interviewers = User::orderBy('name', 'asc')->get();
|
||||||
|
|
||||||
return view('interview.index', compact('interviews', 'interviewers'));
|
return view('interview.index', compact('interviews', 'interviewers'));
|
||||||
@ -49,6 +58,13 @@ public function show($id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
$interview = Interview::findOrFail($id);
|
$interview = Interview::findOrFail($id);
|
||||||
|
|
||||||
|
// Auto-terminate call session if interview timeline has expired
|
||||||
|
if ($interview->isExpired() && $interview->call_status === 'active') {
|
||||||
|
$interview->update(['call_status' => 'ended', 'active_peers' => []]);
|
||||||
|
$interview->call_status = 'ended';
|
||||||
|
}
|
||||||
|
|
||||||
$interviewers = User::orderBy('name', 'asc')->get();
|
$interviewers = User::orderBy('name', 'asc')->get();
|
||||||
|
|
||||||
return view('interview.show', compact('interview', 'interviewers'));
|
return view('interview.show', compact('interview', 'interviewers'));
|
||||||
@ -625,7 +641,8 @@ public function getActiveCalls(Request $request)
|
|||||||
// Auto-terminate call session if interview timeline has expired
|
// Auto-terminate call session if interview timeline has expired
|
||||||
if ($interview->isExpired()) {
|
if ($interview->isExpired()) {
|
||||||
if ($interview->call_status === 'active') {
|
if ($interview->call_status === 'active') {
|
||||||
$interview->update(['call_status' => 'ended']);
|
$interview->update(['call_status' => 'ended', 'active_peers' => []]);
|
||||||
|
$interview->call_status = 'ended';
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -70,6 +70,18 @@ public function isExpired(): bool
|
|||||||
return now()->greaterThan($this->expires_at);
|
return now()->greaterThan($this->expires_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the interview call is active and unexpired.
|
||||||
|
*/
|
||||||
|
public function isCallActive(): bool
|
||||||
|
{
|
||||||
|
if ($this->isExpired() || in_array($this->status, ['completed', 'expired'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->call_status === 'active';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the current time is within the interview timeline.
|
* Check if the current time is within the interview timeline.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -10,11 +10,11 @@
|
|||||||
<x-button id="btn-toggle-interviewer-cam" onclick="toggleInterviewerCam()" variant="ghost" icon="fa-solid fa-video" class="hidden" title="Cam On / Off"></x-button>
|
<x-button id="btn-toggle-interviewer-cam" onclick="toggleInterviewerCam()" variant="ghost" icon="fa-solid fa-video" class="hidden" title="Cam On / Off"></x-button>
|
||||||
|
|
||||||
<!-- 3. Start / Join / Restart Call -->
|
<!-- 3. Start / Join / Restart Call -->
|
||||||
@if($interview->call_status === 'ended')
|
@if($interview->isExpired() || $interview->call_status === 'ended')
|
||||||
<x-button id="btn-start-call" disabled variant="primary" icon="fa-solid fa-phone-slash" class="opacity-50 cursor-not-allowed pointer-events-none">
|
<x-button id="btn-start-call" disabled variant="primary" icon="fa-solid fa-phone-slash" class="opacity-50 cursor-not-allowed pointer-events-none">
|
||||||
Call Ended
|
Call Ended
|
||||||
</x-button>
|
</x-button>
|
||||||
@elseif($interview->call_status === 'active')
|
@elseif(!$interview->isExpired() && $interview->call_status === 'active')
|
||||||
<x-button id="btn-start-call" onclick="startInterviewerCall()" variant="primary" icon="fa-solid fa-phone">
|
<x-button id="btn-start-call" onclick="startInterviewerCall()" variant="primary" icon="fa-solid fa-phone">
|
||||||
Join Call
|
Join Call
|
||||||
</x-button>
|
</x-button>
|
||||||
|
|||||||
@ -76,7 +76,7 @@
|
|||||||
<x-pill id="timeline-status-pill" variant="danger" size="sm" icon="fa-solid fa-circle">Expired</x-pill>
|
<x-pill id="timeline-status-pill" variant="danger" size="sm" icon="fa-solid fa-circle">Expired</x-pill>
|
||||||
@elseif($interview->status === 'completed')
|
@elseif($interview->status === 'completed')
|
||||||
<x-pill id="timeline-status-pill" variant="success" size="sm" icon="fa-solid fa-circle">Completed</x-pill>
|
<x-pill id="timeline-status-pill" variant="success" size="sm" icon="fa-solid fa-circle">Completed</x-pill>
|
||||||
@elseif($interview->call_status === 'active')
|
@elseif(!$interview->isExpired() && $interview->call_status === 'active')
|
||||||
<x-pill id="timeline-status-pill" variant="info" size="sm" class="animate-pulse" icon="fa-solid fa-circle">Call in progress</x-pill>
|
<x-pill id="timeline-status-pill" variant="info" size="sm" class="animate-pulse" icon="fa-solid fa-circle">Call in progress</x-pill>
|
||||||
@elseif($interview->isActiveTimeline())
|
@elseif($interview->isActiveTimeline())
|
||||||
<x-pill id="timeline-status-pill" variant="info" size="sm" icon="fa-solid fa-bolt">Timeline live</x-pill>
|
<x-pill id="timeline-status-pill" variant="info" size="sm" icon="fa-solid fa-bolt">Timeline live</x-pill>
|
||||||
|
|||||||
@ -4,6 +4,10 @@
|
|||||||
|
|
||||||
@php
|
@php
|
||||||
$metrics = $interview->proctor_metrics;
|
$metrics = $interview->proctor_metrics;
|
||||||
|
if ($interview->isExpired() && $interview->call_status === 'active') {
|
||||||
|
$interview->update(['call_status' => 'ended', 'active_peers' => []]);
|
||||||
|
$interview->call_status = 'ended';
|
||||||
|
}
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<tr class="hover:bg-white/[0.02] transition-colors">
|
<tr class="hover:bg-white/[0.02] transition-colors">
|
||||||
@ -36,7 +40,7 @@
|
|||||||
<x-pill variant="danger" size="sm">EXPIRED</x-pill>
|
<x-pill variant="danger" size="sm">EXPIRED</x-pill>
|
||||||
@elseif($interview->status === 'completed')
|
@elseif($interview->status === 'completed')
|
||||||
<x-pill variant="success" size="sm">COMPLETED</x-pill>
|
<x-pill variant="success" size="sm">COMPLETED</x-pill>
|
||||||
@elseif($interview->call_status === 'active')
|
@elseif(!$interview->isExpired() && $interview->call_status === 'active')
|
||||||
<x-pill variant="success" size="sm" icon="fa-solid fa-phone" class="animate-pulse font-extrabold">CALL IN PROGRESS</x-pill>
|
<x-pill variant="success" size="sm" icon="fa-solid fa-phone" class="animate-pulse font-extrabold">CALL IN PROGRESS</x-pill>
|
||||||
@elseif($interview->isActiveTimeline())
|
@elseif($interview->isActiveTimeline())
|
||||||
<x-pill variant="info" size="sm" icon="fa-solid fa-bolt">TIMELINE LIVE</x-pill>
|
<x-pill variant="info" size="sm" icon="fa-solid fa-bolt">TIMELINE LIVE</x-pill>
|
||||||
@ -71,7 +75,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Button 2: Join Call -->
|
<!-- Button 2: Join Call -->
|
||||||
@if($interview->call_status === 'active')
|
@if(!$interview->isExpired() && $interview->call_status === 'active')
|
||||||
<a href="{{ route('interview.show', $interview->id) }}?join_call=1">
|
<a href="{{ route('interview.show', $interview->id) }}?join_call=1">
|
||||||
<x-button variant="success" size="sm" icon="fa-solid fa-phone-volume" class="animate-pulse font-extrabold shadow-md shadow-emerald-500/50">
|
<x-button variant="success" size="sm" icon="fa-solid fa-phone-volume" class="animate-pulse font-extrabold shadow-md shadow-emerald-500/50">
|
||||||
Join Active Call
|
Join Active Call
|
||||||
|
|||||||
@ -416,4 +416,49 @@ public function test_tab_switch_logging_and_external_ai_detection_behavior(): vo
|
|||||||
->assertJsonFragment(['type' => 'talking_on_phone'])
|
->assertJsonFragment(['type' => 'talking_on_phone'])
|
||||||
->assertJsonFragment(['type' => 'reading_external_device']);
|
->assertJsonFragment(['type' => 'reading_external_device']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_expired_interview_automatically_ends_call_status_when_rendering_view(): void
|
||||||
|
{
|
||||||
|
$admin = User::create([
|
||||||
|
'name' => 'Admin Test',
|
||||||
|
'email' => 'admintest@example.com',
|
||||||
|
'role' => 'admin',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create an expired interview that had active call_status in DB
|
||||||
|
$expiredInterview = Interview::create([
|
||||||
|
'candidate_name' => 'Expired Candidate',
|
||||||
|
'candidate_email' => 'expired@example.com',
|
||||||
|
'candidate_phone' => '1231231234',
|
||||||
|
'temp_password' => 'Pass-111222',
|
||||||
|
'expires_at' => now()->subHour(),
|
||||||
|
'language' => 'python',
|
||||||
|
'status' => 'scheduled',
|
||||||
|
'call_status' => 'active',
|
||||||
|
'active_peers' => [['peer_id' => 'peer_1', 'last_seen' => now()->subHour()->timestamp]],
|
||||||
|
'submission_unique_id' => 'expired-cand_1231231234_1752000100',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertTrue($expiredInterview->isExpired());
|
||||||
|
$this->assertFalse($expiredInterview->isCallActive());
|
||||||
|
|
||||||
|
// 1. Loading index page view should automatically end the call status
|
||||||
|
$response = $this->actingAs($admin)->get(route('interview.index'));
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertSee('EXPIRED');
|
||||||
|
$response->assertDontSee('Join Active Call');
|
||||||
|
|
||||||
|
$expiredInterview->refresh();
|
||||||
|
$this->assertEquals('ended', $expiredInterview->call_status);
|
||||||
|
$this->assertEmpty($expiredInterview->active_peers);
|
||||||
|
|
||||||
|
// 2. Reset back to active and test show page view
|
||||||
|
$expiredInterview->update(['call_status' => 'active']);
|
||||||
|
$showResp = $this->actingAs($admin)->get(route('interview.show', $expiredInterview->id));
|
||||||
|
$showResp->assertStatus(200);
|
||||||
|
$showResp->assertSee('Call Ended');
|
||||||
|
|
||||||
|
$expiredInterview->refresh();
|
||||||
|
$this->assertEquals('ended', $expiredInterview->call_status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user