Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117877887
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
19 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/resources/vue/Widgets/Status.vue b/src/resources/vue/Widgets/Status.vue
index 2549569f..241f1794 100644
--- a/src/resources/vue/Widgets/Status.vue
+++ b/src/resources/vue/Widgets/Status.vue
@@ -1,177 +1,185 @@
<template>
<div v-if="!state.isReady" id="status-box" :class="'p-4 mb-3 rounded process-' + className">
<div v-if="state.step != 'domain-confirmed'" class="d-flex align-items-start">
<p id="status-body" class="flex-grow-1">
<span v-if="scope == 'dashboard'">We are preparing your account.</span>
<span v-else-if="scope == 'domain'">We are preparing the domain.</span>
<span v-else>We are preparing the user account.</span>
<br>
Some features may be missing or readonly at the moment.<br>
<span id="refresh-text" v-if="refresh">The process never ends? Press the "Refresh" button, please.</span>
</p>
<button v-if="refresh" id="status-refresh" href="#" class="btn btn-secondary" @click="statusRefresh">
<svg-icon icon="sync-alt"></svg-icon> Refresh
</button>
</div>
<div v-else class="d-flex align-items-start">
<p id="status-body" class="flex-grow-1">
<span v-if="scope == 'dashboard'">Your account is almost ready.</span>
<span v-else-if="scope == 'domain'">The domain is almost ready.</span>
<span v-else>The user account is almost ready.</span>
<br>
Verify your domain to finish the setup process.
</p>
<div v-if="scope == 'domain'">
<button id="status-verify" class="btn btn-secondary text-nowrap" @click="confirmDomain">
<svg-icon icon="sync-alt"></svg-icon> Verify
</button>
</div>
<div v-else-if="state.link && scope != 'domain'">
<router-link id="status-link" class="btn btn-secondary" :to="{ path: state.link }">Verify domain</router-link>
</div>
</div>
<div class="status-progress text-center">
<div class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<span class="progress-label">{{ state.title || 'Initializing...' }}</span>
</div>
</div>
</template>
<script>
export default {
props: {
status: { type: Object, default: () => {} }
},
data() {
return {
className: 'pending',
refresh: false,
delay: 5000,
scope: 'user',
state: { isReady: true }
}
},
watch: {
// We use property watcher because parent component
// might set the property with a delay and we need to parse it
status: function (val, oldVal) {
this.parseStatusInfo(val)
}
},
destroyed() {
clearTimeout(window.infoRequest)
},
mounted() {
this.scope = this.$route.name
},
methods: {
// Displays account status information
parseStatusInfo(info) {
if (info) {
if (!info.isReady) {
+ let failedCount = 0
+ let allCount = info.process.length
+
info.process.forEach((step, idx) => {
- if (!step.state && !('percent' in info)) {
- info.title = step.title
- info.step = step.label
- info.percent = Math.floor(idx / info.process.length * 100);
- info.link = step.link
+ if (!step.state) {
+ failedCount++
+
+ if (!info.title) {
+ info.title = step.title
+ info.step = step.label
+ info.link = step.link
+ }
}
})
+
+ info.percent = Math.floor((allCount - failedCount) / allCount * 100);
}
this.state = info || {}
this.$nextTick(function() {
$(this.$el).find('.progress-bar')
.css('width', info.percent + '%')
.attr('aria-valuenow', info.percent)
})
// Unhide the Refresh button, the process is in failure state
this.refresh = info.processState == 'failed'
if (this.refresh || info.step == 'domain-confirmed') {
this.className = 'failed'
}
}
// 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)
clearTimeout(window.infoRequest)
if (!this.refresh && (!info || !info.isReady)) {
window.infoRequest = setTimeout(() => {
delete window.infoRequest
// Stop updates after user logged out
if (!this.$store.state.isLoggedIn) {
return;
}
axios.get(this.getUrl())
.then(response => {
this.parseStatusInfo(response.data)
this.emitEvent(response.data)
})
.catch(error => {
this.parseStatusInfo(info)
})
}, this.delay);
this.delay += 1000;
}
},
statusRefresh() {
clearTimeout(window.infoRequest)
axios.get(this.getUrl() + '?refresh=1')
.then(response => {
this.$toast[response.data.status](response.data.message)
this.parseStatusInfo(response.data)
this.emitEvent(response.data)
})
.catch(error => {
this.parseStatusInfo(this.state)
})
},
confirmDomain() {
axios.get('/api/v4/domains/' + this.$route.params.domain + '/confirm')
.then(response => {
if (response.data.message) {
this.$toast[response.data.status](response.data.message)
}
if (response.data.status == 'success') {
this.parseStatusInfo(response.data.statusInfo)
response.data.isConfirmed = true
this.emitEvent(response.data)
}
})
},
emitEvent(data) {
// Remove useless data and emit the event (to parent components)
delete data.status
delete data.message
this.$emit('status-update', data)
},
getUrl() {
let url
switch (this.scope) {
case 'dashboard':
url = '/api/v4/users/' + this.$store.state.authInfo.id + '/status'
break
case 'domain':
url = '/api/v4/domains/' + this.$route.params.domain + '/status'
break
default:
url = '/api/v4/users/' + this.$route.params.user + '/status'
}
return url
}
}
}
</script>
diff --git a/src/tests/Browser/StatusTest.php b/src/tests/Browser/StatusTest.php
index b55e4f32..0a312d6c 100644
--- a/src/tests/Browser/StatusTest.php
+++ b/src/tests/Browser/StatusTest.php
@@ -1,270 +1,270 @@
<?php
namespace Tests\Browser;
use App\Domain;
use App\User;
use Carbon\Carbon;
use Tests\Browser;
use Tests\Browser\Components\Status;
use Tests\Browser\Components\Toast;
use Tests\Browser\Pages\Dashboard;
use Tests\Browser\Pages\DomainInfo;
use Tests\Browser\Pages\DomainList;
use Tests\Browser\Pages\Home;
use Tests\Browser\Pages\UserInfo;
use Tests\Browser\Pages\UserList;
use Tests\TestCaseDusk;
use Illuminate\Support\Facades\DB;
class StatusTest extends TestCaseDusk
{
/**
* {@inheritDoc}
*/
public function setUp(): void
{
parent::setUp();
$domain_status = Domain::STATUS_CONFIRMED | Domain::STATUS_VERIFIED;
DB::statement("UPDATE domains SET status = (status | {$domain_status})"
. " WHERE namespace = 'kolab.org'");
DB::statement("UPDATE users SET status = (status | " . User::STATUS_IMAP_READY . ")"
. " WHERE email = 'john@kolab.org'");
}
/**
* {@inheritDoc}
*/
public function tearDown(): void
{
$domain_status = Domain::STATUS_CONFIRMED | Domain::STATUS_VERIFIED;
DB::statement("UPDATE domains SET status = (status | {$domain_status})"
. " WHERE namespace = 'kolab.org'");
DB::statement("UPDATE users SET status = (status | " . User::STATUS_IMAP_READY . ")"
. " WHERE email = 'john@kolab.org'");
parent::tearDown();
}
/**
* Test account status in the Dashboard
*/
public function testDashboard(): void
{
// Unconfirmed domain and user
$domain = Domain::where('namespace', 'kolab.org')->first();
if ($domain->isConfirmed()) {
$domain->status ^= Domain::STATUS_CONFIRMED;
$domain->save();
}
$john = $this->getTestUser('john@kolab.org');
$john->created_at = Carbon::now();
if ($john->isImapReady()) {
$john->status ^= User::STATUS_IMAP_READY;
}
$john->save();
$this->browse(function ($browser) use ($john, $domain) {
$browser->visit(new Home())
->submitLogon('john@kolab.org', 'simple123', true)
->on(new Dashboard())
->with(new Status(), function ($browser) use ($john) {
$browser->assertSeeIn('@body', 'We are preparing your account')
- ->assertProgress(28, 'Creating a mailbox...', 'pending')
+ ->assertProgress(71, 'Creating a mailbox...', 'pending')
->assertMissing('#status-verify')
->assertMissing('#status-link')
->assertMissing('@refresh-button')
->assertMissing('@refresh-text');
$john->status |= User::STATUS_IMAP_READY;
$john->save();
// Wait for auto-refresh, expect domain-confirmed step
$browser->pause(6000)
->assertSeeIn('@body', 'Your account is almost ready')
->assertProgress(85, 'Verifying an ownership of a custom domain...', 'failed')
->assertMissing('@refresh-button')
->assertMissing('@refresh-text')
->assertMissing('#status-verify')
->assertVisible('#status-link');
})
// check if the link to domain info page works
->click('#status-link')
->on(new DomainInfo())
->back()
->on(new Dashboard())
->with(new Status(), function ($browser) {
$browser->assertMissing('@refresh-button')
->assertProgress(85, 'Verifying an ownership of a custom domain...', 'failed');
});
// Confirm the domain and wait until the whole status box disappears
$domain->status |= Domain::STATUS_CONFIRMED;
$domain->save();
// This should take less than 10 seconds
$browser->waitUntilMissing('@status', 10);
});
// Test the Refresh button
if ($domain->isConfirmed()) {
$domain->status ^= Domain::STATUS_CONFIRMED;
$domain->save();
}
$john->created_at = Carbon::now()->subSeconds(3600);
if ($john->isImapReady()) {
$john->status ^= User::STATUS_IMAP_READY;
}
$john->save();
$this->browse(function ($browser) use ($john, $domain) {
$browser->visit(new Dashboard())
->with(new Status(), function ($browser) use ($john, $domain) {
$browser->assertSeeIn('@body', 'We are preparing your account')
- ->assertProgress(28, 'Creating a mailbox...', 'failed')
+ ->assertProgress(71, 'Creating a mailbox...', 'failed')
->assertVisible('@refresh-button')
->assertVisible('@refresh-text');
if ($john->refresh()->isImapReady()) {
$john->status ^= User::STATUS_IMAP_READY;
$john->save();
}
$domain->status |= Domain::STATUS_CONFIRMED;
$domain->save();
$browser->click('@refresh-button')
->assertToast(Toast::TYPE_SUCCESS, 'Setup process finished successfully.');
})
->assertMissing('@status');
});
}
/**
* Test domain status on domains list and domain info page
*
* @depends testDashboard
*/
public function testDomainStatus(): void
{
$domain = Domain::where('namespace', 'kolab.org')->first();
$domain->created_at = Carbon::now();
$domain->status = Domain::STATUS_NEW | Domain::STATUS_ACTIVE | Domain::STATUS_LDAP_READY;
$domain->save();
$this->browse(function ($browser) use ($domain) {
// Test auto-refresh
$browser->on(new Dashboard())
->click('@links a.link-domains')
->on(new DomainList())
->waitFor('@table tbody tr')
// Assert domain status icon
->assertVisible('@table tbody tr:first-child td:first-child svg.fa-globe.text-danger')
->assertText('@table tbody tr:first-child td:first-child svg title', 'Not Ready')
->click('@table tbody tr:first-child td:first-child a')
->on(new DomainInfo())
->with(new Status(), function ($browser) {
$browser->assertSeeIn('@body', 'We are preparing the domain')
->assertProgress(50, 'Verifying a custom domain...', 'pending')
->assertMissing('@refresh-button')
->assertMissing('@refresh-text')
->assertMissing('#status-link')
->assertMissing('#status-verify');
});
$domain->status |= Domain::STATUS_VERIFIED;
$domain->save();
// This should take less than 10 seconds
$browser->waitFor('@status.process-failed')
->with(new Status(), function ($browser) {
$browser->assertSeeIn('@body', 'The domain is almost ready')
->assertProgress(75, 'Verifying an ownership of a custom domain...', 'failed')
->assertMissing('@refresh-button')
->assertMissing('@refresh-text')
->assertMissing('#status-link')
->assertVisible('#status-verify');
});
$domain->status |= Domain::STATUS_CONFIRMED;
$domain->save();
// Test Verify button
$browser->click('@status #status-verify')
->assertToast(Toast::TYPE_SUCCESS, 'Domain verified successfully.')
->waitUntilMissing('@status')
->assertMissing('@verify')
->assertVisible('@config');
});
}
/**
* Test user status on users list and user info page
*
* @depends testDashboard
*/
public function testUserStatus(): void
{
$john = $this->getTestUser('john@kolab.org');
$john->created_at = Carbon::now();
if ($john->isImapReady()) {
$john->status ^= User::STATUS_IMAP_READY;
}
$john->save();
$domain = Domain::where('namespace', 'kolab.org')->first();
if ($domain->isConfirmed()) {
$domain->status ^= Domain::STATUS_CONFIRMED;
$domain->save();
}
$this->browse(function ($browser) use ($john, $domain) {
$browser->visit(new Dashboard())
->click('@links a.link-users')
->on(new UserList())
->waitFor('@table tbody tr')
// Assert user status icons
->assertVisible('@table tbody tr:first-child td:first-child svg.fa-user.text-success')
->assertText('@table tbody tr:first-child td:first-child svg title', 'Active')
->assertVisible('@table tbody tr:nth-child(3) td:first-child svg.fa-user.text-danger')
->assertText('@table tbody tr:nth-child(3) td:first-child svg title', 'Not Ready')
->click('@table tbody tr:nth-child(3) td:first-child a')
->on(new UserInfo())
->with('@form', function (Browser $browser) {
// Assert state in the user edit form
$browser->assertSeeIn('div.row:nth-child(1) label', 'Status')
->assertSeeIn('div.row:nth-child(1) #status', 'Not Ready');
})
->with(new Status(), function ($browser) use ($john) {
$browser->assertSeeIn('@body', 'We are preparing the user account')
- ->assertProgress(28, 'Creating a mailbox...', 'pending')
+ ->assertProgress(71, 'Creating a mailbox...', 'pending')
->assertMissing('#status-verify')
->assertMissing('#status-link')
->assertMissing('@refresh-button')
->assertMissing('@refresh-text');
$john->status |= User::STATUS_IMAP_READY;
$john->save();
// Wait for auto-refresh, expect domain-confirmed step
$browser->pause(6000)
->assertSeeIn('@body', 'The user account is almost ready')
->assertProgress(85, 'Verifying an ownership of a custom domain...', 'failed')
->assertMissing('@refresh-button')
->assertMissing('@refresh-text')
->assertMissing('#status-verify')
->assertVisible('#status-link');
})
->assertSeeIn('#status', 'Active');
// Confirm the domain and wait until the whole status box disappears
$domain->status |= Domain::STATUS_CONFIRMED;
$domain->save();
// This should take less than 10 seconds
$browser->waitUntilMissing('@status', 10);
});
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Apr 5, 9:45 PM (3 w, 10 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18831296
Default Alt Text
(19 KB)
Attached To
Mode
rK kolab
Attached
Detach File
Event Timeline