cozemble / monorepo

A data and process canvas
https://cozemble.com
Apache License 2.0
13 stars 1 forks source link

Explore changing the data editor to be jsonschema driven #6

Closed mike-hogan closed 1 year ago

mike-hogan commented 1 year ago

Change the Record Editor to be driven off of jsonschema definition instead of Models

This will require mapping Models to JSON Schema definitions, and mapping DataRecords back and forth between plain json.

JSON Schema supports regex validation statements, some number validations, etc, and supports extension. So there might be mileage in this, but it might be best regardless for errors to be passed down thru the component tree, and validation be done about the root UI element, so custom validation is possible.

Question is: why am I think about this? If the component uses something standard, it might be more generally useful, and folks might adopt it more, use it more, test it more, augment it more. It just “feels” better to play with open standards where possible.

If a jsonschema driven editor, with extensions like document generation etc, can deliver the UX we’re aiming for, then it makes sense to do that.

mike-hogan commented 1 year ago

Here is a json schema for the invoice test model we have been using. Created with the help of https://bjdash.github.io/JSON-Schema-Builder/

{
    "type": "object",
    "properties": {
        "invoiceNumber": {
            "type": "string",
            "description": "Invoice Number"
        },
        "description": {
            "type": "string",
            "description": "Description of invoice"
        },
        "lineItems": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "quantity": {
                        "type": "integer",
                        "minimum": 0
                    },
                    "item": {
                        "type": "string"
                    },
                    "price": {
                        "type": "number",
                        "minimum": 0,
                        "multipleOf": 0.01
                    }
                },
                "required": [
                    "quantity",
                    "item",
                    "price"
                ]
            }
        },
        "customer": {
            "type": "object",
            "properties": {
                "firstName": {
                    "type": "string"
                },
                "lastName": {
                    "type": "string"
                },
                "email": {
                    "type": "string",
                    "pattern": "^\\w+@[a-zA-Z_]+?\\.[a-zA-Z]{2,3}$"
                },
                "phone": {
                    "type": "string",
                    "pattern": "^[\\+]?[(]?[0-9]{3}[)]?[-\\s\\.]?[0-9]{3}[-\\s\\.]?[0-9]{4,6}$"
                },
                "address": {
                    "type": "object",
                    "properties": {
                        "line1": {
                            "type": "string"
                        },
                        "line2": {
                            "type": "string"
                        },
                        "postcode": {
                            "type": "string"
                        }
                    },
                    "required": [
                        "line1",
                        "postcode"
                    ]
                }
            },
            "required": [
                "firstName",
                "email",
                "phone",
                "address"
            ]
        }
    },
    "required": [
        "invoiceNumber",
        "customer"
    ]
}
mike-hogan commented 1 year ago

You can live test the schema using this React implementation of the same idea: https://rjsf-team.github.io/react-jsonschema-form/

mike-hogan commented 1 year ago

Here is a jsonschema for an invoice containing many customers, with each customer containing many addresses. Silly example, but good to exercise our code:

{
    "type": "object",
    "properties": {
        "invoiceNumber": {
            "type": "string",
            "description": "user name"
        },
        "customers": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "firstName": {
                        "type": "string"
                    },
                    "lastName": {
                        "type": "string"
                    },
                    "email": {
                        "type": "string",
                        "pattern": "^\\w+@[a-zA-Z_]+?\\.[a-zA-Z]{2,3}$"
                    },
                    "addresses": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "line1": {
                                    "type": "string"
                                },
                                "line2": {
                                    "type": "string"
                                },
                                "postcode": {
                                    "type": "string"
                                }
                            },
                            "required": [
                                "line1",
                                "postcode"
                            ]
                        }
                    }
                },
                "required": [
                    "firstName",
                    "email",
                    "addresses"
                ]
            }
        }
    },
    "required": [
        "invoiceNumber"
    ]
}
mike-hogan commented 1 year ago

Comments on current progress (which is awesome!)

Metehan-Altuntekin commented 1 year ago

I have managed to set up a validation system with an errors store in this commit. The error object is the same structure as the record object and therefore each error should be passed to components recursively.

Metehan-Altuntekin commented 1 year ago

JSON Schema seems to not have a support for formula fields. However I could find a way of handling them with giving custom properties to such fields. Probably less than ideal.

mike-hogan commented 1 year ago

Next steps:

mike-hogan commented 1 year ago

Here is the schema for the task above that proves that dependent formulas work:

{
    "type": "object",
    "properties": {
        "invoiceId": {
            "type": "string",
            "description": "user name"
        },
        "prices": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "item": {
                        "type": "string"
                    },
                    "price": {
                        "type": "number"
                    }
                },
                "required": [
                    "item",
                    "price"
                ]
            }
        },
        "items": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "item": {
                        "type": "string"
                    },
                    "quantity": {
                        "type": "number"
                    },
                    "total": {
                        "type": "number"
                    }
                },
                "required": [
                    "item",
                    "quantity"
                ]
            }
        },
        "subtotal": {
            "type": "number"
        },
        "tenPercentSalesTax": {
            "type": "number"
        },
        "total": {
            "type": "number"
        }
    },
    "required": [
        "invoiceId"
    ]
}

And here is some sample data:

{
  "invoiceId": "invoice#22",
  "prices": [
    {
      "item": "apple",
      "price": 0.89
    },
    {
      "item": "banana",
      "price": 0.76
    }
  ],
  "items": [
    {
      "item": "apple",
      "quantity": 2
    },
    {
      "item": "banana",
      "quantity": 4
    }
  ]
}

What this means is that the cost for one apple is 0.89 and the cost for one banana is 0.76. This is meant to be reference data. The items array is the shopping cart. In contains 2 apples and 4 bananas. The total for the apple items line should be 2 x 0.89 and the total for the banana items line should be 4 x 0.76. If I change the cost of an apple or the cost of a banana then these should recalculate. if I change the quantity the associated line should recalculate. The subtotal if the sum of all the total of all the items. The tenPercentSalesTax is 10% of subtotal. Total is that added to the subtotal.

Metehan-Altuntekin commented 1 year ago

Cozemble specific properties like formula are moved into a coz object in the model. That way we will make it more compatible for non Cozemble use cases.

mike-hogan commented 1 year ago

New incarnation is #45