Improved import summary dialog

This commit is contained in:
hay-kot 2021-02-20 11:48:15 -09:00
commit 4de7bd8016
7 changed files with 281 additions and 22 deletions

View file

@ -8,9 +8,9 @@ const backupURLs = {
// Backup // Backup
available: `${backupBase}available`, available: `${backupBase}available`,
createBackup: `${backupBase}export/database`, createBackup: `${backupBase}export/database`,
importBackup: (fileName) => `${backupBase}${fileName}/import`, importBackup: fileName => `${backupBase}${fileName}/import`,
deleteBackup: (fileName) => `${backupBase}${fileName}/delete`, deleteBackup: fileName => `${backupBase}${fileName}/delete`,
downloadBackup: (fileName) => `${backupBase}${fileName}/download`, downloadBackup: fileName => `${backupBase}${fileName}/download`,
}; };
export default { export default {

View file

@ -69,10 +69,9 @@ export default {
this.$emit("loading"); this.$emit("loading");
let response = await api.backups.import(data.name, data); let response = await api.backups.import(data.name, data);
let failed = response.data.failed; let importData = response.data;
let succesful = response.data.successful;
this.$emit("finished", succesful, failed); this.$emit("finished", importData);
}, },
deleteBackup(data) { deleteBackup(data) {
this.$emit("loading"); this.$emit("loading");

View file

@ -0,0 +1,47 @@
<template>
<div>
<v-data-table
dense
:headers="dataHeaders"
:items="dataSet"
item-key="name"
class="elevation-1 mt-2"
show-expand
:expanded.sync="expanded"
:footer-props="{
'items-per-page-options': [100, 200, 300, 400, -1],
}"
:items-per-page="100"
>
<template v-slot:item.status="{ item }">
<div :class="item.status ? 'success--text' : 'error--text'">
{{ item.status ? "Imported" : "Failed" }}
</div>
</template>
<template v-slot:expanded-item="{ headers, item }">
<td :colspan="headers.length">
<div class="ma-2">
{{ item.exception }}
</div>
</td>
</template>
</v-data-table>
</div>
</template>
<script>
export default {
props: {
dataSet: Array,
dataHeaders: Array,
},
data: () => ({
singleExpand: false,
expanded: [],
}),
};
</script>
<style>
</style>

View file

@ -0,0 +1,152 @@
<template>
<div class="text-center">
<v-dialog v-model="dialog" width="70%">
<v-card>
<v-card-title> Import Summary </v-card-title>
<v-card-text>
<v-row class="mb-n9">
<v-card flat>
<v-card-text>
<div>
<h3>Recipes</h3>
</div>
<div class="success--text">
Success: {{ recipeNumbers.success }}
</div>
<div class="error--text">
Failed: {{ recipeNumbers.failure }}
</div>
</v-card-text>
</v-card>
<v-card flat>
<v-card-text>
<div>
<h3>Themes</h3>
</div>
<div class="success--text">
Success: {{ themeNumbers.success }}
</div>
<div class="error--text">
Failed: {{ themeNumbers.failure }}
</div>
</v-card-text>
</v-card>
<v-card flat>
<v-card-text>
<div>
<h3>Settings</h3>
</div>
<div class="success--text">
Success: {{ settingsNumbers.success }}
</div>
<div class="error--text">
Failed: {{ settingsNumbers.failure }}
</div>
</v-card-text>
</v-card>
</v-row>
</v-card-text>
<v-tabs v-model="tab">
<v-tab>Recipes</v-tab>
<v-tab>Themes</v-tab>
<v-tab>Settings</v-tab>
</v-tabs>
<v-tabs-items v-model="tab">
<v-tab-item>
<v-card flat>
<DataTable :data-headers="recipeHeaders" :data-set="recipeData" />
</v-card>
</v-tab-item>
<v-tab-item>
<v-card>
<DataTable
:data-headers="recipeHeaders"
:data-set="themeData"
/> </v-card
></v-tab-item>
<v-tab-item>
<v-card
><DataTable
:data-headers="recipeHeaders"
:data-set="settingsData"
/>
</v-card>
</v-tab-item>
</v-tabs-items>
</v-card>
</v-dialog>
</div>
</template>
<script>
import DataTable from "./DataTable";
export default {
components: {
DataTable,
},
data: () => ({
tab: null,
dialog: false,
recipeData: [],
themeData: [],
settingsData: [],
recipeHeaders: [
{
text: "Status",
value: "status",
},
{
text: "Name",
align: "start",
sortable: true,
value: "name",
},
{ text: "Exception", value: "data-table-expand", align: "center" },
],
allDataTables: [],
}),
computed: {
recipeNumbers() {
let numbers = { success: 0, failure: 0 };
this.recipeData.forEach(element => {
if (element.status) {
numbers.success++;
} else numbers.failure++;
});
return numbers;
},
settingsNumbers() {
let numbers = { success: 0, failure: 0 };
this.settingsData.forEach(element => {
if (element.status) {
numbers.success++;
} else numbers.failure++;
});
return numbers;
},
themeNumbers() {
let numbers = { success: 0, failure: 0 };
this.themeData.forEach(element => {
if (element.status) {
numbers.success++;
} else numbers.failure++;
});
return numbers;
},
},
methods: {
open(importData) {
this.recipeData = importData.recipeImports;
this.themeData = importData.themeReport;
this.settingsData = importData.settingsReport;
this.dialog = true;
},
},
};
</script>
<style>
</style>

View file

@ -41,6 +41,7 @@
:failed-header="$t('settings.backup.failed-imports')" :failed-header="$t('settings.backup.failed-imports')"
:failed="failedImports" :failed="failedImports"
/> />
<ImportSummaryDialog ref="report" :import-data="importData" />
</v-card-text> </v-card-text>
</v-card> </v-card>
</template> </template>
@ -48,6 +49,7 @@
<script> <script>
import api from "@/api"; import api from "@/api";
import SuccessFailureAlert from "../../UI/SuccessFailureAlert"; import SuccessFailureAlert from "../../UI/SuccessFailureAlert";
import ImportSummaryDialog from "./ImportSummaryDialog";
import UploadBtn from "../../UI/UploadBtn"; import UploadBtn from "../../UI/UploadBtn";
import AvailableBackupCard from "./AvailableBackupCard"; import AvailableBackupCard from "./AvailableBackupCard";
import NewBackupCard from "./NewBackupCard"; import NewBackupCard from "./NewBackupCard";
@ -58,6 +60,7 @@ export default {
UploadBtn, UploadBtn,
AvailableBackupCard, AvailableBackupCard,
NewBackupCard, NewBackupCard,
ImportSummaryDialog,
}, },
data() { data() {
return { return {
@ -65,6 +68,7 @@ export default {
successfulImports: [], successfulImports: [],
backupLoading: false, backupLoading: false,
availableBackups: [], availableBackups: [],
importData: [],
}; };
}, },
mounted() { mounted() {
@ -87,12 +91,10 @@ export default {
this.backupLoading = false; this.backupLoading = false;
} }
}, },
processFinished(successful = null, failed = null) { processFinished(data) {
this.getAvailableBackups(); this.getAvailableBackups();
this.backupLoading = false; this.backupLoading = false;
this.successfulImports = successful; this.$refs.report.open(data);
this.failedImports = failed;
this.$refs.report.open();
}, },
}, },
}; };

View file

@ -0,0 +1,20 @@
from typing import Optional
from pydantic.main import BaseModel
class RecipeImport(BaseModel):
name: Optional[str]
slug: str
status: bool
exception: Optional[str]
class ThemeImport(BaseModel):
name: str
status: bool
exception: Optional[str]
class SettingsImport(BaseModel):
name: str
status: bool
exception: Optional[str]

View file

@ -1,12 +1,13 @@
import json import json
import shutil import shutil
import zipfile import zipfile
from logging import error from logging import error, exception
from pathlib import Path from pathlib import Path
from typing import List from typing import List
from app_config import BACKUP_DIR, IMG_DIR, TEMP_DIR from app_config import BACKUP_DIR, IMG_DIR, TEMP_DIR
from db.database import db from db.database import db
from models.import_models import RecipeImport, SettingsImport, ThemeImport
from models.theme_models import SiteTheme from models.theme_models import SiteTheme
from services.recipe_services import Recipe from services.recipe_services import Recipe
from services.settings_services import SiteSettings from services.settings_services import SiteSettings
@ -57,23 +58,29 @@ class ImportDatabase:
raise Exception("Import file does not exist") raise Exception("Import file does not exist")
def run(self): def run(self):
report = {} recipe_report = []
settings_report = []
theme_report = []
if self.imp_recipes: if self.imp_recipes:
report = self.import_recipes() recipe_report = self.import_recipes()
if self.imp_settings: if self.imp_settings:
self.import_settings() settings_report = self.import_settings()
if self.imp_themes: if self.imp_themes:
self.import_themes() theme_report = self.import_themes()
self.clean_up() self.clean_up()
return report if report else None return {
"recipeImports": recipe_report,
"settingsReport": settings_report,
"themeReport": theme_report,
}
def import_recipes(self): def import_recipes(self):
recipe_dir: Path = self.import_dir.joinpath("recipes") recipe_dir: Path = self.import_dir.joinpath("recipes")
imports = []
successful_imports = [] successful_imports = []
failed_imports = []
for recipe in recipe_dir.glob("*.json"): for recipe in recipe_dir.glob("*.json"):
with open(recipe, "r") as f: with open(recipe, "r") as f:
@ -82,16 +89,27 @@ class ImportDatabase:
try: try:
recipe_obj = Recipe(**recipe_dict) recipe_obj = Recipe(**recipe_dict)
recipe_obj.save_to_db(self.session) recipe_obj.save_to_db(self.session)
import_status = RecipeImport(
name=recipe_obj.name, slug=recipe_obj.slug, status=True
)
imports.append(import_status)
successful_imports.append(recipe.stem) successful_imports.append(recipe.stem)
logger.info(f"Imported: {recipe.stem}") logger.info(f"Imported: {recipe.stem}")
except Exception as inst: except Exception as inst:
logger.error(inst) logger.error(inst)
logger.info(f"Failed Import: {recipe.stem}") logger.info(f"Failed Import: {recipe.stem}")
failed_imports.append(recipe.stem) import_status = RecipeImport(
name=recipe.stem,
slug=recipe.stem,
status=False,
exception=str(inst),
)
imports.append(import_status)
self._import_images(successful_imports) self._import_images(successful_imports)
return {"successful": successful_imports, "failed": failed_imports} return imports
@staticmethod @staticmethod
def _recipe_migration(recipe_dict: dict) -> dict: def _recipe_migration(recipe_dict: dict) -> dict:
@ -130,7 +148,7 @@ class ImportDatabase:
def import_themes(self): def import_themes(self):
themes_file = self.import_dir.joinpath("themes", "themes.json") themes_file = self.import_dir.joinpath("themes", "themes.json")
theme_imports = []
with open(themes_file, "r") as f: with open(themes_file, "r") as f:
themes: list[dict] = json.loads(f.read()) themes: list[dict] = json.loads(f.read())
for theme in themes: for theme in themes:
@ -138,17 +156,38 @@ class ImportDatabase:
continue continue
new_theme = SiteTheme(**theme) new_theme = SiteTheme(**theme)
try: try:
db.themes.create(self.session, new_theme.dict()) db.themes.create(self.session, new_theme.dict())
except: theme_imports.append(ThemeImport(name=new_theme.name, status=True))
except Exception as inst:
logger.info(f"Unable Import Theme {new_theme.name}") logger.info(f"Unable Import Theme {new_theme.name}")
theme_imports.append(
ThemeImport(name=new_theme.name, status=False, exception=str(inst))
)
return theme_imports
def import_settings(self): def import_settings(self):
settings_file = self.import_dir.joinpath("settings", "settings.json") settings_file = self.import_dir.joinpath("settings", "settings.json")
settings_imports = []
with open(settings_file, "r") as f: with open(settings_file, "r") as f:
settings: dict = json.loads(f.read()) settings: dict = json.loads(f.read())
db.settings.update(self.session, settings.get("name"), settings) name = settings.get("name")
try:
db.settings.update(self.session, name, settings)
import_status = SettingsImport(name=name, status=True)
except Exception as inst:
import_status = SettingsImport(
name=name, status=False, exception=str(inst)
)
settings_imports.append(import_status)
return settings_imports
def clean_up(self): def clean_up(self):
shutil.rmtree(TEMP_DIR) shutil.rmtree(TEMP_DIR)