1
1
mirror of https://github.com/Fabio286/antares.git synced 2025-06-05 21:59:22 +02:00

feat: fake table data generator

This commit is contained in:
2021-02-13 18:45:16 +01:00
parent 0f69d1dbb7
commit a176174b8d
10 changed files with 973 additions and 6 deletions

View File

@ -0,0 +1,205 @@
<template>
<fieldset class="input-group mb-0" :disabled="!fakerGroups.length">
<select
v-model="selectedGroup"
class="form-select"
:disabled="!isChecked"
style="flex-grow: 0;"
@change="onChange"
>
<option value="manual">
{{ $t('message.manualValue') }}
</option>
<option
v-for="group in fakerGroups"
:key="group.name"
:value="group.name"
>
{{ $t(`faker.${group.name}`) }}
</option>
</select>
<select
v-if="selectedGroup !== 'manual'"
v-model="selectedMethod"
class="form-select"
:disabled="!isChecked"
@change="onChange"
>
<option
v-for="method in fakerMethods"
:key="method.name"
:value="method.name"
>
{{ $t(`faker.${method.name}`) }}
</option>
</select>
<ForeignKeySelect
v-else-if="foreignKeys.includes(field.name)"
ref="formInput"
class="form-select"
:value.sync="selectedValue"
:key-usage="getKeyUsage(field.name)"
:disabled="!isChecked"
/>
<input
v-else-if="inputProps().mask"
ref="formInput"
v-model="selectedValue"
v-mask="inputProps().mask"
class="form-input"
:type="inputProps().type"
:disabled="!isChecked"
>
<input
v-else-if="inputProps().type === 'file'"
ref="formInput"
class="form-input"
type="file"
:disabled="!isChecked"
>
<input
v-else-if="inputProps().type === 'number'"
ref="formInput"
v-model="selectedValue"
class="form-input"
step="any"
:type="inputProps().type"
:disabled="!isChecked"
>
<input
v-else
ref="formInput"
v-model="selectedValue"
class="form-input"
:type="inputProps().type"
:disabled="!isChecked"
>
<slot />
</fieldset>
</template>
<script>
import { mask } from 'vue-the-mask';
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
import ForeignKeySelect from '@/components/ForeignKeySelect';
import FakerMethods from 'common/FakerMethods';
export default {
name: 'FakerSelect',
components: {
ForeignKeySelect
},
directives: {
mask
},
props: {
type: String,
field: Object,
isChecked: Boolean,
foreignKeys: Array,
keyUsage: Array,
fieldLength: Number,
fieldObj: Object
},
data () {
return {
localType: null,
selectedGroup: 'manual',
selectedMethod: '',
selectedValue: '',
debounceTimeout: null
};
},
computed: {
fakerGroups () {
if ([...TEXT, ...LONG_TEXT].includes(this.type))
this.localType = 'string';
else if (NUMBER.includes(this.type))
this.localType = 'number';
else if (FLOAT.includes(this.type))
this.localType = 'float';
else if ([...DATE, ...DATETIME].includes(this.type))
this.localType = 'datetime';
else if (TIME.includes(this.type))
this.localType = 'time';
return FakerMethods.getGroupsByType(this.localType);
},
fakerMethods () {
return FakerMethods.getMethods({ type: this.localType, group: this.selectedGroup });
}
},
watch: {
fieldObj () {
if (this.fieldObj)
this.selectedValue = this.fieldObj.value;
},
selectedGroup () {
if (this.fakerMethods.length)
this.selectedMethod = this.fakerMethods[0].name;
else
this.selectedMethod = '';
},
selectedMethod () {
this.onChange();
},
selectedValue () {
clearTimeout(this.debounceTimeout);
this.debounceTimeout = null;
this.debounceTimeout = setTimeout(() => {
this.onChange();
}, 200);
}
},
methods: {
inputProps () {
if ([...TEXT, ...LONG_TEXT].includes(this.type))
return { type: 'text', mask: false };
if ([...NUMBER, ...FLOAT].includes(this.type))
return { type: 'number', mask: false };
if (TIME.includes(this.type)) {
let timeMask = '##:##:##';
const precision = this.fieldLength;
for (let i = 0; i < precision; i++)
timeMask += i === 0 ? '.#' : '#';
return { type: 'text', mask: timeMask };
}
if (DATE.includes(this.type))
return { type: 'text', mask: '####-##-##' };
if (DATETIME.includes(this.type)) {
let datetimeMask = '####-##-## ##:##:##';
const precision = this.fieldLength;
for (let i = 0; i < precision; i++)
datetimeMask += i === 0 ? '.#' : '#';
return { type: 'text', mask: datetimeMask };
}
if (BLOB.includes(this.type))
return { type: 'file', mask: false };
if (BIT.includes(this.type))
return { type: 'text', mask: false };
return { type: 'text', mask: false };
},
getKeyUsage (keyName) {
return this.keyUsage.find(key => key.field === keyName);
},
onChange () {
this.$emit('update:value', {
group: this.selectedGroup,
method: this.selectedMethod,
value: this.selectedValue
});
}
}
};
</script>

View File

@ -0,0 +1,278 @@
<template>
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-drama-masks mr-1" /> {{ $t('message.addFakeData') }}
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<fieldset :disabled="isInserting">
<div
v-for="field in fields"
:key="field.name"
class="form-group"
>
<div class="col-3 col-sm-12">
<label class="form-label" :title="field.name">{{ field.name }}</label>
</div>
<div class="column columns col-sm-12">
<FakerSelect
:type="field.type"
class="column columns pr-0"
:is-checked="!fieldsToExclude.includes(field.name)"
:foreign-keys="foreignKeys"
:key-usage="keyUsage"
:field="field"
:field-length="fieldLength(field)"
:field-obj="localRow[field.name]"
:value.sync="localRow[field.name]"
>
<span class="input-group-addon text-small" :class="`type-${field.type.toLowerCase()}`">
{{ field.type }} {{ fieldLength(field) | wrapNumber }}
</span>
<label class="form-checkbox ml-3" :title="$t('word.insert')">
<input
type="checkbox"
:checked="!field.autoIncrement"
@change.prevent="toggleFields($event, field)"
><i class="form-icon" />
</label>
</FakerSelect>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div class="modal-footer text-light">
<div class="input-group col-2 tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')">
<input
v-model="nInserts"
type="number"
class="form-input"
min="1"
:disabled="isInserting"
>
<span class="input-group-addon">
<i class="mdi mdi-24px mdi-repeat" />
</span>
</div>
<div class="column col-auto">
<button
class="btn btn-primary mr-2"
:class="{'loading': isInserting}"
@click.stop="insertRows"
>
{{ $t('word.insert') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }}
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import moment from 'moment';
import { TEXT, LONG_TEXT, NUMBER, FLOAT, DATE, TIME, DATETIME, BLOB } from 'common/fieldTypes';
import { mask } from 'vue-the-mask';
import { mapGetters, mapActions } from 'vuex';
import Tables from '@/ipc-api/Tables';
import FakerSelect from '@/components/FakerSelect';
export default {
name: 'ModalFakerRows',
components: {
FakerSelect
},
directives: {
mask
},
filters: {
wrapNumber (num) {
if (!num) return '';
return `(${num})`;
}
},
props: {
tabUid: [String, Number],
fields: Array,
keyUsage: Array
},
data () {
return {
localRow: {},
fieldsToExclude: [],
nInserts: 1,
isInserting: false
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace',
getWorkspaceTab: 'workspaces/getWorkspaceTab'
}),
workspace () {
return this.getWorkspace(this.selectedWorkspace);
},
foreignKeys () {
return this.keyUsage.map(key => key.field);
}
},
watch: {
nInserts (val) {
if (!val || val < 1)
this.nInserts = 1;
else if (val > 1000)
this.nInserts = 1000;
}
},
created () {
window.addEventListener('keydown', this.onKey);
},
mounted () {
const rowObj = {};
for (const field of this.fields) {
let fieldDefault;
if (field.default === 'NULL') fieldDefault = null;
else {
if ([...NUMBER, ...FLOAT].includes(field.type))
fieldDefault = +field.default;
if ([...TEXT, ...LONG_TEXT].includes(field.type))
fieldDefault = field.default ? field.default.substring(1, field.default.length - 1) : '';
if ([...TIME, ...DATE].includes(field.type))
fieldDefault = field.default;
if (DATETIME.includes(field.type)) {
if (field.default && field.default.toLowerCase().includes('current_timestamp')) {
let datePrecision = '';
for (let i = 0; i < field.datePrecision; i++)
datePrecision += i === 0 ? '.S' : 'S';
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
}
}
}
rowObj[field.name] = { value: fieldDefault };
if (field.autoIncrement)// Disable by default auto increment fields
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
}
this.localRow = { ...rowObj };
},
beforeDestroy () {
window.removeEventListener('keydown', this.onKey);
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
}),
async insertRows () {
this.isInserting = true;
const rowToInsert = this.localRow;
Object.keys(rowToInsert).forEach(key => {
if (this.fieldsToExclude.includes(key))
delete rowToInsert[key];
if (typeof rowToInsert[key] === 'undefined')
delete rowToInsert[key];
});
const fieldTypes = {};
this.fields.forEach(field => {
fieldTypes[field.name] = field.type;
});
try {
const { status, response } = await Tables.insertTableFakeRows({
uid: this.selectedWorkspace,
schema: this.workspace.breadcrumbs.schema,
table: this.workspace.breadcrumbs.table,
row: rowToInsert,
repeat: this.nInserts,
fields: fieldTypes
});
if (status === 'success') {
this.closeModal();
this.$emit('reload');
}
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isInserting = false;
},
closeModal () {
this.$emit('hide');
},
fieldLength (field) {
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
else if (TEXT.includes(field.type)) return field.charLength;
return field.length;
},
toggleFields (event, field) {
if (event.target.checked)
this.fieldsToExclude = this.fieldsToExclude.filter(f => f !== field.name);
else
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
},
filesChange (event, field) {
const { files } = event.target;
if (!files.length) return;
this.localRow[field] = files[0].path;
},
getKeyUsage (keyName) {
return this.keyUsage.find(key => key.field === keyName);
},
onKey (e) {
e.stopPropagation();
if (e.key === 'Escape')
this.closeModal();
}
}
};
</script>
<style scoped>
.modal-container {
max-width: 700px;
}
.form-label {
overflow: hidden;
white-space: normal;
text-overflow: ellipsis;
}
.input-group-addon {
display: flex;
align-items: center;
}
.modal-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

View File

@ -50,6 +50,16 @@
:tabindex="key+1"
@change="filesChange($event,field.name)"
>
<input
v-else-if="inputProps(field).type === 'number'"
ref="formInput"
v-model="localRow[field.name]"
class="form-input"
step="any"
:type="inputProps(field).type"
:disabled="fieldsToExclude.includes(field.name)"
:tabindex="key+1"
>
<input
v-else
ref="formInput"
@ -255,6 +265,7 @@ export default {
},
fieldLength (field) {
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
else if (TEXT.includes(field.type)) return field.charLength;
return field.length;
},
inputProps (field) {

View File

@ -437,7 +437,7 @@ export default {
schema: this.schema,
table: this.table,
numPrecision: null,
numLength: null,
numLength: 11,
datePrecision: null,
charLength: null,
nullable: false,

View File

@ -37,7 +37,13 @@
<span>{{ $t('word.add') }}</span>
<i class="mdi mdi-24px mdi-playlist-plus ml-1" />
</button>
<div class="dropdown export-dropdown">
<button class="btn btn-dark btn-sm" @click="showFakerModal">
<span>{{ $t('word.faker') }}</span>
<i class="mdi mdi-24px mdi-drama-masks ml-1" />
</button>
<div class="dropdown export-dropdown pr-2">
<button
:disabled="isQuering"
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
@ -91,6 +97,14 @@
@hide="hideAddModal"
@reload="reloadTable"
/>
<ModalFakerRows
v-if="isFakerModal"
:fields="fields"
:key-usage="keyUsage"
:tab-uid="tabUid"
@hide="hideFakerModal"
@reload="reloadTable"
/>
</div>
</template>
@ -98,6 +112,7 @@
import Tables from '@/ipc-api/Tables';
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
import ModalNewTableRow from '@/components/ModalNewTableRow';
import ModalFakerRows from '@/components/ModalFakerRows';
import { mapGetters, mapActions } from 'vuex';
import tableTabs from '@/mixins/tableTabs';
@ -105,7 +120,8 @@ export default {
name: 'WorkspaceTableTab',
components: {
WorkspaceQueryTable,
ModalNewTableRow
ModalNewTableRow,
ModalFakerRows
},
mixins: [tableTabs],
props: {
@ -119,6 +135,7 @@ export default {
results: [],
lastTable: null,
isAddModal: false,
isFakerModal: false,
autorefreshTimer: 0,
refreshInterval: null,
sortParams: {}
@ -222,6 +239,12 @@ export default {
hideAddModal () {
this.isAddModal = false;
},
showFakerModal () {
this.isFakerModal = true;
},
hideFakerModal () {
this.isFakerModal = false;
},
onKey (e) {
if (this.isSelected) {
e.stopPropagation();