Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions js/user_migration-admin-settings.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/user_migration-admin-settings.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions js/user_migration-personal-settings.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/user_migration-personal-settings.js.map

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@

class Application extends App implements IBootstrap {
public const APP_ID = 'user_migration';
public const APP_NAME = 'User migration';
public const SETTINGS_SECTION_NAME = 'Data migration';

public function __construct() {
parent::__construct(self::APP_ID);
Expand Down
7 changes: 6 additions & 1 deletion lib/Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,16 @@ public function status(): DataResponse {

// TODO handle import job status

$statusMap = [
UserExport::STATUS_WAITING => 'waiting',
UserExport::STATUS_STARTED => 'started',
];

if (!empty($userExport)) {
return new DataResponse([
'current' => 'export',
'migrators' => $userExport->getMigratorsArray(),
'status' => $userExport->getStatus(),
'status' => $statusMap[$userExport->getStatus()],
], Http::STATUS_OK);
}

Expand Down
48 changes: 0 additions & 48 deletions lib/Controller/PageController.php

This file was deleted.

3 changes: 1 addition & 2 deletions lib/Migrator/FilesMigrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,6 @@ public function getDisplayName(): string {
* {@inheritDoc}
*/
public function getDescription(): string {
// TODO handle migrator dependency resoluton as TrashbinMigrator is dependent on FilesMigrator
return $this->l10n->t('Files including deleted files, versions, comments, collaborative tags, and favorites');
return $this->l10n->t('Files including versions, comments, collaborative tags, and favorites');
}
}
2 changes: 1 addition & 1 deletion lib/Settings/Admin/Section.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public function getID(): string {
}

public function getName(): string {
return $this->l->t(Application::SETTINGS_SECTION_NAME);
return $this->l->t('Data migration');
}

public function getPriority(): int {
Expand Down
2 changes: 1 addition & 1 deletion lib/Settings/Personal/Section.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public function getID(): string {
}

public function getName(): string {
return $this->l->t(Application::SETTINGS_SECTION_NAME);
return $this->l->t('Data migration');
}

public function getPriority(): int {
Expand Down
2 changes: 1 addition & 1 deletion lib/Settings/Personal/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,6 @@ public function getSection(): string {
}

public function getPriority(): int {
return 0;
return 100;
}
}
140 changes: 81 additions & 59 deletions src/components/ExportSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,91 +22,103 @@

<template>
<div class="section">
<h2>{{ t(APP_ID, 'Export') }}</h2>
<h2>{{ t('user_migration', 'Export') }}</h2>

<h3 class="settings-hint">{{ t(APP_ID, 'Select the data you want to export') }}</h3>
<h3 class="settings-hint">
{{ t('user_migration', 'Please select the data you want to export') }}
</h3>

<div class="section__grid">
<!-- Base user data is permanently enabled -->
<div class="section__checkbox">
<CheckboxRadioSwitch :checked="true"
:disabled="true">
User information and settings
{{ t('user_migration', 'User information and settings') }}
</CheckboxRadioSwitch>
<em class="section__description">Some descriptive text about the data to be exported. Aliquam eu sem at lacus consequat malesuada sit amet et nulla.</em>
<em class="section__description">{{ t('user_migration', 'Basic user information including user ID and display name as well as your settings') }}</em>
</div>
<div v-for="({id, displayName, description}) in sortedMigrators"
class="section__checkbox"
:key="id">
:key="id"
class="section__checkbox">
<CheckboxRadioSwitch name="migrators"
:value="id"
:checked.sync="selectedMigrators">
{{ displayName }}
</CheckboxRadioSwitch>
<em class="section__description">{{ description }}</em>
</div>
<!-- TODO since TrashbinMigrator depends on FilesMigrator server should have some sort of migrator dependency API -->
</div>

<!-- <span>Migrators: {{ selectedMigrators }}</span> -->

<Button v-if="status.current !== 'export'"
type="secondary"
:aria-label="t(APP_ID, 'Export your data')"
:aria-label="t('user_migration', 'Export your data')"
:disabled="status.current === 'import'"
@click.stop.prevent="startExport">
<template #icon>
<PackageDown title="" :size="20" />
</template>
{{ t(APP_ID, 'Export') }}
</Button>
<Button v-else
type="secondary"
:aria-label="t(APP_ID, 'Show export status')"
:disabled="status.current === 'import'"
@click.stop.prevent="openModal">
{{ t(APP_ID, 'Show status')}}
{{ t('user_migration', 'Export') }}
</Button>
<div v-else class="section__status">
<Button type="secondary"
:aria-label="t('user_migration', 'Show export status')"
:disabled="status.current === 'import'"
@click.stop.prevent="openModal">
{{ t('user_migration', 'Show status') }}
</Button>
<span class="settings-hint">{{ status.status === 'waiting' ? t('user_migration', 'Export queued') : t('user_migration', 'Export in progress…') }}</span>
</div>

<Modal v-if="modalOpened"
@close="closeModal">
<div class="section__modal">
<EmptyContent>
{{ t(APP_ID, 'Export in progress…') }}
{{ modalMessage }}
<template #icon>
<PackageDown />
<PackageDown decorative />
</template>
<template #desc>
{{ t(APP_ID, 'Please do not use your account while exporting.') }}
<template v-if="status.status === 'started'" #desc>
{{ t('user_migration', 'Please do not use your account while exporting.') }}
</template>
</EmptyContent>
<!-- TODO show list of data currently being exported -->
<!-- TODO use spinner as percentage of export complete cannot be queried from server -->
<ProgressBar size="medium"
:value="60"
:error="error" />
<div v-if="status.status === 'waiting' || status.status === 'started'"
class="section__icon icon-loading" />
<CheckCircleOutline v-else
class="section__icon"
title=""
:size="40" />
</div>
</Modal>
</div>
</template>

<script>
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
import confirmPassword from '@nextcloud/password-confirmation'
import { generateOcsUrl } from '@nextcloud/router'
import { showError } from '@nextcloud/dialogs'

import Button from '@nextcloud/vue/dist/Components/Button'
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
import CheckboxRadioSwitch from '@nextcloud/vue/dist/Components/CheckboxRadioSwitch'
import CheckCircleOutline from 'vue-material-design-icons/CheckCircleOutline'
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
import Modal from '@nextcloud/vue/dist/Components/Modal'
import PackageDown from 'vue-material-design-icons/PackageDown'
import ProgressBar from '@nextcloud/vue/dist/Components/ProgressBar'

import { APP_ID } from '../shared/constants'

export default {
name: 'ExportSection',

components: {
Button,
CheckboxRadioSwitch,
CheckCircleOutline,
EmptyContent,
Modal,
PackageDown,
},

props: {
migrators: {
type: Array,
Expand All @@ -118,43 +130,42 @@ export default {
},
},

components: {
Button,
EmptyContent,
PackageDown,
Modal,
ProgressBar,
CheckboxRadioSwitch,
},

data() {
return {
modalOpened: false,
selectedMigrators: ['settings'],
error: false,
APP_ID,
selectedMigrators: [],
}
},

computed: {
modalMessage() {
if (this.status.status === 'waiting') {
return t('user_migration', 'Export queued')
} else if (this.status.status === 'started') {
return t('user_migration', 'Export in progress…')
}
return t('user_migration', 'Export completed successfully')
},

sortedMigrators() {
// TODO sort migrators?
return this.migrators
}
},
},

methods: {
async startExport() {
try {
await confirmPassword()
await axios.post(
generateOcsUrl('/apps/{appId}/api/v1/export', { appId: APP_ID }),
{ migrators: this.selectedMigrators },
)
await axios.post(generateOcsUrl('/apps/{appId}/api/v1/export', { appId: APP_ID }), {
migrators: this.selectedMigrators,
})
this.$emit('refresh-status')
this.openModal()
} catch (error) {
this.logger.error(`Error starting user export: ${error.message || 'Unknown error'}`, { error })
// TODO show error message in a dialog
const errorMessage = error.message || 'Unknown error'
this.logger.error(`Error starting user export: ${errorMessage}`, { error })
showError(errorMessage)
}
},

Expand All @@ -172,9 +183,9 @@ export default {
<style lang="scss" scoped>
.section__grid {
display: grid;
gap: 10px 20px;
gap: 40px;
grid-auto-flow: row;
grid-template-columns: repeat(auto-fit, 400px);
grid-template-columns: repeat(auto-fit, minmax(320px, 400px));
margin-bottom: 40px;

.section__description {
Expand All @@ -184,14 +195,25 @@ export default {
}
}

.section__modal {
align-self: center;
margin: 20px auto;
width: 80%;
height: 80%;
.section__status {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 0 20px;

.settings-hint {
margin: auto 0;
}
}

.section__modal {
margin: 110px auto;

&::v-deep .empty-content {
margin-top: 0;
}

.section__icon {
height: 40px;
margin-top: 20px;
}
}
</style>
Loading