diff --git a/src/app/Http/Controllers/API/V4/UsersController.php b/src/app/Http/Controllers/API/V4/UsersController.php --- a/src/app/Http/Controllers/API/V4/UsersController.php +++ b/src/app/Http/Controllers/API/V4/UsersController.php @@ -135,13 +135,20 @@ if (!empty(request()->input('refresh'))) { $updated = false; + $async = false; $last_step = 'none'; foreach ($response['process'] as $idx => $step) { $last_step = $step['label']; if (!$step['state']) { - if (!$this->execProcessStep($user, $step['label'])) { + $exec = $this->execProcessStep($user, $step['label']); + + if (!$exec) { + if ($exec === null) { + $async = true; + } + break; } @@ -158,6 +165,12 @@ $response['status'] = $success ? 'success' : 'error'; $response['message'] = \trans('app.process-' . $suffix); + + if ($async && !$success) { + $response['processState'] = 'waiting'; + $response['status'] = 'success'; + $response['message'] = \trans('app.process-async'); + } } $response = array_merge($response, self::userStatuses($user)); @@ -594,9 +607,10 @@ * @param \App\User $user User object * @param string $step Step identifier (as in self::statusInfo()) * - * @return bool True if the execution succeeded, False otherwise + * @return bool|null True if the execution succeeded, False if not, Null when + * the job has been sent to the worker (result unknown) */ - public static function execProcessStep(User $user, string $step): bool + public static function execProcessStep(User $user, string $step): ?bool { try { if (strpos($step, 'domain-') === 0) { @@ -618,6 +632,14 @@ case 'user-imap-ready': // User not in IMAP? Verify again + // Do it synchronously if the imap admin credentials are available + // otherwise let the worker do the job + if (!\config('imap.admin_password')) { + \App\Jobs\User\VerifyJob::dispatch($user->id); + + return null; + } + $job = new \App\Jobs\User\VerifyJob($user->id); $job->handle(); diff --git a/src/resources/lang/en/app.php b/src/resources/lang/en/app.php --- a/src/resources/lang/en/app.php +++ b/src/resources/lang/en/app.php @@ -15,6 +15,7 @@ 'planbutton' => 'Choose :plan', + 'process-async' => 'Setup process has been pushed. Please wait.', 'process-user-new' => 'Registering a user...', 'process-user-ldap-ready' => 'Creating a user...', 'process-user-imap-ready' => 'Creating a mailbox...', diff --git a/src/resources/vue/Widgets/Status.vue b/src/resources/vue/Widgets/Status.vue --- a/src/resources/vue/Widgets/Status.vue +++ b/src/resources/vue/Widgets/Status.vue @@ -51,12 +51,17 @@ refresh: false, delay: 5000, scope: 'user', - state: { isReady: true } + state: { isReady: true }, + waiting: 0, } }, watch: { // We use property watcher because parent component // might set the property with a delay and we need to parse it + // FIXME: Problem with this and update-status event is that whenever + // we emit the event a watcher function is executed, causing + // duplicate parseStatusInfo() calls. Fortunaltely this does not + // cause duplicate http requests. status: function (val, oldVal) { this.parseStatusInfo(val) } @@ -99,18 +104,26 @@ }) // Unhide the Refresh button, the process is in failure state - this.refresh = info.processState == 'failed' + this.refresh = info.processState == 'failed' && this.waiting == 0 if (this.refresh || info.step == 'domain-confirmed') { this.className = 'failed' } + + // A async job has been dispatched, switch to a waiting mode where + // we hide the Refresh button and pull status for about a minute, + // after that we switch to normal mode, i.e. user can Refresh again (if still not ready) + if (info.processState == 'waiting') { + this.waiting = 10 + this.delay = 5000 + } else if (this.waiting > 0) { + this.waiting -= 1 + } } - // Update status process info every 10 seconds - // FIXME: This probably should have some limit, or the interval - // should grow (well, until it could be done with websocket notifications) + // Update status process info every 5,6,7,8,9,... seconds clearTimeout(window.infoRequest) - if (!this.refresh && (!info || !info.isReady)) { + if ((!this.refresh || this.waiting > 0) && (!info || !info.isReady)) { window.infoRequest = setTimeout(() => { delete window.infoRequest // Stop updates after user logged out diff --git a/src/tests/Feature/Controller/UsersTest.php b/src/tests/Feature/Controller/UsersTest.php --- a/src/tests/Feature/Controller/UsersTest.php +++ b/src/tests/Feature/Controller/UsersTest.php @@ -293,6 +293,8 @@ */ public function testStatus(): void { + Queue::fake(); + $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); @@ -337,6 +339,28 @@ $this->assertSame(true, $json['process'][2]['state']); $this->assertSame('success', $json['status']); $this->assertSame('Setup process finished successfully.', $json['message']); + + Queue::size(1); + + // Test case for when the verify job is dispatched to the worker + $john->refresh(); + $john->status ^= User::STATUS_IMAP_READY; + $john->save(); + + \config(['imap.admin_password' => null]); + + $response = $this->actingAs($john)->get("/api/v4/users/{$john->id}/status?refresh=1"); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertFalse($json['isImapReady']); + $this->assertFalse($json['isReady']); + $this->assertSame('success', $json['status']); + $this->assertSame('waiting', $json['processState']); + $this->assertSame('Setup process has been pushed. Please wait.', $json['message']); + + Queue::assertPushed(\App\Jobs\User\VerifyJob::class, 1); } /**