mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
feat: foreign key support in add/edit row
This commit is contained in:
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1,6 +1,6 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
github: [fabio286]
|
||||||
patreon: fabio286
|
patreon: fabio286
|
||||||
open_collective: # Replace with a single Open Collective username
|
open_collective: # Replace with a single Open Collective username
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
import { app, BrowserWindow, nativeImage } from 'electron';
|
import { app, BrowserWindow, nativeImage } from 'electron';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { format as formatUrl } from 'url';
|
import { format as formatUrl } from 'url';
|
||||||
|
|
||||||
import ipcHandlers from './ipc-handlers';
|
import ipcHandlers from './ipc-handlers';
|
||||||
|
|
||||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||||
|
@ -64,4 +64,14 @@ export default (connections) => {
|
|||||||
return { status: 'error', response: err.toString() };
|
return { status: 'error', response: err.toString() };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-foreign-list', async (event, params) => {
|
||||||
|
try {
|
||||||
|
const results = await Tables.getForeignList(connections[params.uid], params);
|
||||||
|
return { status: 'success', response: results };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -40,8 +40,6 @@ export default class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async getKeyUsage (connection, schema, table) {
|
static async getKeyUsage (connection, schema, table) {
|
||||||
// SELECT * FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA='fep-gprs' AND TABLE_NAME='struttura_macchine' AND REFERENCED_TABLE_NAME IS NOT NULL;
|
|
||||||
|
|
||||||
const { rows } = await connection
|
const { rows } = await connection
|
||||||
.select('*')
|
.select('*')
|
||||||
.schema('information_schema')
|
.schema('information_schema')
|
||||||
|
@ -54,12 +54,13 @@ export default class {
|
|||||||
|
|
||||||
static async insertTableRows (connection, params) {
|
static async insertTableRows (connection, params) {
|
||||||
const insertObj = {};
|
const insertObj = {};
|
||||||
console.log(params);
|
|
||||||
for (const key in params.row) {
|
for (const key in params.row) {
|
||||||
const type = params.fields[key];
|
const type = params.fields[key];
|
||||||
let escapedParam;
|
let escapedParam;
|
||||||
|
|
||||||
if (NUMBER.includes(type))
|
if (params.row[key] === null)
|
||||||
|
escapedParam = 'NULL';
|
||||||
|
else if (NUMBER.includes(type))
|
||||||
escapedParam = params.row[key];
|
escapedParam = params.row[key];
|
||||||
else if ([...TEXT, ...LONG_TEXT].includes(type))
|
else if ([...TEXT, ...LONG_TEXT].includes(type))
|
||||||
escapedParam = `"${sqlEscaper(params.row[key])}"`;
|
escapedParam = `"${sqlEscaper(params.row[key])}"`;
|
||||||
@ -85,4 +86,17 @@ export default class {
|
|||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async getForeignList (connection, params) {
|
||||||
|
const query = connection
|
||||||
|
.select(`${params.column} AS foreignColumn`)
|
||||||
|
.schema(params.schema)
|
||||||
|
.from(params.table)
|
||||||
|
.orderBy('foreignColumn ASC');
|
||||||
|
|
||||||
|
if (params.description)
|
||||||
|
query.select(`LEFT(${params.description}, 20) AS foreignDescription`);
|
||||||
|
|
||||||
|
return query.run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
89
src/renderer/components/ForeignKeySelect.vue
Normal file
89
src/renderer/components/ForeignKeySelect.vue
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<template>
|
||||||
|
<select
|
||||||
|
ref="editField"
|
||||||
|
class="px-1"
|
||||||
|
@change="onChange"
|
||||||
|
@blur="$emit('blur')"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="row in foreignList"
|
||||||
|
:key="row.foreignColumn"
|
||||||
|
:value="row.foreignColumn"
|
||||||
|
:selected="row.foreignColumn === value"
|
||||||
|
>
|
||||||
|
{{ row.foreignColumn }} {{ 'foreignDescription' in row ? ` - ${row.foreignDescription}` : '' | cutText }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Tables from '@/ipc-api/Tables';
|
||||||
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
|
import { TEXT, LONG_TEXT } from 'common/fieldTypes';
|
||||||
|
export default {
|
||||||
|
name: 'ForeignKeySelect',
|
||||||
|
filters: {
|
||||||
|
cutText (val) {
|
||||||
|
if (typeof val !== 'string') return val;
|
||||||
|
return val.length > 15 ? `${val.substring(0, 15)}...` : val;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: [String, Number],
|
||||||
|
keyUsage: Object
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
foreignList: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
selectedWorkspace: 'workspaces/getSelected'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
let firstTextField;
|
||||||
|
const params = {
|
||||||
|
uid: this.selectedWorkspace,
|
||||||
|
schema: this.keyUsage.refSchema,
|
||||||
|
table: this.keyUsage.refTable
|
||||||
|
};
|
||||||
|
|
||||||
|
try { // Field data
|
||||||
|
const { status, response } = await Tables.getTableColumns(params);
|
||||||
|
if (status === 'success')
|
||||||
|
firstTextField = response.find(field => [...TEXT, ...LONG_TEXT].includes(field.type)).name || false;
|
||||||
|
else
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
|
try { // Foregn list
|
||||||
|
const { status, response } = await Tables.getForeignList({
|
||||||
|
...params,
|
||||||
|
column: this.keyUsage.refColumn,
|
||||||
|
description: firstTextField
|
||||||
|
});
|
||||||
|
|
||||||
|
if (status === 'success')
|
||||||
|
this.foreignList = response.rows;
|
||||||
|
else
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions({
|
||||||
|
addNotification: 'notifications/addNotification'
|
||||||
|
}),
|
||||||
|
onChange () {
|
||||||
|
this.$emit('update:value', this.$refs.editField.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -23,8 +23,15 @@
|
|||||||
<label class="form-label" :title="field.name">{{ field.name }}</label>
|
<label class="form-label" :title="field.name">{{ field.name }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group col-8 col-sm-12">
|
<div class="input-group col-8 col-sm-12">
|
||||||
|
<ForeignKeySelect
|
||||||
|
v-if="foreignKeys.includes(field.name)"
|
||||||
|
class="form-select"
|
||||||
|
:value.sync="localRow[field.name]"
|
||||||
|
:key-usage="getKeyUsage(field.name)"
|
||||||
|
:disabled="fieldsToExclude.includes(field.name)"
|
||||||
|
/>
|
||||||
<input
|
<input
|
||||||
v-if="inputProps(field).mask"
|
v-else-if="inputProps(field).mask"
|
||||||
v-model="localRow[field.name]"
|
v-model="localRow[field.name]"
|
||||||
v-mask="inputProps(field).mask"
|
v-mask="inputProps(field).mask"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
@ -100,9 +107,13 @@ import { TEXT, LONG_TEXT, NUMBER, DATE, TIME, DATETIME, BLOB, BIT } from 'common
|
|||||||
import { mask } from 'vue-the-mask';
|
import { mask } from 'vue-the-mask';
|
||||||
import { mapGetters, mapActions } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
import Tables from '@/ipc-api/Tables';
|
import Tables from '@/ipc-api/Tables';
|
||||||
|
import ForeignKeySelect from '@/components/ForeignKeySelect';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ModalNewTableRow',
|
name: 'ModalNewTableRow',
|
||||||
|
components: {
|
||||||
|
ForeignKeySelect
|
||||||
|
},
|
||||||
directives: {
|
directives: {
|
||||||
mask
|
mask
|
||||||
},
|
},
|
||||||
@ -113,8 +124,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
fields: Array,
|
connection: Object,
|
||||||
connection: Object
|
tabUid: [String, Number]
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@ -126,10 +137,21 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
getWorkspace: 'workspaces/getWorkspace'
|
selectedWorkspace: 'workspaces/getSelected',
|
||||||
|
getWorkspace: 'workspaces/getWorkspace',
|
||||||
|
getWorkspaceTab: 'workspaces/getWorkspaceTab'
|
||||||
}),
|
}),
|
||||||
workspace () {
|
workspace () {
|
||||||
return this.getWorkspace(this.connection.uid);
|
return this.getWorkspace(this.selectedWorkspace);
|
||||||
|
},
|
||||||
|
foreignKeys () {
|
||||||
|
return this.keyUsage.map(key => key.column);
|
||||||
|
},
|
||||||
|
fields () {
|
||||||
|
return this.getWorkspaceTab(this.tabUid) ? this.getWorkspaceTab(this.tabUid).fields : [];
|
||||||
|
},
|
||||||
|
keyUsage () {
|
||||||
|
return this.getWorkspaceTab(this.tabUid) ? this.getWorkspaceTab(this.tabUid).keyUsage : [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -194,7 +216,7 @@ export default {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { status, response } = await Tables.insertTableRows({
|
const { status, response } = await Tables.insertTableRows({
|
||||||
uid: this.connection.uid,
|
uid: this.selectedWorkspace,
|
||||||
schema: this.workspace.breadcrumbs.schema,
|
schema: this.workspace.breadcrumbs.schema,
|
||||||
table: this.workspace.breadcrumbs.table,
|
table: this.workspace.breadcrumbs.table,
|
||||||
row: rowToInsert,
|
row: rowToInsert,
|
||||||
@ -271,6 +293,10 @@ export default {
|
|||||||
if (!files.length) return;
|
if (!files.length) return;
|
||||||
|
|
||||||
this.localRow[field] = files[0].path;
|
this.localRow[field] = files[0].path;
|
||||||
|
},
|
||||||
|
|
||||||
|
getKeyUsage (keyName) {
|
||||||
|
return this.keyUsage.find(key => key.column === keyName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -16,6 +16,13 @@
|
|||||||
:class="`${isNull(col)} type-${fieldType(cKey)}`"
|
:class="`${isNull(col)} type-${fieldType(cKey)}`"
|
||||||
@dblclick="editON($event, col, cKey)"
|
@dblclick="editON($event, col, cKey)"
|
||||||
>{{ col | typeFormat(fieldType(cKey), fieldPrecision(cKey)) | cutText }}</span>
|
>{{ col | typeFormat(fieldType(cKey), fieldPrecision(cKey)) | cutText }}</span>
|
||||||
|
<ForeignKeySelect
|
||||||
|
v-else-if="foreignKeys.includes(cKey)"
|
||||||
|
class="editable-field"
|
||||||
|
:value.sync="editingContent"
|
||||||
|
:key-usage="getKeyUsage(cKey)"
|
||||||
|
@blur="editOFF"
|
||||||
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<input
|
<input
|
||||||
v-if="inputProps.mask"
|
v-if="inputProps.mask"
|
||||||
@ -131,11 +138,13 @@ import hexToBinary from 'common/libs/hexToBinary';
|
|||||||
import { TEXT, LONG_TEXT, NUMBER, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
|
import { TEXT, LONG_TEXT, NUMBER, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
|
||||||
import { mask } from 'vue-the-mask';
|
import { mask } from 'vue-the-mask';
|
||||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||||
|
import ForeignKeySelect from '@/components/ForeignKeySelect';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WorkspaceQueryTableRow',
|
name: 'WorkspaceQueryTableRow',
|
||||||
components: {
|
components: {
|
||||||
ConfirmModal
|
ConfirmModal,
|
||||||
|
ForeignKeySelect
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
mask
|
mask
|
||||||
@ -240,6 +249,9 @@ export default {
|
|||||||
},
|
},
|
||||||
isImage () {
|
isImage () {
|
||||||
return ['gif', 'jpg', 'png', 'bmp', 'ico', 'tif'].includes(this.contentInfo.ext);
|
return ['gif', 'jpg', 'png', 'bmp', 'ico', 'tif'].includes(this.contentInfo.ext);
|
||||||
|
},
|
||||||
|
foreignKeys () {
|
||||||
|
return this.keyUsage.map(key => key.column);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
@ -381,6 +393,9 @@ export default {
|
|||||||
},
|
},
|
||||||
selectRow (event, row) {
|
selectRow (event, row) {
|
||||||
this.$emit('selectRow', event, row);
|
this.$emit('selectRow', event, row);
|
||||||
|
},
|
||||||
|
getKeyUsage (keyName) {
|
||||||
|
return this.keyUsage.find(key => key.column === keyName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -33,7 +33,6 @@
|
|||||||
<div class="workspace-query-results column col-12">
|
<div class="workspace-query-results column col-12">
|
||||||
<WorkspaceQueryTable
|
<WorkspaceQueryTable
|
||||||
v-if="results"
|
v-if="results"
|
||||||
v-show="!isQuering"
|
|
||||||
ref="queryTable"
|
ref="queryTable"
|
||||||
:results="results"
|
:results="results"
|
||||||
:tab-uid="tabUid"
|
:tab-uid="tabUid"
|
||||||
@ -43,8 +42,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ModalNewTableRow
|
<ModalNewTableRow
|
||||||
v-if="isAddModal"
|
v-if="isAddModal"
|
||||||
:fields="fields"
|
:tab-uid="tabUid"
|
||||||
:connection="connection"
|
|
||||||
@hide="hideAddModal"
|
@hide="hideAddModal"
|
||||||
@reload="reloadTable"
|
@reload="reloadTable"
|
||||||
/>
|
/>
|
||||||
@ -57,7 +55,6 @@ import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
|
|||||||
import ModalNewTableRow from '@/components/ModalNewTableRow';
|
import ModalNewTableRow from '@/components/ModalNewTableRow';
|
||||||
import { mapGetters, mapActions } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
import tableTabs from '@/mixins/tableTabs';
|
import tableTabs from '@/mixins/tableTabs';
|
||||||
// import { TEXT, LONG_TEXT } from 'common/fieldTypes';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WorkspaceTableTab',
|
name: 'WorkspaceTableTab',
|
||||||
@ -90,13 +87,6 @@ export default {
|
|||||||
},
|
},
|
||||||
isSelected () {
|
isSelected () {
|
||||||
return this.workspace.selected_tab === 1;
|
return this.workspace.selected_tab === 1;
|
||||||
},
|
|
||||||
firstTextField () { // TODO: move inside new row modal and row components
|
|
||||||
if (this.fields.length) {
|
|
||||||
const textField = this.fields.find(field => [...TEXT, ...LONG_TEXT].includes(field.type));
|
|
||||||
return textField ? textField.name : '';
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -25,4 +25,8 @@ export default class {
|
|||||||
static insertTableRows (params) {
|
static insertTableRows (params) {
|
||||||
return ipcRenderer.invoke('insertTableRows', params);
|
return ipcRenderer.invoke('insertTableRows', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getForeignList (params) {
|
||||||
|
return ipcRenderer.invoke('get-foreign-list', params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user