From 4455bdf6547d28eb89910388e114952614f8115c Mon Sep 17 00:00:00 2001 From: Henning Bredel Date: Tue, 10 Sep 2024 10:28:46 +0200 Subject: [PATCH] Read and resolve schema from open API --- .../client/js/plugins/LitterAssessment.jsx | 166 +++++++++++++----- .../geonode/less/_litterassessment.less | 13 ++ 2 files changed, 132 insertions(+), 47 deletions(-) diff --git a/geonode_mapstore_client/client/js/plugins/LitterAssessment.jsx b/geonode_mapstore_client/client/js/plugins/LitterAssessment.jsx index 997f534d07..6e4abdb980 100644 --- a/geonode_mapstore_client/client/js/plugins/LitterAssessment.jsx +++ b/geonode_mapstore_client/client/js/plugins/LitterAssessment.jsx @@ -5,6 +5,8 @@ import { createSelector } from "reselect"; import { Glyphicon } from "react-bootstrap"; import assign from "object-assign"; +import jp from "jsonpath"; + import axios from "@mapstore/framework/libs/ajax"; import { createPlugin } from "@mapstore/framework/utils/PluginsUtils"; import { setControlProperty } from "@mapstore/framework/actions/controls"; @@ -12,35 +14,42 @@ import { addAuthenticationParameter } from "@mapstore/framework/utils/SecurityUt import Message from "@mapstore/framework/components/I18N/Message"; import controls from "@mapstore/framework/reducers/controls"; -import { - parseDevHostname, - getGeoNodeLocalConfig, -} from '@js/utils/APIUtils'; +import { parseDevHostname, getGeoNodeLocalConfig } from "@js/utils/APIUtils"; import Button from "@js/components/Button"; import OverlayContainer from "@js/components/OverlayContainer"; import { getResourceId } from "@js/selectors/resource"; import Form from "@rjsf/core"; - const log = (type) => console.log.bind(console, type); +async function getUiSchemas() { + const baseUrl = getGeoNodeLocalConfig( + "geoNodeSettings.staticPath", + "/static/" + ); + return axios.get(baseUrl + "litterassessment/litterassessmentConfig.json"); +} + +async function getOpenApiSchema() { + const url = parseDevHostname("/litterassessment"); + return axios.get(`${url}/openapi.json`); +} + async function getModels() { - const configPath = "litterassessment/litterassessmentConfig.json"; - const configUrl = - getGeoNodeLocalConfig("geoNodeSettings.staticPath", "/static/") + - configPath; + const url = parseDevHostname("/litterassessment"); + const configUrl = `${url}/models`; return axios.get(configUrl); } -function triggerAiInference({ formData }) { - +function triggerAiInference(selectedModel, { formData }) { const headers = { - "Content-type": "application/x-www-form-urlencoded;charset=utf-8", + "Content-type": "application/json", Accept: "application/json", }; - const url = parseDevHostname("/litterassessment/") + const path = `/litterassessment/models/${selectedModel}/` + const url = parseDevHostname(path); return axios .post(url, formData, headers) .then((response) => { @@ -77,32 +86,84 @@ function toWmsUrl(wmsLayerOptions, securityToken) { return new URL(`${url}?${query}`).toString(); } -function LitterAssessment({ enabled, pk, wmsLayers = [], securityToken, onClose }) { - const [models, setModels] = useState({ - "model a": { jsonSchema: {}, uiSchema: {} }, - "model b": { jsonSchema: {}, uiSchema: {} }, - }); - +function LitterAssessment({ + enabled, + pk, + wmsLayers = [], + securityToken, + onClose, +}) { + const [models, setModels] = useState({}); + const [uischemas, setSchemas] = useState({}); + const [jsonSchemas, setJsonSchemas] = useState({}); const [selectedModel, setSelectedModel] = useState(""); const isMounted = useRef(false); useEffect(() => { isMounted.current = true; - return () => { - isMounted.current = false; - }; - }, []); - useEffect(() => { + getOpenApiSchema() + .then(async (response) => { + const openApiSchema = response.data; + const re = new RegExp("/models/"); + const models = {}; + Object.entries(openApiSchema.paths).forEach(async ([k, v]) => { + if (k.includes("/models/") && k !== "/models/") { + const key = k.replace(re, ""); + const modelName = key.endsWith("/") ? key.slice(0, -1) : key; + + const resolve = function (ref) { + if (ref === undefined || !ref.startsWith("#/")) { + return undefined; + } + const jsonPath = ref.replace("#/", "$.").replaceAll("/", "."); + return jp.query(openApiSchema, jsonPath)[0] || undefined; + }; + + const responseBody = + v.post?.requestBody?.content["application/json"]; + const schema = resolve(responseBody?.schema["$ref"]); + + jp.apply(schema, "$.properties.*['$ref']", (ref) => + resolve(ref) + ).forEach((rr) => { + schema.properties[rr.path.slice(-1)] = rr.value; + }); + + models[modelName] = schema; + } + }); + setJsonSchemas(models); + }) + .catch((err) => { + console.error("could not get OpenAPI schema!", err); + }); + + getUiSchemas() + .then((response) => { + const schemas = response.data.schemas; + setSchemas(schemas); + }) + .catch((err) => { + console.error("could get schemas from config!", err); + }); + getModels() .then((response) => { - const models = response.data.models; - setSelectedModel(Object.keys(models)[0] || null) + const models = response.data.models?.reduce( + (acc, m) => ({ ...acc, [m.name]: m }), + {} + ); + setSelectedModel(Object.keys(models)[0] || null); setModels(models); }) .catch((err) => { console.error("could get models from config!", err); }); + + return () => { + isMounted.current = false; + }; }, []); const wmsLayer = wmsLayers?.length @@ -127,31 +188,42 @@ function LitterAssessment({ enabled, pk, wmsLayers = [], securityToken, onClose - { + setSelectedModel(event.target.value); + }} value={selectedModel} > - {Object.keys(models) - .map((modelName, index) => ( - - ))} + {Object.keys(models).map((model, index) => ( + + ))} -
triggerAiInference(e)} - onError={log("errors")} - > -
- -
-
+
+

Description

+ + {models[selectedModel]?.description} + +
+ +
+
triggerAiInference(selectedModel, e)} + onError={log("errors")} + > +
+ +
+
+
diff --git a/geonode_mapstore_client/client/themes/geonode/less/_litterassessment.less b/geonode_mapstore_client/client/themes/geonode/less/_litterassessment.less index a85bdbe15f..8fd06f8f6c 100644 --- a/geonode_mapstore_client/client/themes/geonode/less/_litterassessment.less +++ b/geonode_mapstore_client/client/themes/geonode/less/_litterassessment.less @@ -43,6 +43,19 @@ #model-select { margin-left: 1em; } + + .model-description { + + } + + .model-input { + + .submit-btn { + margin-top: 1em; + } + + } + } .ms-popover { display: inline-block;