Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: remove jsonb_array and fix jsonb data types such that it can be used in schema design and graphql api #4409

Merged
merged 35 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
4cd9812
fix: remove jsonb_array type
mswertz Oct 29, 2024
5ba651b
fix: remove jsonb_array type
mswertz Oct 29, 2024
adfd5d1
fix tests
mswertz Oct 29, 2024
fe90ef7
Merge branch 'master' into fix/remove_json_array
mswertz Nov 4, 2024
3238649
Merge branch 'master' of github.com:molgenis/molgenis-emx2 into fix/r…
mswertz Nov 6, 2024
69aa6bd
wip
mswertz Nov 6, 2024
917fdb6
undo|
mswertz Nov 6, 2024
8889644
Merge branch 'master' of github.com:molgenis/molgenis-emx2 into fix/r…
mswertz Nov 6, 2024
02141e3
add jsonb to graphql api
mswertz Nov 6, 2024
41443eb
many fixes for the proper json passing
mswertz Nov 6, 2024
16dcee1
fix test
mswertz Nov 6, 2024
e5bedd1
Merge branch 'master' of github.com:molgenis/molgenis-emx2 into fix/r…
mswertz Nov 7, 2024
8e20cad
add test
mswertz Nov 7, 2024
ea0c91a
Merge branch 'master' into fix/remove_json_array
mswertz Nov 11, 2024
3c1aa07
merged
mswertz Nov 15, 2024
092c9d2
Merge branch 'master' of github.com:molgenis/molgenis-emx2 into fix/r…
mswertz Nov 18, 2024
d5dd710
expand test coverage
mswertz Nov 18, 2024
59b7b78
Merge branch 'master' into fix/remove_json_array
mswertz Nov 18, 2024
fc50cf2
remove unused code
mswertz Nov 18, 2024
8f34bdb
merge
mswertz Nov 19, 2024
5febc75
Merge branch 'master' into fix/remove_json_array
svandenhoek Nov 25, 2024
da8e178
Updated code from #4323 to be compatible with this branch
svandenhoek Nov 25, 2024
3755cbd
updated docs
svandenhoek Nov 25, 2024
2188e18
generalized GraphqlCustomTypes in switch
svandenhoek Nov 25, 2024
8414b94
serialize should only throw a CoercingSerializeException (https://www…
svandenhoek Nov 25, 2024
c7ddff2
Updated toJsonb to include full validation
svandenhoek Nov 25, 2024
dad3e7f
Improved JSON validation (no trailing data, duplicate check)
svandenhoek Nov 25, 2024
e3d1243
auto-formatting
svandenhoek Nov 25, 2024
996dfe1
WIP: front-end validation
svandenhoek Nov 26, 2024
d0833c6
Added tests
svandenhoek Nov 26, 2024
150161c
fixes null giving wrong front-end error message
svandenhoek Nov 26, 2024
70f505f
fixed comments
svandenhoek Nov 26, 2024
d2e2d76
more comment fixes
svandenhoek Nov 26, 2024
ff6cdbf
processed feedback
svandenhoek Nov 26, 2024
83f7eec
formatting
svandenhoek Nov 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/metadata-utils/src/fieldHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const fieldTypes = () => {
"TEXT_ARRAY",
"UUID",
"UUID_ARRAY",
"JSON",
];
};

Expand Down
3 changes: 1 addition & 2 deletions apps/metadata-utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ export type CellValueType =
| "DATETIME"
| "DATETIME_ARRAY"
| "PERIOD"
| "JSONB"
| "JSONB_ARRAY"
| "JSON"
| "REF"
| "REF_ARRAY"
| "REFBACK"
Expand Down
2 changes: 2 additions & 0 deletions apps/molgenis-components/lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import InputDate from "../src/components/forms/InputDate.vue";
import InputDateTime from "../src/components/forms/InputDateTime.vue";
import InputDecimal from "../src/components/forms/InputDecimal.vue";
import InputEmail from "../src/components/forms/InputEmail.vue";
import InputJson from "../src/components/forms/InputJson.vue";
import InputFile from "../src/components/forms/InputFile.vue";
import InputGroup from "../src/components/forms/InputGroup.vue";
import InputHeading from "../src/components/forms/InputHeading.vue";
Expand Down Expand Up @@ -189,6 +190,7 @@ export {
InputDateTime,
InputDecimal,
InputEmail,
InputJson,
InputFile,
InputGroup,
InputHeading,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const filterTypeMap = {
HYPERLINK_ARRAY: StringFilter,
TEXT: StringFilter,
TEXT_ARRAY: StringFilter,
JSON: StringFilter,
UUID: StringFilter,
UUID_ARRAY: StringFilter,
INT: IntegerFilter,
Expand Down
14 changes: 14 additions & 0 deletions apps/molgenis-components/src/components/forms/FormInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import InputText from "../forms/InputText.vue";
import BaseInput from "../forms/baseInputs/BaseInput.vue";
import InputEmail from "./InputEmail.vue";
import InputHyperlink from "./InputHyperlink.vue";
import InputJson from "./InputJson.vue";
import InputRefList from "./InputRefList.vue";

const typeToInputMap = {
Expand All @@ -43,6 +44,7 @@ const typeToInputMap = {
HYPERLINK: InputHyperlink,
STRING: InputString,
TEXT: InputText,
JSON: InputJson,
INT: InputInt,
LONG: InputLong,
DECIMAL: InputDecimal,
Expand Down Expand Up @@ -364,6 +366,17 @@ export default {
</div>
<div>You typed: {{ JSON.stringify(textValueArray, null, 2) }}</div>
</DemoItem>
<DemoItem>
<div>
<FormInput
id="json-example"
columnType="JSON"
label="Example json input"
v-model="jsonValue"
/>
</div>
<div>You typed: {{ jsonValue }}</div>
</DemoItem>
<DemoItem>
<div>
<FormInput
Expand Down Expand Up @@ -536,6 +549,7 @@ export default {
intValueArray: [5, 37],
textValue: "example text",
textValueArray: ["text", "more text"],
jsonValue: '{"name":"bofke"}',
longValue: "1337",
longValueArray: ["0", "101"],
decimalValue: 3.7,
Expand Down
97 changes: 97 additions & 0 deletions apps/molgenis-components/src/components/forms/InputJson.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<template>
<FormGroup
:id="id"
:label="label"
:required="required"
:description="description"
:errorMessage="stringError"
>
<InputGroup>
<textarea
:id="id"
:ref="id"
:name="name"
:value="modelValue"
@input="
$emit('update:modelValue', ($event.target as HTMLInputElement).value)
"
type="text"
class="form-control"
:class="{ 'is-invalid': stringError }"
:aria-describedby="id"
:placeholder="placeholder"
:readonly="readonly"
/>
</InputGroup>
</FormGroup>
</template>

<script lang="ts">
import FormGroup from "./FormGroup.vue";
import InputGroup from "./InputGroup.vue";
import BaseInput from "./baseInputs/BaseInput.vue";
import { parseJson, isJsonObjectOrArray } from "./formUtils/formUtils";
svandenhoek marked this conversation as resolved.
Show resolved Hide resolved

export default {
name: "InputJson",
components: { FormGroup, InputGroup },
extends: BaseInput,
computed: {
stringError() {
if (typeof this.modelValue === "string") {
try {
if (!isJsonObjectOrArray(JSON.parse(this.modelValue))) {
return `Root element must be an object or array`;
}
} catch {
return `Please enter valid JSON`;
}
return this.errorMessage;
} else {
return this.errorMessage;
}
},
},
};
</script>

<style scoped>
.is-invalid {
background-image: none;
}

span:hover .hoverIcon {
visibility: visible;
}
</style>

<docs>
<template>
<div>
<InputJson
id="input-json"
v-model="value"
label="My JSON input label"
description="Some help needed?"
/>
You typed: {{ value }}<br />
<b>Readonly</b>
<InputJson
id="input-json2"
:readonly="true"
v-model="readOnlyValue"
description="Should not be able to edit this"
/>
</div>
</template>
<script>
export default {
data: function () {
return {
value: '{"name":"bofke"}',
readOnlyValue: '{"name":"bofke"}',
};
},
};
</script>
</docs>
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ function getColumnError(
if (type === "PERIOD_ARRAY" && containsInvalidPeriod(value)) {
return "Invalid Period: should start with a P and should contain at least a Y(year), M(month) or D(day): e.g. 'P1Y3M14D'";
}
if (type === "JSON") {
try {
if (!isJsonObjectOrArray(JSON.parse(value))) {
return `Root element must be an object or array`;
}
} catch {
return `Please enter valid JSON`;
}
}
if (column.validation) {
return getColumnValidationError(column.validation, rowData, tableMetaData);
}
Expand Down Expand Up @@ -227,6 +236,13 @@ function containsInvalidPeriod(periods: any) {
return periods.find((period: any) => !isValidPeriod(period));
}

export function isJsonObjectOrArray(parsedJson: any) {
svandenhoek marked this conversation as resolved.
Show resolved Hide resolved
if (typeof parsedJson === "object" && parsedJson !== null) {
chinook25 marked this conversation as resolved.
Show resolved Hide resolved
return true;
}
return false;
}

export function removeKeyColumns(tableMetaData: ITableMetaData, rowData: IRow) {
const keyColumnsIds = tableMetaData?.columns
?.filter((column: IColumn) => column.key === 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import HyperlinkDisplay from "./cellTypes/HyperlinkDisplay.vue";
const typeMap: { [key: string]: string } = {
FILE: "FileDisplay",
TEXT: "TextDisplay",
JSON: "TextDisplay",
REFBACK: "ListDisplay",
REF: "ObjectDisplay",
ONTOLOGY: "ObjectDisplay",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,8 @@ function graphqlFilter(
if (conditions.length) {
if (
col.columnType.startsWith("STRING") ||
col.columnType.startsWith("TEXT")
col.columnType.startsWith("TEXT") ||
col.columnType.startsWith("JSON")
) {
filter[col.id] = { like: conditions };
} else if (col.columnType.startsWith("BOOL")) {
Expand Down
1 change: 1 addition & 0 deletions apps/schema/src/columnTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default [
"HYPERLINK_ARRAY",
"INT",
"INT_ARRAY",
"JSON",
"LONG",
"LONG_ARRAY",
"ONTOLOGY",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,57 @@
package org.molgenis.emx2.graphql;

import com.fasterxml.jackson.core.JsonProcessingException;
import graphql.schema.*;
import jakarta.servlet.http.Part;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import org.molgenis.emx2.BinaryFileWrapper;
import org.molgenis.emx2.utils.MolgenisObjectMapper;

public class GraphqlCustomTypes {
private static final MolgenisObjectMapper objectMapper = MolgenisObjectMapper.INTERNAL;

private GraphqlCustomTypes() {
// hide constructor
}

public static final GraphQLScalarType GraphQLJsonAsString =
GraphQLScalarType.newScalar()
.name("JsonString")
.description("A JSON represented as string")
.coercing(
new Coercing<String, String>() {

@Override
public String serialize(Object dataFetcherResult) {
// Convert Java object to JSON string
try {
return objectMapper.getWriter().writeValueAsString(dataFetcherResult);
} catch (JsonProcessingException e) {
throw new CoercingSerializeException(
"Unable to serialize to JSON string: " + e.getMessage());
}
}

@Override
public String parseValue(Object input) {
// Pass-through parsing (only used for input values)
return input.toString();
}

@Override
public String parseLiteral(Object input) {
// Pass-through literal parsing
if (input instanceof graphql.language.StringValue) {
return ((graphql.language.StringValue) input).getValue();
}
throw new CoercingParseLiteralException("Value is not a valid JSON string");
}
})
.build();

// thanks to https://stackoverflow.com/questions/57372259/how-to-upload-files-with-graphql-java
public static final GraphQLScalarType GraphQLFileUpload =
GraphQLScalarType.newScalar()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static org.molgenis.emx2.graphql.GraphqlApiMutationResult.Status.SUCCESS;
import static org.molgenis.emx2.graphql.GraphqlApiMutationResult.typeForMutationResult;
import static org.molgenis.emx2.graphql.GraphqlConstants.*;
import static org.molgenis.emx2.graphql.GraphqlCustomTypes.GraphQLJsonAsString;
import static org.molgenis.emx2.sql.SqlQuery.*;

import graphql.Scalars;
Expand Down Expand Up @@ -187,6 +188,10 @@ private void createTableField(Column col, GraphQLObjectType.Builder tableBuilder
tableBuilder.field(
GraphQLFieldDefinition.newFieldDefinition().name(id).type(Scalars.GraphQLString));
break;
case JSON:
tableBuilder.field(
GraphQLFieldDefinition.newFieldDefinition().name(id).type(GraphQLJsonAsString));
break;
case STRING_ARRAY:
case EMAIL_ARRAY:
case HYPERLINK_ARRAY:
Expand All @@ -196,7 +201,6 @@ private void createTableField(Column col, GraphQLObjectType.Builder tableBuilder
case DATETIME_ARRAY:
case PERIOD_ARRAY:
case UUID_ARRAY:
case JSONB_ARRAY:
tableBuilder.field(
GraphQLFieldDefinition.newFieldDefinition()
.name(id)
Expand Down Expand Up @@ -507,6 +511,8 @@ private GraphQLScalarType graphQLTypeOf(Column col) {
return GraphQLLong;
case DECIMAL, DECIMAL_ARRAY:
return Scalars.GraphQLFloat;
case JSON:
return GraphQLJsonAsString;
case DATE,
DATETIME,
PERIOD,
Expand Down Expand Up @@ -908,6 +914,7 @@ private GraphQLInputType getGraphQLInputType(ColumnType columnType) {
case INT_ARRAY -> GraphQLList.list(Scalars.GraphQLInt);
case LONG_ARRAY -> GraphQLList.list(GraphQLLong);
case DECIMAL_ARRAY -> GraphQLList.list(Scalars.GraphQLFloat);
case JSON -> GraphqlCustomTypes.GraphQLJsonAsString;
case STRING_ARRAY,
TEXT_ARRAY,
DATE_ARRAY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,48 @@ public void testTableType() throws IOException {
assertEquals("ONTOLOGIES", result.at("/_schema/tables/3/tableType").asText());
}

@Test
public void testJsonType() throws IOException {
try {
Schema myschema = database.dropCreateSchema("testJsonType");
myschema.create(
table("TestJson", column("name").setPkey(), column("json").setType(ColumnType.JSON)));

grapql = new GraphqlApiFactory().createGraphqlForSchema(myschema, taskService);

Table table = myschema.getTable("TestJson");
String value = "{\"name\":\"bofke\"}";
table.insert(row("name", "test", "json", value));

assertEquals(value, execute("{TestJson{json}}").at("/TestJson/0/json").asText());

String value2 = "{\"name\":\"bofke2\"}";
Map data = new LinkedHashMap();
data.put("name", "test");
data.put("json", value2);
grapql.execute(
new ExecutionInput.Builder()
.query("mutation update($value:[TestJsonInput]){update(TestJson:$value){message}}")
.variables(Map.of("value", data))
.build());

assertEquals(value2, execute("{TestJson{json}}").at("/TestJson/0/json").asText());
assertEquals(
value2,
execute(
"{TestJson(filter:{json:{equals:\"{\\\"name\\\": \\\"bofke2\\\"}\"}}){json}}") // notice the extra space!
.at("/TestJson/0/json")
.asText());
assertEquals(
value2,
execute("{TestJson(filter:{json:{like:\"bofke2\"}}){json}}") // more useful
.at("/TestJson/0/json")
.asText());
} finally {
grapql = new GraphqlApiFactory().createGraphqlForSchema(schema, taskService);
}
}

@Test
public void testFileType() throws IOException {
try {
Expand Down
Loading