Skip to content
Snippets Groups Projects
Commit ce7c4753 authored by Martin Weise's avatar Martin Weise
Browse files

more information for frontend

Former-commit-id: 01485bd2
parent c6b95b70
No related branches found
No related tags found
1 merge request!42Fixed the query service tests
Showing
with 194 additions and 46 deletions
...@@ -2,6 +2,7 @@ package at.tuwien.endpoints; ...@@ -2,6 +2,7 @@ package at.tuwien.endpoints;
import at.tuwien.api.database.DatabaseBriefDto; import at.tuwien.api.database.DatabaseBriefDto;
import at.tuwien.api.database.DatabaseCreateDto; import at.tuwien.api.database.DatabaseCreateDto;
import at.tuwien.api.database.DatabaseDto;
import at.tuwien.api.database.DatabaseModifyDto; import at.tuwien.api.database.DatabaseModifyDto;
import at.tuwien.entities.database.Database; import at.tuwien.entities.database.Database;
import at.tuwien.exception.*; import at.tuwien.exception.*;
...@@ -78,9 +79,8 @@ public class DatabaseEndpoint { ...@@ -78,9 +79,8 @@ public class DatabaseEndpoint {
@ApiResponse(code = 400, message = "The payload contains invalid data."), @ApiResponse(code = 400, message = "The payload contains invalid data."),
@ApiResponse(code = 404, message = "No database with this id was found in metadata database."), @ApiResponse(code = 404, message = "No database with this id was found in metadata database."),
}) })
public ResponseEntity<DatabaseBriefDto> findById(@NotBlank @PathVariable Long id) throws DatabaseNotFoundException { public ResponseEntity<DatabaseDto> findById(@NotBlank @PathVariable Long id) throws DatabaseNotFoundException {
final DatabaseBriefDto database = databaseMapper.databaseToDatabaseBriefDto(databaseService.findById(id)); return ResponseEntity.ok(databaseMapper.databaseToDatabaseDto(databaseService.findById(id)));
return ResponseEntity.ok(database);
} }
@PutMapping("/{id}") @PutMapping("/{id}")
......
...@@ -3,6 +3,7 @@ package at.tuwien.endpoint; ...@@ -3,6 +3,7 @@ package at.tuwien.endpoint;
import at.tuwien.BaseUnitTest; import at.tuwien.BaseUnitTest;
import at.tuwien.api.database.DatabaseBriefDto; import at.tuwien.api.database.DatabaseBriefDto;
import at.tuwien.api.database.DatabaseCreateDto; import at.tuwien.api.database.DatabaseCreateDto;
import at.tuwien.api.database.DatabaseDto;
import at.tuwien.api.database.DatabaseModifyDto; import at.tuwien.api.database.DatabaseModifyDto;
import at.tuwien.endpoints.DatabaseEndpoint; import at.tuwien.endpoints.DatabaseEndpoint;
import at.tuwien.exception.*; import at.tuwien.exception.*;
...@@ -104,7 +105,7 @@ public class EndpointUnitTest extends BaseUnitTest { ...@@ -104,7 +105,7 @@ public class EndpointUnitTest extends BaseUnitTest {
when(databaseService.findById(DATABASE_1_ID)) when(databaseService.findById(DATABASE_1_ID))
.thenReturn(DATABASE_1); .thenReturn(DATABASE_1);
final ResponseEntity<DatabaseBriefDto> response = databaseEndpoint.findById(DATABASE_1_ID); final ResponseEntity<DatabaseDto> response = databaseEndpoint.findById(DATABASE_1_ID);
/* test */ /* test */
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
......
package at.tuwien.mapper; package at.tuwien.mapper;
import at.tuwien.api.database.DatabaseBriefDto; import at.tuwien.api.database.DatabaseBriefDto;
import at.tuwien.api.database.DatabaseDto;
import at.tuwien.api.database.DatabaseModifyDto; import at.tuwien.api.database.DatabaseModifyDto;
import at.tuwien.entities.database.Database; import at.tuwien.entities.database.Database;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
...@@ -19,6 +20,11 @@ public interface DatabaseMapper { ...@@ -19,6 +20,11 @@ public interface DatabaseMapper {
}) })
DatabaseBriefDto databaseToDatabaseBriefDto(Database data); DatabaseBriefDto databaseToDatabaseBriefDto(Database data);
@Mappings({
@Mapping(target = "id", source = "id")
})
DatabaseDto databaseToDatabaseDto(Database data);
// https://stackoverflow.com/questions/1657193/java-code-library-for-generating-slugs-for-use-in-pretty-urls#answer-1657250 // https://stackoverflow.com/questions/1657193/java-code-library-for-generating-slugs-for-use-in-pretty-urls#answer-1657250
default String databaseToInternalDatabaseName(Database data) { default String databaseToInternalDatabaseName(Database data) {
final Pattern NONLATIN = Pattern.compile("[^\\w-]"); final Pattern NONLATIN = Pattern.compile("[^\\w-]");
......
package at.tuwien.api.database;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.*;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Getter
@Setter
@ToString
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DatabaseDto {
@NotNull
@Min(value = 1)
@Parameter(name = "database id", example = "1")
private Long id;
@NotBlank
@Parameter(name = "database name", example = "Weather Australia")
private String name;
@NotBlank
@Parameter(name = "database internal name", example = "weather_australia")
private String internalName;
@NotBlank
@Parameter(name = "database description", example = "Weather Australia 2009-2021")
private String description;
@NotBlank
@Parameter(name = "database publisher", example = "National Office")
private String publisher;
}
...@@ -3,4 +3,4 @@ id,vorname,nachname ...@@ -3,4 +3,4 @@ id,vorname,nachname
2,max,mustermann 2,max,mustermann
3,sabine,huber 3,sabine,huber
4,peter,muster 4,peter,muster
5,martin,mustermann 5,martina,musterfrau
\ No newline at end of file \ No newline at end of file
...@@ -12,20 +12,19 @@ import at.tuwien.entities.database.table.columns.TableColumn; ...@@ -12,20 +12,19 @@ import at.tuwien.entities.database.table.columns.TableColumn;
import at.tuwien.exception.ArbitraryPrimaryKeysException; import at.tuwien.exception.ArbitraryPrimaryKeysException;
import at.tuwien.exception.ImageNotSupportedException; import at.tuwien.exception.ImageNotSupportedException;
import org.apache.commons.lang.WordUtils; import org.apache.commons.lang.WordUtils;
import org.jooq.CreateTableColumnStep; import org.jooq.*;
import org.jooq.DSLContext;
import org.jooq.DataType;
import org.jooq.Field;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.Mappings; import org.mapstruct.Mappings;
import org.mapstruct.Named; import org.mapstruct.Named;
import javax.sound.sampled.Line;
import java.text.Normalizer; import java.text.Normalizer;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.jooq.impl.SQLDataType.*; import static org.jooq.impl.SQLDataType.*;
import static org.jooq.impl.DSL.*; import static org.jooq.impl.DSL.*;
...@@ -137,6 +136,7 @@ public interface TableMapper { ...@@ -137,6 +136,7 @@ public interface TableMapper {
if (Arrays.stream(data.getColumns()).anyMatch(dto -> dto.getCheckExpression() != null)) { if (Arrays.stream(data.getColumns()).anyMatch(dto -> dto.getCheckExpression() != null)) {
throw new ImageNotSupportedException("Currently no check operations are supported"); throw new ImageNotSupportedException("Currently no check operations are supported");
} }
final List<Constraint> constraints = new LinkedList<>();
final CreateTableColumnStep step = context.createTableIfNotExists(nameToInternalName(data.getName())); final CreateTableColumnStep step = context.createTableIfNotExists(nameToInternalName(data.getName()));
for (ColumnCreateDto column : data.getColumns()) { for (ColumnCreateDto column : data.getColumns()) {
final DataType<?> dataType = columnTypeDtoToDataType(column) final DataType<?> dataType = columnTypeDtoToDataType(column)
...@@ -144,7 +144,20 @@ public interface TableMapper { ...@@ -144,7 +144,20 @@ public interface TableMapper {
.identity(column.getPrimaryKey()); .identity(column.getPrimaryKey());
step.column(nameToInternalName(nameToColumnName(column.getName())), dataType); step.column(nameToInternalName(nameToColumnName(column.getName())), dataType);
} }
/* constraints */ /* primary keys */
constraints.add(constraint("PK_FDA")
.primaryKey(Arrays.stream(data.getColumns())
.filter(ColumnCreateDto::getPrimaryKey)
.map(c -> field(nameToInternalName(nameToColumnName(c.getName()))))
.toArray(Field[]::new)));
/* unique constraints */
final Stream<ColumnCreateDto> uniqueConstraints = Arrays.stream(data.getColumns())
.filter(ColumnCreateDto::getUnique);
if (uniqueConstraints.count() > 0) {
uniqueConstraints.forEach(c -> constraints.add(constraint("UQ_" + nameToInternalName(nameToColumnName(c.getName())))
.unique(nameToInternalName(nameToColumnName(c.getName())))));
}
step.constraints(constraints);
return step; return step;
} }
......
...@@ -3,6 +3,7 @@ package at.tuwien.service; ...@@ -3,6 +3,7 @@ package at.tuwien.service;
import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.api.database.query.QueryResultDto;
import at.tuwien.api.database.table.TableCreateDto; import at.tuwien.api.database.table.TableCreateDto;
import at.tuwien.api.database.table.TableCsvDto; import at.tuwien.api.database.table.TableCsvDto;
import at.tuwien.api.database.table.columns.ColumnCreateDto;
import at.tuwien.entities.database.Database; import at.tuwien.entities.database.Database;
import at.tuwien.entities.database.table.Table; import at.tuwien.entities.database.table.Table;
import at.tuwien.exception.ArbitraryPrimaryKeysException; import at.tuwien.exception.ArbitraryPrimaryKeysException;
......
<template> <template>
<v-toolbar v-if="db" dense flat> <v-toolbar v-if="db" flat>
<v-toolbar-title>{{ db.name }}</v-toolbar-title> <v-toolbar-title>
{{ db.name }}
</v-toolbar-title>
<template v-slot:extension> <template v-slot:extension>
<v-tabs v-model="tab" color="primary" class="mb-1"> <v-tabs v-model="tab" color="primary" class="mb-1">
<v-tabs-slider color="primary" /> <v-tabs-slider color="primary" />
<v-tab :to="`/db/${$route.params.db_id}`"> <v-tab :to="`/db/${$route.params.db_id}/`">
Info Info
</v-tab> </v-tab>
<v-tab :to="`/db/${$route.params.db_id}/tables`"> <v-tab :to="`/db/${$route.params.db_id}/tables/`">
Tables Tables
</v-tab> </v-tab>
<v-tab v-if="false" :to="`/db/${$route.params.db_id}/query`"> <v-tab v-if="false" :to="`/db/${$route.params.db_id}/query/`">
Query Query
</v-tab> </v-tab>
<v-tab :to="`/db/${$route.params.db_id}/admin`"> <v-tab :to="`/db/${$route.params.db_id}/admin/`">
Admin Admin
</v-tab> </v-tab>
</v-tabs> </v-tabs>
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
</v-card-text> </v-card-text>
<v-card-text v-for="(c, idx) in columns" :key="idx" class="pa-3 mb-2"> <v-card-text v-for="(c, idx) in columns" :key="idx" class="pa-3 mb-2">
<v-row class="column pa-2 ml-1 mr-1"> <v-row class="column pa-2 ml-1 mr-1">
<v-col cols="4"> <v-col cols="3">
<v-text-field v-model="c.name" required label="Name" /> <v-text-field v-model="c.name" required label="Name" />
</v-col> </v-col>
<v-col cols="3"> <v-col cols="3">
...@@ -36,10 +36,16 @@ ...@@ -36,10 +36,16 @@
label="Primary Key" label="Primary Key"
@change="(x) => onChange(idx, x, 'primaryKey')" /> @change="(x) => onChange(idx, x, 'primaryKey')" />
</v-col> </v-col>
<v-col cols="1">
<v-checkbox
v-model="c.unique"
label="Unique"
@change="(x) => onChange(idx, x, 'unique')" />
</v-col>
<v-col cols="2"> <v-col cols="2">
<v-checkbox <v-checkbox
v-model="c.nullAllowed" v-model="c.nullAllowed"
label="Null Allowed" label="NULL Allowed"
@change="(x) => onChange(idx, x, 'nullAllowed')" /> @change="(x) => onChange(idx, x, 'nullAllowed')" />
</v-col> </v-col>
<v-spacer /> <v-spacer />
......
...@@ -33,14 +33,26 @@ ...@@ -33,14 +33,26 @@
<th>Column Name</th> <th>Column Name</th>
<th>Type</th> <th>Type</th>
<th>Primary Key</th> <th>Primary Key</th>
<th>Null Allowed</th> <th>Unique</th>
<th>NULL Allowed</th>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(col, idx) in tableDetails.columns" :key="idx"> <tr v-for="(col, idx) in tableDetails.columns" :key="idx">
<td class="pl-0">{{ col.name }}</td> <td>
<td class="pl-0">{{ col.columnType }}</td> {{ col.name }}
<td class="pl-0"><v-simple-checkbox v-model="col.isPrimaryKey" disabled /></td> </td>
<td class="pl-0"><v-simple-checkbox v-model="col.isNullAllowed" disabled /></td> <td>
{{ col.columnType }}
</td>
<td>
<v-simple-checkbox v-model="col.isPrimaryKey" disabled aria-readonly="true" />
</td>
<td>
<v-simple-checkbox v-model="col.unique" disabled aria-readonly="true" />
</td>
<td>
<v-simple-checkbox v-model="col.isNullAllowed" disabled aria-readonly="true" />
</td>
</tr> </tr>
</tbody> </tbody>
</v-simple-table> </v-simple-table>
......
...@@ -4,9 +4,12 @@ ...@@ -4,9 +4,12 @@
<v-tabs-items v-model="tab"> <v-tabs-items v-model="tab">
<v-tab-item> <v-tab-item>
<v-card flat> <v-card flat>
<v-card-text> <v-card-title>
{{ db.name }} {{ publisher }}
</v-card-text> </v-card-title>
<v-card-subtitle>
{{ description }}
</v-card-subtitle>
</v-card> </v-card>
</v-tab-item> </v-tab-item>
</v-tabs-items> </v-tabs-items>
...@@ -28,11 +31,18 @@ export default { ...@@ -28,11 +31,18 @@ export default {
computed: { computed: {
db () { db () {
return this.$store.state.db return this.$store.state.db
},
description () {
return this.db.description === null ? '(no description)' : ''
},
publisher () {
return this.db.publisher === null ? '(no publisher)' : ''
} }
}, },
async mounted () { async mounted () {
try { try {
const res = await this.$axios.get(`/api/database/${this.$route.params.db_id}`) const res = await this.$axios.get(`/api/database/${this.$route.params.db_id}`)
console.debug('database', res.data)
this.$store.commit('SET_DATABASE', res.data) this.$store.commit('SET_DATABASE', res.data)
} catch (err) { } catch (err) {
this.$toast.error('Could not load database.') this.$toast.error('Could not load database.')
......
...@@ -4,15 +4,40 @@ ...@@ -4,15 +4,40 @@
Table Import CSV Table Import CSV
</h3> </h3>
<v-row dense> <v-row dense>
<v-col cols="10"> <v-col cols="8">
<v-checkbox
v-model="tableInsert.skipHeader"
label="Skip first row" />
</v-col>
</v-row>
<v-row dense>
<v-col cols="8">
<v-text-field
v-model="tableInsert.nullElement"
placeholder="e.g. NA or leave empty"
label="NULL Element" />
</v-col>
</v-row>
<v-row dense>
<v-col cols="8">
<v-text-field
v-model="tableInsert.delimiter"
label="Delimiter"
placeholder="e.g. ;" />
</v-col>
</v-row>
<v-row dense>
<v-col cols="8">
<v-file-input <v-file-input
v-model="file" v-model="file"
accept="text/csv" accept="text/csv"
show-size show-size
label="CSV File" /> label="CSV File" />
</v-col> </v-col>
<v-col cols="2" class="mt-3"> </v-row>
<v-btn :loading="loading" @click="upload">Upload</v-btn> <v-row dense>
<v-col cols="6">
<v-btn :disabled="!file" :loading="loading" color="primary" @click="upload">Next</v-btn>
</v-col> </v-col>
</v-row> </v-row>
</div> </div>
...@@ -25,6 +50,12 @@ export default { ...@@ -25,6 +50,12 @@ export default {
data () { data () {
return { return {
loading: false, loading: false,
tableInsert: {
skipHeader: false,
nullElement: null,
delimiter: null,
csvLocation: null
},
file: null file: null
} }
}, },
...@@ -33,22 +64,37 @@ export default { ...@@ -33,22 +64,37 @@ export default {
methods: { methods: {
async upload () { async upload () {
this.loading = true this.loading = true
const url = `/api/tables/api/database/${this.$route.params.db_id}/table/${this.$route.params.table_id}` const url = '/server-middleware/table_from_csv'
const data = new FormData() const data = new FormData()
data.append('file', this.file) data.append('file', this.file)
try { try {
const res = await this.$axios.post(url, data, { const res = await this.$axios.post(url, data, {
headers: { 'Content-Type': 'multipart/form-data' } headers: { 'Content-Type': 'multipart/form-data' }
}) })
if (res.data.Result) { if (res.data.success) {
this.$toast.success('Uploaded successfully!') this.tableInsert.csvLocation = res.data.file.filename
this.$router.push({ path: '.' }) console.debug('upload csv', res.data)
} else { } else {
this.$toast.error('Could not upload CSV data') console.error('Could not upload CSV data', res.data)
this.loading = false
return
} }
} catch (err) { } catch (err) {
this.$toast.error('Could not upload data.') console.error('Could not upload data.', err)
this.loading = false
return
}
const insertUrl = `/api/tables/api/database/${this.$route.params.db_id}/table/${this.$route.params.table_id}/data`
let insertResult
try {
insertResult = await this.$axios.post(insertUrl, this.tableInsert)
console.debug('inserted table', insertResult.data)
} catch (err) {
console.error('Could not insert data.', err)
this.loading = false
return
} }
this.$toast.success('Uploaded csv into table.')
this.loading = false this.loading = false
} }
} }
......
<template> <template>
<div> <v-card>
<h3> <v-card-title v-if="!loading">
{{ tableName }} {{ table.name }}
</h3> </v-card-title>
<v-card-subtitle v-if="!loading">
{{ table.description }}
</v-card-subtitle>
<v-data-table <v-data-table
:headers="headers" :headers="headers"
:items="rows" :items="rows"
:loading="loading" :loading="loading"
:items-per-page="10" :items-per-page="30"
class="elevation-1"> class="elevation-1" />
</v-data-table> </v-card>
</div>
</template> </template>
<script> <script>
export default { export default {
...@@ -20,7 +22,7 @@ export default { ...@@ -20,7 +22,7 @@ export default {
data () { data () {
return { return {
loading: true, loading: true,
tableName: '', table: null,
headers: [], headers: [],
rows: [] rows: []
} }
...@@ -33,12 +35,12 @@ export default { ...@@ -33,12 +35,12 @@ export default {
async loadProperties () { async loadProperties () {
try { try {
const res = await this.$axios.get(`/api/tables/api/database/${this.$route.params.db_id}/table/${this.$route.params.table_id}`) const res = await this.$axios.get(`/api/tables/api/database/${this.$route.params.db_id}/table/${this.$route.params.table_id}`)
this.tableName = res.data.name this.table = res.data
console.debug('headers', res.data.columns) console.debug('headers', res.data.columns)
this.headers = res.data.columns.map((c) => { this.headers = res.data.columns.map((c) => {
return { return {
value: c.internalName, value: c.internalName,
text: c.name text: this.columnAddition(c) + c.name
} }
}) })
} catch (err) { } catch (err) {
...@@ -55,6 +57,15 @@ export default { ...@@ -55,6 +57,15 @@ export default {
this.$toast.error('Could not load table data.') this.$toast.error('Could not load table data.')
} }
this.loading = false this.loading = false
},
columnAddition (column) {
if (column.isPrimaryKey) {
return ''
}
if (column.unique) {
return ''
}
return ''
} }
} }
} }
......
...@@ -201,6 +201,7 @@ export default { ...@@ -201,6 +201,7 @@ export default {
console.debug('inserted table', insertResult.data) console.debug('inserted table', insertResult.data)
} catch (err) { } catch (err) {
console.log(err) console.log(err)
this.$toast.error('Could not insert csv into table.')
return return
} }
this.step = 4 this.step = 4
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment