Skip to content

Commit

Permalink
Added integer/decimal/boolean to LiteralDataNodeType
Browse files Browse the repository at this point in the history
  • Loading branch information
binh-vu committed Jun 19, 2024
1 parent dd7f80e commit ef07f76
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 61 deletions.
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "web-sand"
version = "4.1.4"
version = "4.2.0"
description = "UI for browsing/editing semantic descriptions"
authors = ["Binh Vu <[email protected]>"]
repository = "https://github.com/usc-isi-i2/sand"
Expand All @@ -15,7 +15,7 @@ sand = 'sand.__main__:cli'
[tool.poetry.dependencies]
python = ">= 3.10, < 3.14"
kgdata = "^7.0.4"
sem-desc = "^6.11.2"
sem-desc = "^6.12.0"
peewee = "^3.15.2"
Flask = "^2.2.2"
python-dotenv = ">= 0.19.0, < 0.20.0"
Expand Down
24 changes: 24 additions & 0 deletions sand/deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,30 @@ def deserialize_graph(
f"expect `uri` to be a string but get {type(node['value']['uri'])}"
)
node_value = node["value"]["uri"]
elif datatype == LiteralNodeDataType.Integer:
if "value" not in node["value"]:
raise ValueError(f"missing `value` in literal_node")
if not isinstance(node["value"]["value"], int):
raise ValueError(
f"expect `value` to be an integer but get {type(node['value']['value'])}"
)
node_value = str(node["value"]["value"])
elif datatype == LiteralNodeDataType.Decimal:
if "value" not in node["value"]:
raise ValueError(f"missing `value` in literal_node")
if not isinstance(node["value"]["value"], float):
raise ValueError(
f"expect `value` to be a decimal but get {type(node['value']['value'])}"
)
node_value = str(node["value"]["value"])
elif datatype == LiteralNodeDataType.Boolean:
if "value" not in node["value"]:
raise ValueError(f"missing `value` in literal_node")
if not isinstance(node["value"]["value"], bool):
raise ValueError(
f"expect `value` to be a bool but get {type(node['value']['value'])}"
)
node_value = "true" if node["value"]["value"] else "false"
else:
raise ValueError(f"unknown datatype {node['value']['type']}")

Expand Down
2 changes: 1 addition & 1 deletion www/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sand",
"version": "2.4.1",
"version": "2.5.0",
"private": true,
"dependencies": {
"@ant-design/colors": "^6.0.0",
Expand Down
25 changes: 20 additions & 5 deletions www/src/models/sm/SMGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ export interface DataNode {
readonly nodetype: "data_node";
}

export type LiteralDataType = "entity-id" | "string";
export type LiteralDataType =
| "string"
| "entity-id"
| "integer"
| "decimal"
| "boolean";

export interface LiteralNode {
id: string;
Expand All @@ -32,6 +37,14 @@ export interface LiteralNode {
type: "string";
value: string;
}
| {
type: "integer" | "decimal";
value: number;
}
| {
type: "boolean";
value: boolean;
}
| {
type: "entity-id";
id: string;
Expand Down Expand Up @@ -170,13 +183,15 @@ export class SMGraph {
);
nodeByColumnIndex = (columnIndex: number): DataNode =>
this.nodes[this.column2nodeIndex[columnIndex]] as DataNode;
nodeByEntityId = (id: string): LiteralNode =>
literalNodeByValue = (
value: string | number | boolean
): LiteralNode | undefined =>
this.nodes.filter(
(node) =>
node.nodetype === "literal_node" &&
node.value.type === "entity-id" &&
node.value.id === id
)[0] as LiteralNode;
((node.value.type === "entity-id" && node.value.id === value) ||
(node.value.type !== "entity-id" && node.value.value === value))
)[0] as LiteralNode | undefined;

edge = (source: string, target: string) =>
this.edges.filter((e) => e.source === source && e.target === target)[0];
Expand Down
207 changes: 154 additions & 53 deletions www/src/pages/table/forms/NodeForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { WithStyles, withStyles } from "@material-ui/styles";
import { Button, Form, Modal, Radio, Space, Switch, Typography } from "antd";
import {
Button,
Form,
Modal,
Input,
Radio,
Space,
Switch,
Typography,
} from "antd";
import { observer } from "mobx-react";
import React, { useEffect, useMemo, useState } from "react";
import { useStores } from "../../../models";
Expand All @@ -11,6 +20,7 @@ import {
SMEdge,
SMNode,
SMNodeType,
LiteralDataType,
} from "../../../models/sm";
import { NodeSearchComponent, SearchValue } from "../NodeSearchComponent";
import {
Expand Down Expand Up @@ -138,60 +148,98 @@ export const ClassNodeSubForm = observer(
export const LiteralNodeSubForm = observer(
(props: { sm: SemanticModel; node?: LiteralNode; onDone: () => void }) => {
const { entityStore } = useStores();
const [id, setId] = useState<string | undefined>(
props.node?.value?.type === "entity-id" ? props.node.value.id : undefined
const [datatype, setDatatype] = useState<LiteralDataType>(
props.node?.value?.type || "entity-id"
);
const [value, setValue] = useState<string | undefined>(
props.node?.value?.type === "entity-id"
? props.node.value.id
: props.node?.value !== undefined
? props.node.value.value.toString()
: undefined
);
const [isInContext, setIsInContext] = useState(
props.node !== undefined && props.node.nodetype === "literal_node"
? props.node.isInContext
: false
props.node !== undefined ? props.node.isInContext : false
);

const duplicatedId = useMemo(
const duplicatedValue = useMemo(
() =>
id !== undefined &&
value !== undefined &&
((props.node === undefined &&
props.sm.graph.nodeByEntityId(id) !== undefined) ||
props.sm.graph.literalNodeByValue(value) !== undefined) ||
(props.node !== undefined &&
props.sm.graph.nodeByEntityId(id)?.id !== props.node.id)),
[props.sm.graph.version, id]
props.sm.graph.literalNodeByValue(value)?.id !== props.node.id)),
[props.sm.graph.version, value]
);

if (props.node !== undefined && props.node.value.type === "string") {
return <div>Not Implemented Yet</div>;
}

const onSave = () => {
if (id === undefined) return;
if (duplicatedId) return;
if (value === undefined) return;
if (duplicatedValue) return;

const ent = entityStore.get(id)!;
if (datatype === "entity-id") {
const ent = entityStore.get(value)!;

if (props.node === undefined) {
// always create a new node
props.sm.graph.addLiteralNode({
id: props.sm.graph.nextNodeId(),
value: {
type: "entity-id",
id: id,
uri: ent.uri,
},
label: ent.readableLabel,
nodetype: "literal_node",
isInContext: isInContext,
});
if (props.node === undefined) {
// always create a new node
props.sm.graph.addLiteralNode({
id: props.sm.graph.nextNodeId(),
value: {
type: datatype,
id: value,
uri: ent.uri,
},
label: ent.readableLabel,
nodetype: "literal_node",
isInContext: isInContext,
});
} else {
// we update existing node, it can be
props.sm.graph.updateLiteralNode(props.node.id, {
value: {
type: "entity-id",
id: value,
uri: ent.uri,
},
label: ent.readableLabel,
nodetype: "literal_node",
isInContext: isInContext,
});
}
} else {
// we update existing node, it can be
props.sm.graph.updateLiteralNode(props.node.id, {
value: {
type: "entity-id",
id: id,
uri: ent.uri,
},
label: ent.readableLabel,
nodetype: "literal_node",
isInContext: isInContext,
});
let normValue = undefined;

if (datatype === "boolean") {
normValue = { type: datatype, value: value === "true" };
} else if (datatype === "string") {
normValue = { type: datatype, value };
} else {
normValue =
datatype === "integer"
? { type: datatype, value: parseInt(value) }
: { type: datatype, value: parseFloat(value) };
if (Number.isNaN(normValue.value)) {
return;
}
}

if (props.node === undefined) {
// always create a new node
props.sm.graph.addLiteralNode({
id: props.sm.graph.nextNodeId(),
value: normValue!,
label: value,
nodetype: "literal_node",
isInContext: isInContext,
});
} else {
// we update existing node, it can be
props.sm.graph.updateLiteralNode(props.node.id, {
value: normValue,
label: value,
nodetype: "literal_node",
isInContext: isInContext,
});
}
}

props.onDone();
Expand All @@ -206,28 +254,81 @@ export const LiteralNodeSubForm = observer(
return (
props.node === undefined ||
props.node.isInContext !== isInContext ||
(props.node.value.type === "entity-id" && props.node.value.id !== id)
(props.node.value.type === "entity-id" &&
props.node.value.id !== value) ||
(props.node.value.type !== "entity-id" &&
props.node.value.value.toString() !== value)
);
};

let valueInputForm = undefined;

if (datatype === "entity-id") {
valueInputForm = (
<EntitySearchComponent
value={value}
onSelect={setValue}
onDeselect={() => setValue(undefined)}
/>
);
} else if (
datatype === "string" ||
datatype === "integer" ||
datatype === "decimal"
) {
valueInputForm = (
<Input
value={value}
type={datatype === "integer" ? "number" : "text"}
onChange={(event) =>
setValue(
event.target.value.length > 0 ? event.target.value : undefined
)
}
/>
);
} else if (datatype === "boolean") {
valueInputForm = (
<Radio.Group
value={value}
onChange={(event) => setValue(event.target.value)}
>
<Radio value="true">True</Radio>
<Radio value="false">False</Radio>
</Radio.Group>
);
}

return (
<>
<Form.Item
label="Data Type"
validateStatus={duplicatedValue ? "error" : undefined}
help={duplicatedValue ? "Entity's already in the graph" : undefined}
>
<Radio.Group
value={datatype}
onChange={(event) => setDatatype(event.target.value)}
>
<Radio value="entity-id">Entity Id</Radio>
<Radio value="string">String</Radio>
<Radio value="integer">Integer</Radio>
<Radio value="decimal">Decimal</Radio>
<Radio value="boolean">Boolean</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label={
<Typography.Text
copyable={id !== undefined ? { text: id } : undefined}
copyable={value !== undefined ? { text: value } : undefined}
>
Entity
{datatype === "entity-id" ? "Entity" : "Value"}
</Typography.Text>
}
validateStatus={duplicatedId ? "error" : undefined}
help={duplicatedId ? "Entity's already in the graph" : undefined}
validateStatus={duplicatedValue ? "error" : undefined}
help={duplicatedValue ? "Entity's already in the graph" : undefined}
>
<EntitySearchComponent
value={id}
onSelect={setId}
onDeselect={() => setId(undefined)}
/>
{valueInputForm}
</Form.Item>
<Form.Item label="Is In Context?">
<Switch
Expand All @@ -240,7 +341,7 @@ export const LiteralNodeSubForm = observer(
<Button
type="primary"
onClick={onSave}
disabled={id === undefined || duplicatedId || !isModified()}
disabled={value === undefined || duplicatedValue || !isModified()}
>
Save
</Button>
Expand Down

0 comments on commit ef07f76

Please sign in to comment.