Skip to content

Commit

Permalink
feat(#117): import and restore locale backup dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
lukashornych committed Oct 16, 2024
1 parent d582f30 commit 113e466
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 85 deletions.
5 changes: 5 additions & 0 deletions src/modules/backup-viewer/components/BackupViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { restoreTaskName } from '@/modules/backup-viewer/model/RestoreTask'
import TaskList from '@/modules/task-viewer/components/TaskList.vue'
import BackupList from '@/modules/backup-viewer/components/BackupList.vue'
import BackupCatalogButton from '@/modules/backup-viewer/components/BackupCatalogButton.vue'
import RestoreLocalBackupFileButton from '@/modules/backup-viewer/components/RestoreLocalBackupFileButton.vue'
const shownTaskStates: TaskState[] = [TaskState.Running, TaskState.Queued, TaskState.Failed]
const shownTaskTypes: string[] = [backupTaskName, restoreTaskName]
Expand Down Expand Up @@ -51,6 +52,10 @@ emit('ready')
{{ t('backupViewer.button.reloadBackups') }}
</VTooltip>
</VBtn>
<RestoreLocalBackupFileButton
:connection="params.connection"
@restore="reloadBackups"
/>
<BackupCatalogButton
:connection="params.connection"
@backup="reloadBackups"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ServerFile } from '@/modules/connection/model/server-file/ServerFile'
import RestoreBackupDialog from '@/modules/backup-viewer/components/RestoreBackupDialog.vue'
import RestoreBackupFileDialog from '@/modules/backup-viewer/components/RestoreBackupFileDialog.vue'
import { ref } from 'vue'
import { Connection } from '@/modules/connection/model/Connection'
import { useI18n } from 'vue-i18n'
Expand All @@ -20,7 +20,7 @@ const showRestoreDialog = ref<boolean>(false)
</script>

<template>
<RestoreBackupDialog
<RestoreBackupFileDialog
v-model="showRestoreDialog"
:connection="connection"
:backup-file="backupFile"
Expand All @@ -39,7 +39,7 @@ const showRestoreDialog = ref<boolean>(false)
</VTooltip>
</VBtn>
</template>
</RestoreBackupDialog>
</RestoreBackupFileDialog>
</template>

<style lang="scss" scoped>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function reset(): void {
async function restore(): Promise<boolean> {
try {
await backupViewerService.restoreCatalog(
await backupViewerService.restoreBackupFile(
props.connection,
props.backupFile.fileId,
catalogName.value
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script setup lang="ts">
import RestoreLocalBackupFileDialog from '@/modules/backup-viewer/components/RestoreLocalBackupFileDialog.vue'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { Connection } from '@/modules/connection/model/Connection'
const { t } = useI18n()
const props = defineProps<{
connection: Connection
}>()
const showRestoreDialog = ref<boolean>(false)
</script>

<template>
<RestoreLocalBackupFileDialog
v-model="showRestoreDialog"
:connection="connection"
>
<template #activator="{ props }">
<VBtn
icon
@click="showRestoreDialog = true"
v-bind="props"
>
<VIcon>mdi-cloud-upload-outline</VIcon>

<VTooltip activator="parent">
{{ t('backupViewer.button.restoreLocalBackup') }}
</VTooltip>
</VBtn>
</template>
</RestoreLocalBackupFileDialog>
</template>

<style lang="scss" scoped>
</style>
134 changes: 134 additions & 0 deletions src/modules/backup-viewer/components/RestoreLocalBackupFileDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<script setup lang="ts">
import VFormDialog from '@/modules/base/component/VFormDialog.vue'
import { BackupViewerService, useBackupViewerService } from '@/modules/backup-viewer/service/BackupViewerService'
import { Connection } from '@/modules/connection/model/Connection'
import { useI18n } from 'vue-i18n'
import { computed, ref } from 'vue'
import { ClassifierValidationErrorType } from '@/modules/connection/model/data-type/ClassifierValidationErrorType'
import { Toaster, useToaster } from '@/modules/notification/service/Toaster'
const backupViewerService: BackupViewerService = useBackupViewerService()
const toaster: Toaster = useToaster()
const { t } = useI18n()
const props = defineProps<{
modelValue: boolean
connection: Connection
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void,
(e: 'restore'): void
}>()
const backupFileRules = [
(value: File): any => {
if (value != undefined) {
return true
}
return t('backupViewer.restoreLocal.form.backupFile.validations.required')
}
]
const catalogNameRules = [
(value: string): any => {
if (value != undefined && value.trim().length > 0) return true
return t('backupViewer.restoreLocal.form.catalogName.validations.required')
},
async (value: string): Promise<any> => {
const classifierValidationResult : ClassifierValidationErrorType | undefined =
await backupViewerService.isCatalogNameValid(props.connection, value)
if (classifierValidationResult == undefined) return true
return t(`backupViewer.restoreLocal.form.catalogName.validations.${classifierValidationResult}`)
},
async (value: string): Promise<any> => {
const available: boolean = await backupViewerService.isCatalogNameAvailable(props.connection, value)
if (available) return true
return t('backupViewer.restoreLocal.form.catalogName.validations.notAvailable')
}
]
const backupFile = ref<File>()
const catalogName = ref<string>('')
const changed = computed<boolean>(() => {
return catalogName.value != undefined &&
catalogName.value.length > 0 &&
backupFile.value != undefined
})
function reset(): void {
backupFile.value = undefined
catalogName.value = ''
}
async function restoreLocal(): Promise<boolean> {
try {
await backupViewerService.restoreLocalBackupFile(
props.connection,
backupFile.value!,
catalogName.value
)
toaster.success(t('backupViewer.restoreLocal.notification.restoreRequested'))
emit('restore')
return true
} catch (e: any) {
toaster.error(t(
'backupViewer.restoreLocal.notification.couldNotRestoreBackupFile',
{ reason: e.message }
))
return false
}
}
</script>

<template>
<VFormDialog
:model-value="modelValue"
:changed="changed"
confirm-button-icon="mdi-cloud-upload-outline"
:confirm="restoreLocal"
:reset="reset"
@update:model-value="emit('update:modelValue', $event)"
>
<template #activator="{ props }">
<slot name="activator" v-bind="{ props }" />
</template>

<template #title>
{{ t('backupViewer.restoreLocal.title') }}
</template>

<template #prepend-form>
{{ t('backupViewer.restoreLocal.description') }}
</template>

<template #default>
<VFileInput
v-model="backupFile"
:label="t('backupViewer.restoreLocal.form.backupFile.label')"
:rules="backupFileRules"
required
clearable
/>
<VTextField
v-model="catalogName"
:label="t('backupViewer.restoreLocal.form.catalogName.label')"
:rules="catalogNameRules"
required
/>
</template>

<template #append-form>
<VAlert icon="mdi-information-outline" type="info">
{{ t('backupViewer.restoreLocal.info') }}
</VAlert>
</template>

<template #confirm-button-body>
{{ t('backupViewer.restoreLocal.button.restore') }}
</template>
</VFormDialog>
</template>

<style lang="scss" scoped>
</style>
77 changes: 6 additions & 71 deletions src/modules/backup-viewer/service/BackupViewerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ import { OffsetDateTime } from '@/modules/connection/model/data-type/OffsetDateT
import { Uuid } from '@/modules/connection/model/data-type/Uuid'
import { mandatoryInject } from '@/utils/reactivity'
import { InjectionKey } from 'vue'
import {
GrpcRestoreCatalogRequest,
GrpcRestoreCatalogResponse
} from '@/modules/connection/driver/grpc/gen/GrpcEvitaManagementAPI_pb'
import { ConnectionService } from '@/modules/connection/service/ConnectionService'
import { CatalogVersionAtResponse } from '@/modules/connection/model/CatalogVersionAtResponse'
import { TaskStatus } from '@/modules/connection/model/task/TaskStatus'
Expand Down Expand Up @@ -75,18 +71,13 @@ export class BackupViewerService {
return await driver.getFilesToFetch(connection, backupTaskName, pageNumber, pageSize)
}

async restoreCatalog(
async restoreBackupFile(
connection: Connection,
fileId: Uuid,
catalogName: string
): Promise<TaskStatus> {
const driver = await this.connectionService.getDriver(connection)
return await driver.restoreCatalog(connection, fileId, catalogName)
}

async uploadBackup(connection: Connection, stream: AsyncIterable<GrpcRestoreCatalogRequest>): Promise<GrpcRestoreCatalogResponse>{
const driver = await this.connectionService.getDriver(connection)
return await driver.uploadFile(connection, stream)
return await driver.restoreCatalogFromServerFile(connection, fileId, catalogName)
}

async isCatalogNameValid(connection: Connection, catalogName: string): Promise<ClassifierValidationErrorType | undefined> {
Expand All @@ -108,66 +99,10 @@ export class BackupViewerService {
return false
}


// todo lho prototype for local file restore
// async function upload(){
// await backupViewerService.uploadBackup(props.params.connection, sendCatalogChunks())
// }
//
// async function* sendCatalogChunks(): AsyncIterable<GrpcRestoreCatalogRequest> {
// if (!file.value) {
// return;
// }
//
// const CHUNK_SIZE = 64 * 1024; // 64 KB chunks
// const fileReader = new FileReader();
// let offset = 0;
// const totalSize = file.value.size;
//
// // Helper function to read a chunk
// function readChunk() {
// if (offset >= totalSize) {
// fileReader.abort();
// return;
// }
// const chunk = file.value!.slice(offset, offset + CHUNK_SIZE);
// fileReader.readAsArrayBuffer(chunk);
// }
//
// // Promise to handle the load of one chunk
// const onLoadPromise = () => {
// return new Promise<Uint8Array>((resolve, reject) => {
// fileReader.onload = (event: ProgressEvent<FileReader>) => {
// if (event.target?.result) {
// const arrayBuffer = event.target.result as ArrayBuffer;
// const fileChunk = new Uint8Array(arrayBuffer);
// offset += CHUNK_SIZE;
// resolve(fileChunk);
// }
// };
//
// fileReader.onerror = () => {
// console.error('Error reading file.');
// fileReader.abort();
// reject(new Error('Error reading file'));
// };
// });
// };
//
// try {
// while (offset < totalSize) {
// readChunk();
// const chunk = await onLoadPromise();
// yield new GrpcRestoreCatalogRequest({
// catalogName: 'random',
// backupFile: chunk
// }); // Yield the chunk here
// }
// } catch (error) {
// console.error('Error during file upload:', error);
// }
// }

async restoreLocalBackupFile(connection: Connection, file: Blob, catalogName: string): Promise<TaskStatus> {
const driver = await this.connectionService.getDriver(connection)
return await driver.restoreCatalog(connection, file, catalogName)
}
}

export const useBackupViewerService = (): BackupViewerService => {
Expand Down
4 changes: 2 additions & 2 deletions src/modules/connection/driver/EvitaDBDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,13 @@ export interface EvitaDBDriver {
taskTypes?: string[]
): Promise<PaginatedList<TaskStatus>>
//TODO: Add doc
restoreCatalog(connection: Connection, fileId: Uuid, catalogName: string): Promise<TaskStatus>
restoreCatalogFromServerFile(connection: Connection, fileId: Uuid, catalogName: string): Promise<TaskStatus>
//TODO: Add doc
cancelTask(connection: Connection, taskId: Uuid): Promise<boolean>
//TODO: Add doc
downloadFile(connection:Connection, fileId: Uuid):Promise<Blob>
//TODO: Add doc
uploadFile(connection: Connection, stream: AsyncIterable<GrpcRestoreCatalogRequest>):Promise<GrpcRestoreCatalogResponse>
restoreCatalog(connection: Connection, file: Blob, catalogName: string): Promise<TaskStatus>
deleteFile(connection: Connection, fileId: Uuid): Promise<boolean>
//TODO: Add doc
downloadRecordingEventTypes(connection: Connection):Promise<EventType[]>
Expand Down
Loading

0 comments on commit 113e466

Please sign in to comment.