diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cc4dac..a72b2c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bumped Comunica version from 3.2.3 to 4.2.0, resulting in increased execution speed of some queries (#212). Breaking change for typed literals in query configuration, "variables" field; see README for new syntax. - Removed not supported cosmetic fields from configuration file (#18). -- SPARQL edit fields have syntax coloring and validation now (#143). +- JSON and SPARQL edit fields have syntax coloring and validation now (#142 and #143). ### Fixed diff --git a/main/package-lock.json b/main/package-lock.json index d18dd88..994edd2 100644 --- a/main/package-lock.json +++ b/main/package-lock.json @@ -17,6 +17,7 @@ "@rdfjs/types": "^1.1.0", "@triply/yasqe": "^4.2.28", "events": "^3.3.0", + "jsonlint-mod": "^1.7.6", "prop-types": "^15.8.1", "query-sparql-link-traversal-solid-no-default-predicates": "^0.1.1", "rdf-string": "^1.6.3", @@ -15959,6 +15960,93 @@ "readable-stream": "^4.0.0" } }, + "node_modules/jsonlint-mod": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/jsonlint-mod/-/jsonlint-mod-1.7.6.tgz", + "integrity": "sha512-oGuk6E1ehmIpw0w9ttgb2KsDQQgGXBzZczREW8OfxEm9eCQYL9/LCexSnh++0z3AiYGcXpBgqDSx9AAgzl/Bvg==", + "dependencies": { + "chalk": "^2.4.2", + "JSV": "^4.0.2", + "underscore": "^1.9.1" + }, + "bin": { + "jsonlint": "lib/cli.js" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/jsonlint-mod/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jsonlint-mod/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jsonlint-mod/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/jsonlint-mod/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/jsonlint-mod/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/jsonlint-mod/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/jsonlint-mod/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -15977,6 +16065,14 @@ "node": "*" } }, + "node_modules/JSV": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", + "integrity": "sha512-ZJ6wx9xaKJ3yFUhq5/sk82PJMuUyLk277I8mQeyDgCTjGdjWJIvPfaU5LIXaMuaN2UO1X3kZH4+lgphublZUHw==", + "engines": { + "node": "*" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -19030,6 +19126,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "license": "MIT" + }, "node_modules/undici": { "version": "5.29.0", "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", diff --git a/main/package.json b/main/package.json index e925b89..7241fee 100644 --- a/main/package.json +++ b/main/package.json @@ -22,6 +22,7 @@ "@rdfjs/types": "^1.1.0", "@triply/yasqe": "^4.2.28", "events": "^3.3.0", + "jsonlint-mod": "^1.7.6", "prop-types": "^15.8.1", "query-sparql-link-traversal-solid-no-default-predicates": "^0.1.1", "rdf-string": "^1.6.3", diff --git a/main/src/components/CustomQueryEditor/customEditor.jsx b/main/src/components/CustomQueryEditor/customEditor.jsx index 9ace8c0..ad0ce0e 100644 --- a/main/src/components/CustomQueryEditor/customEditor.jsx +++ b/main/src/components/CustomQueryEditor/customEditor.jsx @@ -15,6 +15,38 @@ import { getDefaultSession } from "@inrupt/solid-client-authn-browser"; import { SparqlEditField } from "./sparqlEditField"; +import { JsonEditField } from "./jsonEditField"; + +const defaultSparqlQuery = `SELECT ?s ?p ?o +WHERE { + ?s ?p ?o +}`; +const defaultSparqlQueryIndexSources = `PREFIX rdfs: + +SELECT ?source +WHERE { + ?s rdfs:seeAlso ?source +}`; +const defaultSparqlQueryIndirectVariables = `PREFIX schema: + +SELECT DISTINCT ?genre +WHERE { + ?list schema:genre ?genre +} +ORDER BY ?genre`; + +const defaultExtraComunicaContext = JSON.stringify({ "lenient": true }, null, 2); +const defaultTemplateOptions = JSON.stringify( + { + "variableOne": [ + "option1", + "(etc...)" + ], + "(etc...)": [] + }, null, 2); +const defaultAskQueryDetails = JSON.stringify({ "trueText": "this displays when true.", "falseText": "this displays when false." }, null, 2); +const defaultHttpProxiesDetails = JSON.stringify([{ "urlStart": "http://www.example.com/path-xyz", "httpProxy": "http://myproxy.org/" }], null, 2); + export default function CustomEditor(props) { const session = getDefaultSession(); const loggedIn = session.info.isLoggedIn; @@ -26,180 +58,88 @@ export default function CustomEditor(props) { description: '', source: '', queryString: '', - comunicaContext: '', comunicaContextCheck: false, sourceIndexCheck: false, - askQueryCheck: false, - httpProxiesCheck: false, directVariablesCheck: false, indirectVariablesCheck: false, + askQueryCheck: false, + httpProxiesCheck: false, }); const [validFlags, setValidFlags] = useState({}); - const [errorWhileLoading, setErrorWhileLoading] = useState(""); const [parsingError, setParsingError] = useState(""); - const [parsingErrorComunica, setParsingErrorComunica] = useState(false); - const [parsingErrorAsk, setParsingErrorAsk] = useState(false); - const [parsingErrorHttpProxies, setParsingErrorHttpProxies] = useState(false); - const [parsingErrorTemplate, setParsingErrorTemplate] = useState(false); - - // Default placeholders for the forms - const defaultSparqlQuery = `SELECT ?s ?p ?o -WHERE { - ?s ?p ?o -}`; - const defaultSparqlQueryIndexSources = `PREFIX rdfs: - -SELECT ?source -WHERE { - ?s rdfs:seeAlso ?source -}`; - const defaultSparqlQueryIndirectVariables = `PREFIX schema: - -SELECT DISTINCT ?genre -WHERE { - ?list schema:genre ?genre -} -ORDER BY ?genre`; - const [indirectVariableSourceList, setIndirectVariableSourceList] = useState([defaultSparqlQueryIndirectVariables]); - - const defaultExtraComunicaContext = JSON.stringify({ "lenient": true }, null, 2); - const defaultAskQueryDetails = JSON.stringify({ "trueText": "this displays when true.", "falseText": "this displays when false." }, null, 2); - const defaultHttpProxiesDetails = JSON.stringify([{ "urlStart": "http://www.example.com/path-xyz", "httpProxy": "http://myproxy.org/" }], null, 2); - const defaultTemplateOptions = JSON.stringify( - { - "variableOne": [ - "option1", - "(etc...)" - ], - "(etc...)": [] - }, null, 5); - + const [indirectVariablesQueryList, setIndirectVariablesQueryList] = useState([defaultSparqlQueryIndirectVariables]); useEffect(() => { try { let searchParams; if (props.newQuery) { searchParams = new URLSearchParams(location.search); - } else { - const edittingQuery = configManager.getQueryById(props.id); - searchParams = edittingQuery.searchParams; + } else { + const editingQuery = configManager.getQueryById(props.id); + searchParams = editingQuery.searchParams; } const obj = {} searchParams.forEach((value, key) => { obj[key] = value; - }) - + }); if (obj.indirectQueries) { - setIndirectVariableSourceList(JSON.parse(obj.indirectQueries)); + setIndirectVariablesQueryList(JSON.parse(obj.indirectQueries)); } setFormData(obj); - } catch (error) { setErrorWhileLoading("Apologies, something went wrong with the loading of your custom query..."); } }, [location.search]); - - // This function handles the submission of the form. Both for editting as for creation. This distinction is made by the `props.newQuery`. - const handleSubmit = async (event) => { - event.preventDefault(); - - if (Object.values(validFlags).some((x) => x === false)) { - setParsingError("Invalid query. Check the SPARQL fields."); - } else if (parsingErrorComunica || parsingErrorAsk || parsingErrorHttpProxies || parsingErrorTemplate) { - setParsingError("Invalid query. Check the JSON fields."); - } else { - setParsingError(""); - const htmlFormData = new FormData(event.currentTarget); - let jsonData = Object.fromEntries(htmlFormData.entries()); - // LOG console.log(`----- jsonData (from HTML form data):\n${JSON.stringify(jsonData, null, 2)}`); - // LOG console.log(`----- formData (from state):\n${JSON.stringify(formData, null, 2)}`); - - // not all required properties are in jsonDataFromHtml; add them here from formData - const additionalProperties = ["queryString", "indexSourceQuery"]; - for (const p of additionalProperties) { - if (formData.hasOwnProperty(p)) { - jsonData[p] = formData[p]; - } - } - - if (jsonData.indirectVariablesCheck) { - jsonData.indirectQueries = JSON.stringify(indirectVariableSourceList); + useEffect(() => { + let newErrorMessage = ""; + // only one error message is set, so the first one that occurs is the one that is shown + if (validFlags['queryString'] === false) { + newErrorMessage = "Invalid SPARQL query."; + } + if (!newErrorMessage && isChecked(formData.comunicaContextCheck)) { + if (validFlags['comunicaContext'] === false) { + newErrorMessage = "Invalid Comunica context configuration."; } - // LOG console.log(`----- jsonData (finally):\n${JSON.stringify(jsonData, null, 2)}`); - - const searchParams = new URLSearchParams(jsonData); - jsonData.searchParams = searchParams; - - if (props.newQuery) { - navigate({ search: searchParams.toString() }); - - configManager.addNewQueryGroup('cstm', 'Custom queries', 'EditNoteIcon'); - addQuery(jsonData); + } + if (!newErrorMessage && isChecked(formData.sourceIndexCheck)) { + if (validFlags['indexSourceQuery'] === false) { + newErrorMessage = "Invalid indirect sources SPARQL query."; } - else { - const customQuery = configManager.getQueryById(props.id); - updateQuery(jsonData, customQuery); + } + if (!newErrorMessage && isChecked(formData.directVariablesCheck)) { + if (validFlags['variables'] === false) { + newErrorMessage = "Invalid fixed templated variables specification."; } } - }; - - // These functions handle the entry changes from the user's input in the form - const handleChange = (event) => { - const { name, value, validFlag } = event.target; - const indirectVariablesQueryRegex = /indirectVariablesQuery-(\d)+/; - const result = indirectVariablesQueryRegex.exec(name); - if (result) { - const index = result[1]; - handleIndirectVariablesChange(event, index); - } else { - setFormData((prevFormData) => ({ - ...prevFormData, - [name]: value - })); + if (!newErrorMessage && isChecked(formData.indirectVariablesCheck)) { + for (const [key, value] of Object.entries(validFlags)) { + if (key.startsWith('indirectVariablesQuery-') && value === false) { + newErrorMessage = `Invalid SPARQL query to retrieve variable(s) from source(s).`; + break; + } + } } - if (validFlag !== undefined) { - setValidFlags((prevValidFlags) => ({ - ...prevValidFlags, - [name]: validFlag - })); + if (!newErrorMessage && isChecked(formData.askQueryCheck)) { + if (validFlags['askQuery'] === false) { + newErrorMessage = "Invalid ASK query specification."; + } } - setParsingError(""); - }; - - const handleIndirectVariablesChange = (event, index) => { - const newList = [...indirectVariableSourceList]; - newList[index] = event.target.value; - setIndirectVariableSourceList(newList); - } - - const handleJSONparsing = (event, errorSetter) => { - const { name, value } = event.target; - errorSetter(false); - - let parsedValue; - try { - parsedValue = JSON.parse(value); - } catch (error) { - errorSetter(true); - parsedValue = value; + if (!newErrorMessage && isChecked(formData.httpProxiesCheck)) { + if (validFlags['httpProxies'] === false) { + newErrorMessage = "Invalid HTTP proxies specification."; + } } + setParsingError(newErrorMessage); + }, [formData, validFlags]); - setFormData((prevFormData) => ({ - ...prevFormData, - [name]: parsedValue, - })); - }; - - // These functions serve for a correct parsing of JSON objects, lists, etc. right before submitting - const ensureBoolean = (value) => value === 'on' || value === true; + const isChecked = (value) => value === 'on' || value === true; const parseAllObjectsToJSON = (dataWithStrings) => { - const parsedObject = dataWithStrings; - if (ensureBoolean(dataWithStrings.comunicaContextCheck)) { + if (isChecked(dataWithStrings.comunicaContextCheck)) { parsedObject.comunicaContext = JSON.parse(dataWithStrings.comunicaContext); if (!!dataWithStrings.source && dataWithStrings.source.trim() !== '') @@ -211,38 +151,132 @@ ORDER BY ?genre`; } } - if (ensureBoolean(dataWithStrings.sourceIndexCheck)) { + if (isChecked(dataWithStrings.sourceIndexCheck)) { parsedObject.sourcesIndex = { url: parsedObject.indexSourceUrl, queryString: parsedObject.indexSourceQuery } } - if (ensureBoolean(dataWithStrings.askQueryCheck)) { + if (isChecked(dataWithStrings.askQueryCheck)) { parsedObject.askQuery = JSON.parse(dataWithStrings.askQuery); } - if (ensureBoolean(dataWithStrings.httpProxiesCheck)) { + if (isChecked(dataWithStrings.httpProxiesCheck)) { parsedObject.httpProxies = JSON.parse(dataWithStrings.httpProxies); } - if (ensureBoolean(dataWithStrings.directVariablesCheck)) { + if (isChecked(dataWithStrings.directVariablesCheck)) { parsedObject.variables = JSON.parse(dataWithStrings.variables); } - if (ensureBoolean(dataWithStrings.indirectVariablesCheck)) { + if (isChecked(dataWithStrings.indirectVariablesCheck)) { parsedObject.indirectVariables = { queryStrings: JSON.parse(dataWithStrings.indirectQueries) }; } return parsedObject; } - // These are the functions for the addition and removal of indirect variable input fields - const handleIndirectVariableSource = () => { - setIndirectVariableSourceList([...indirectVariableSourceList, ""]); + const handleSubmit = async (event) => { + event.preventDefault(); + + // LOG console.log("----- customEditor.handleSubmit"); + + if (parsingError) { + // LOG console.log(`not submitting, parsingError: ${parsingError}`); + return; + } + + const htmlFormData = new FormData(event.currentTarget); + let collectedData = Object.fromEntries(htmlFormData.entries()); + // LOG console.log(`collectedData (from HTML form data):\n${JSON.stringify(collectedData, null, 2)}`); + // LOG console.log(`formData (from state):\n${JSON.stringify(formData, null, 2)}`); + + collectedData.queryString = formData.queryString; + if (isChecked(collectedData.sourceIndexCheck)) { + collectedData.indexSourceQuery = formData.indexSourceQuery; + } + if (isChecked(collectedData.comunicaContextCheck)) { + collectedData.comunicaContext = formData.comunicaContext; + } + if (isChecked(collectedData.sourceIndexCheck)) { + collectedData.indexSourceQuery = formData.indexSourceQuery; + } + if (isChecked(collectedData.directVariablesCheck)) { + collectedData.variables = formData.variables; + } + if (isChecked(collectedData.indirectVariablesCheck)) { + collectedData.indirectQueries = JSON.stringify(indirectVariablesQueryList); + } + if (isChecked(collectedData.askQueryCheck)) { + collectedData.askQuery = formData.askQuery; + } + if (isChecked(collectedData.httpProxiesCheck)) { + collectedData.httpProxies = formData.httpProxies; + } + + // LOG console.log(`collectedData (finally):\n${JSON.stringify(collectedData, null, 2)}`); + + const searchParams = new URLSearchParams(collectedData); + collectedData.searchParams = searchParams; + + if (props.newQuery) { + configManager.addNewQueryGroup('cstm', 'Custom queries', 'EditNoteIcon'); + + const creationID = Date.now().toString(); + const jsonData = parseAllObjectsToJSON(collectedData); + configManager.addQuery({ + ...jsonData, + id: creationID, + queryGroupId: "cstm", + icon: "AutoAwesomeIcon", + }); + + navigate(`/${creationID}`); + } else { + const customQuery = configManager.getQueryById(props.id); + const jsonData = parseAllObjectsToJSON(collectedData); + configManager.updateQuery({ + ...jsonData, + id: customQuery.id, + queryGroupId: customQuery.queryGroupId, + icon: customQuery.icon + }); + + navigate(`/${customQuery.id}`); + } + }; + + const handleChange = (event) => { + const { name, value, validFlag } = event.target; + const indirectVariablesQueryRegex = /indirectVariablesQuery-(\d)+/; + const result = indirectVariablesQueryRegex.exec(name); + if (result) { + const index = result[1]; + setIndirectVariablesQueryList((prevList) => { + const newList = [...prevList]; + newList[index] = value; + return newList; + }); + } else { + setFormData((prevFormData) => ({ + ...prevFormData, + [name]: value + })); + } + if (validFlag !== undefined) { + setValidFlags((prevValidFlags) => ({ + ...prevValidFlags, + [name]: validFlag + })); + } + }; + + const handleAddIndirectVariablesQuery = () => { + setIndirectVariablesQueryList([...indirectVariablesQueryList, ""]); } - const handleIndirectVariableSourceRemove = (index) => { - setIndirectVariableSourceList((prevList) => { + const handleRemoveIndirectVariablesQuery = (index) => { + setIndirectVariablesQueryList((prevList) => { const newList = [...prevList]; newList.splice(index, 1); return newList; @@ -255,32 +289,6 @@ ORDER BY ?genre`; } - // These Functions are the submit functions for whether the creation or edit of a custom query - const addQuery = (formData) => { - const creationID = Date.now().toString(); - formData = parseAllObjectsToJSON(formData); - - configManager.addQuery({ - ...formData, - id: creationID, - queryGroupId: "cstm", - icon: "AutoAwesomeIcon", - }); - navigate(`/${creationID}`); - }; - - const updateQuery = (formData, customQuery) => { - formData = parseAllObjectsToJSON(formData); - configManager.updateQuery({ - ...formData, - id: customQuery.id, - queryGroupId: customQuery.queryGroupId, - icon: customQuery.icon - }); - - navigate(`/${customQuery.id}`); - }; - return ( {!loggedIn && @@ -336,7 +344,7 @@ ORDER BY ?genre`; label="SPARQL query" name="queryString" helperText="Enter your SPARQL query here." - value={!!formData.queryString ? formData.queryString : formData.queryString === '' ? '' : defaultSparqlQuery} + value={formData.queryString === '' ? '' : formData.queryString || defaultSparqlQuery} onChange={handleChange} /> @@ -349,13 +357,12 @@ ORDER BY ?genre`; { - setParsingErrorComunica(false); setFormData((prevFormData) => ({ ...prevFormData, - 'comunicaContextCheck': !formData.comunicaContextCheck, + 'comunicaContextCheck': !isChecked(formData.comunicaContextCheck), })); } } @@ -364,12 +371,12 @@ ORDER BY ?genre`; - {formData.comunicaContextCheck && + {isChecked(formData.comunicaContextCheck) &&
- handleJSONparsing(e, setParsingErrorComunica)} - onChange={(e) => handleJSONparsing(e, setParsingErrorComunica)} - sx={{ marginBottom: '16px' }} + helperText="Enter your extra Comunica context in JSON-format." + value={formData.comunicaContext === '' ? '' : formData.comunicaContext || defaultExtraComunicaContext} + onChange={handleChange} />
} @@ -401,21 +400,21 @@ ORDER BY ?genre`; { setFormData((prevFormData) => ({ ...prevFormData, - 'sourceIndexCheck': !formData.sourceIndexCheck, + 'sourceIndexCheck': !isChecked(formData.sourceIndexCheck), })); } } />} label="Indirect sources" /> - {formData.sourceIndexCheck && + {isChecked(formData.sourceIndexCheck) &&
@@ -446,36 +445,27 @@ ORDER BY ?genre`; { - setParsingErrorTemplate(false); setFormData((prevFormData) => ({ ...prevFormData, - 'directVariablesCheck': !formData.directVariablesCheck, + 'directVariablesCheck': !isChecked(formData.directVariablesCheck), })); } } />} label="Fixed Variables" /> - {formData.directVariablesCheck && + {isChecked(formData.directVariablesCheck) &&
Give the variable names and options for this templated query. - handleJSONparsing(e, setParsingErrorTemplate)} - onChange={(e) => handleJSONparsing(e, setParsingErrorTemplate)} - sx={{ marginBottom: '16px' }} + helperText="Enter your fixed templated variables specification in JSON-format." + value={formData.variables === '' ? '' : formData.variables || defaultTemplateOptions} + onChange={handleChange} />
} @@ -483,39 +473,38 @@ ORDER BY ?genre`; { - setParsingErrorTemplate(false); setFormData((prevFormData) => ({ ...prevFormData, - 'indirectVariablesCheck': !formData.indirectVariablesCheck, + 'indirectVariablesCheck': !isChecked(formData.indirectVariablesCheck), })); } } />} label="Indirect Variables" /> - {formData.indirectVariablesCheck && + {isChecked(formData.indirectVariablesCheck) &&
- Give one or more queries to retrieve the variable(s) from the source(s). + Give one or more SPARQL queries to retrieve variable(s) from source(s).
{ - indirectVariableSourceList.map((sourceString, index) => ( + indirectVariablesQueryList.map((ivQuery, index) => (
)) } -
@@ -538,35 +527,26 @@ ORDER BY ?genre`; { - setParsingErrorAsk(false); setFormData((prevFormData) => ({ ...prevFormData, - 'askQueryCheck': !formData.askQueryCheck, + 'askQueryCheck': !isChecked(formData.askQueryCheck), })); } } />} label="ASK query" /> - {formData.askQueryCheck && + {isChecked(formData.askQueryCheck) &&
- handleJSONparsing(e, setParsingErrorAsk)} - onChange={(e) => handleJSONparsing(e, setParsingErrorAsk)} - sx={{ marginBottom: '16px' }} + helperText="Enter your ASK query specification in JSON-format." + value={formData.askQuery === '' ? '' : formData.askQuery || defaultAskQueryDetails} + onChange={handleChange} />
} @@ -574,35 +554,26 @@ ORDER BY ?genre`; { - setParsingErrorHttpProxies(false); setFormData((prevFormData) => ({ ...prevFormData, - 'httpProxiesCheck': !formData.httpProxiesCheck, + 'httpProxiesCheck': !isChecked(formData.httpProxiesCheck), })); } } />} label="Http proxies" /> - {formData.httpProxiesCheck && + {isChecked(formData.httpProxiesCheck) &&
- handleJSONparsing(e, setParsingErrorHttpProxies)} - onChange={(e) => handleJSONparsing(e, setParsingErrorHttpProxies)} - sx={{ marginBottom: '16px' }} + helperText="Enter your HTTP proxies specification JSON-format." + value={formData.httpProxies === '' ? '' : formData.httpProxies || defaultHttpProxiesDetails} + onChange={handleChange} />
} @@ -614,7 +585,7 @@ ORDER BY ?genre`; {parsingError && ( - {parsingError} + {parsingError} )} diff --git a/main/src/components/CustomQueryEditor/jsonEditField.css b/main/src/components/CustomQueryEditor/jsonEditField.css new file mode 100644 index 0000000..e1102d7 --- /dev/null +++ b/main/src/components/CustomQueryEditor/jsonEditField.css @@ -0,0 +1,5 @@ +.jsonField .CodeMirror-wrap { + line-height: 1.5em; + font-size: 14px; + border: 1px solid #d1d1d1 +} diff --git a/main/src/components/CustomQueryEditor/jsonEditField.jsx b/main/src/components/CustomQueryEditor/jsonEditField.jsx new file mode 100644 index 0000000..7ffaa66 --- /dev/null +++ b/main/src/components/CustomQueryEditor/jsonEditField.jsx @@ -0,0 +1,137 @@ +import { useEffect, useRef, useState } from 'react'; + +import CodeMirror from 'codemirror'; +import 'codemirror/mode/javascript/javascript'; +import 'codemirror/lib/codemirror.js'; +import 'codemirror/addon/lint/lint.css'; +import 'codemirror/addon/lint/lint'; +import 'codemirror/addon/lint/json-lint'; +import jsonlint from 'jsonlint-mod'; + +import './jsonEditField.css'; + +import { + Box, + FormControl, + FormHelperText, + InputLabel, +} from '@mui/material'; + +export function JsonEditField({ + required = false, + label, + name, + helperText, + value, + onChange +}) { + const parentRef = useRef(null); + const cmInstance = useRef(null); + const [errorMsg, setErrorMsg] = useState(""); + + function handleChange() { + const value = cmInstance.current.getValue(); + let newErrorMsg = ""; + if (required && value.trim() === '') { + newErrorMsg = "This field is required"; + } else { + try { + JSON.parse(value); + } catch (e) { + newErrorMsg = "Check syntax"; + } + } + setErrorMsg(newErrorMsg); + if (onChange) { + // construct an event with properties as expected in customEditor + onChange({ target: { name, value, validFlag: (newErrorMsg == "") } }); + } + } + + useEffect(() => { + window.jsonlint = jsonlint; + + if (parentRef.current && !cmInstance.current) { + cmInstance.current = new CodeMirror(parentRef.current, { + mode: {name: "javascript", json: true}, + lineNumbers: true, + lineWrapping: true, + indentUnit: 2, + tabSize: 2, + value, + inputStyle: "textarea", + gutters: ['CodeMirror-lint-markers'], + lint: true + }); + + cmInstance.current.on('change', () => { + handleChange(); + }); + + // force first change event + handleChange(); + } + + return () => { + if (cmInstance.current) { + cmInstance.current.getWrapperElement().remove(); // remove DOM node and count on GC to clean up + cmInstance.current = null; + } + }; + }, []); + + // accept external updates to value + useEffect(() => { + if (value != cmInstance.current.getValue()) { + cmInstance.current.setValue(value); + handleChange(); + } + }, [value]); + + return ( + + + {label && ( + + {label} + + )} + +
+ + + + + {errorMsg ? `${helperText} (${errorMsg})` : helperText} + + + ); +} diff --git a/test/cypress/e2e/custom-query-editor.cy.js b/test/cypress/e2e/custom-query-editor.cy.js index b4388d2..d06f202 100644 --- a/test/cypress/e2e/custom-query-editor.cy.js +++ b/test/cypress/e2e/custom-query-editor.cy.js @@ -1,13 +1,18 @@ import { orderedUrl } from "../support/utils"; describe("Custom Query Editor tests", () => { - it("Create a new simple query", () => { - cy.visit("/#/customQuery"); cy.get('input[name="name"]').type("new simple query"); cy.get('textarea[name="description"]').type("new description"); + cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list"); + + cy.setCodeMirrorValue("#sparql-edit-field-queryString", "This is not a valid SPARQL query"); + + cy.contains("Invalid SPARQL query."); + cy.get('button[type="submit"]').click(); + cy.contains("Invalid SPARQL query."); cy.setCodeMirrorValue("#sparql-edit-field-queryString", `PREFIX schema: @@ -21,33 +26,54 @@ describe("Custom Query Editor tests", () => { ]. }`); - cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list"); - + cy.get('[data-cy="parsingError"]').should('not.exist'); cy.get('button[type="submit"]').click(); - // Checking if the book query works cy.contains("Colleen Hoover").should('exist'); }); - it("Create a new simple query; bad SPARQL syntax in queryString", () => { - + it("Create a new simple query with a Comunica context", () => { cy.visit("/#/customQuery"); - cy.get('input[name="name"]').type("new simple query bad queryString"); + cy.get('input[name="name"]').type("new simple query with comunica context"); cy.get('textarea[name="description"]').type("new description"); - cy.setCodeMirrorValue("#sparql-edit-field-queryString", "This is not a valid SPARQL query"); + cy.setCodeMirrorValue("#sparql-edit-field-queryString", `PREFIX schema: - cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list"); + SELECT * WHERE { + ?list schema:name ?listTitle; + schema:itemListElement [ + schema:name ?bookTitle; + schema:creator [ + schema:name ?authorName + ] + ]. + }`); + + cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list ; http://huppledepup.doesnotexist.com/we-want-lenient"); + + cy.get('input[name="comunicaContextCheck"]').click(); + + cy.setCodeMirrorValue("#json-edit-field-comunicaContext", `{"lenient": truezzz}`); + + cy.contains("Invalid Comunica context configuration."); + cy.get('input[name="comunicaContextCheck"]').click(); + cy.get('[data-cy="parsingError"]').should('not.exist'); + cy.get('input[name="comunicaContextCheck"]').click(); + cy.get('button[type="submit"]').click(); + cy.contains("Invalid Comunica context configuration."); + cy.setCodeMirrorValue("#json-edit-field-comunicaContext", `{"lenient": true}`); + + cy.get('[data-cy="parsingError"]').should('not.exist'); cy.get('button[type="submit"]').click(); - cy.contains("Invalid query. Check the SPARQL fields."); + // Checking if the book query works + cy.contains("Colleen Hoover").should('exist'); }); it("Create a new query, with multiple sources", () => { - cy.visit("/#/customQuery"); cy.get('input[name="name"]').type("material query"); @@ -73,7 +99,10 @@ WHERE { } ORDER BY ?componentName `); + cy.get('input[name="source"]').type("http://localhost:8080/verifiable-example/components-vc ; http://localhost:8080/verifiable-example/components-vc-incorrect-proof ; http://localhost:8080/example/components"); + + cy.get('[data-cy="parsingError"]').should('not.exist'); cy.get('button[type="submit"]').click(); // Checking if the query works @@ -81,7 +110,6 @@ ORDER BY ?componentName }); it("Create a new query, here an ASK query", () => { - cy.visit("/#/customQuery"); cy.get('input[name="name"]').type("Is there an artist etc..."); @@ -95,21 +123,23 @@ ASK WHERE { ?person foaf:name ?name. ?person dbo:influencedBy dbp:Pablo_Picasso. }`); + cy.get('input[name="source"]').type("http://localhost:8080/example/artists"); cy.get('input[name="askQueryCheck"]').click() - cy.get('textarea[name="askQuery"]').clear() - cy.get('textarea[name="askQuery"]').type('"trueText":"Yes, there is at least one artist influenced by Picasso!","falseText":"No, there is not a single artist influenced by Picasso."}', { parseSpecialCharSequences: false }) + cy.setCodeMirrorValue("#json-edit-field-askQuery", '"trueText":"Yes, there is at least one artist influenced by Picasso!","falseText":"No, there is not a single artist influenced by Picasso."}') + cy.contains("Invalid ASK query specification."); + cy.get('input[name="askQueryCheck"]').click() + cy.get('[data-cy="parsingError"]').should('not.exist'); + cy.get('input[name="askQueryCheck"]').click() cy.get('button[type="submit"]').click(); + cy.contains("Invalid ASK query specification."); - // Check faulty input error - cy.contains("Invalid query. Check the JSON fields."); - - cy.get('textarea[name="askQuery"]').clear() - cy.get('textarea[name="askQuery"]').type('{"trueText":"Yes, there is at least one artist influenced by Picasso!","falseText":"No, there is not a single artist influenced by Picasso."}', { parseSpecialCharSequences: false }) + cy.setCodeMirrorValue("#json-edit-field-askQuery", '{"trueText":"Yes, there is at least one artist influenced by Picasso!","falseText":"No, there is not a single artist influenced by Picasso."}') + cy.get('[data-cy="parsingError"]').should('not.exist'); cy.get('button[type="submit"]').click(); // Check if the query works @@ -117,7 +147,6 @@ ASK WHERE { }); it("Create a new query, here with http proxies", () => { - cy.visit("/#/customQuery"); cy.get('input[name="name"]').type("My idols custom..."); @@ -131,21 +160,23 @@ SELECT ?name ?birthDate_int WHERE { schema:birthDate ?birthDate_int; ]. }`); + cy.get('input[name="source"]').type("http://localhost:8001/example/idols"); cy.get('input[name="httpProxiesCheck"]').click() - cy.get('textarea[name="httpProxies"]').clear() - cy.get('textarea[name="httpProxies"]').type('{"urlStart":"http://localhost:8001","httpProxy":"http://localhost:8000/"}, {"urlStart":"http://localhost:8002","httpProxy":"http://localhost:9000/"}]', { parseSpecialCharSequences: false }) + cy.setCodeMirrorValue("#json-edit-field-httpProxies", '{"urlStart":"http://localhost:8001","httpProxy":"http://localhost:8000/"}, {"urlStart":"http://localhost:8002","httpProxy":"http://localhost:9000/"}]'); + cy.contains("Invalid HTTP proxies specification."); + cy.get('input[name="httpProxiesCheck"]').click() + cy.get('[data-cy="parsingError"]').should('not.exist'); + cy.get('input[name="httpProxiesCheck"]').click() cy.get('button[type="submit"]').click(); + cy.contains("Invalid HTTP proxies specification."); - // Check faulty input error - cy.contains("Invalid query. Check the JSON fields."); - - cy.get('textarea[name="httpProxies"]').clear() - cy.get('textarea[name="httpProxies"]').type('[{"urlStart":"http://localhost:8001","httpProxy":"http://localhost:8000/"}, {"urlStart":"http://localhost:8002","httpProxy":"http://localhost:9000/"}]', { parseSpecialCharSequences: false }) + cy.setCodeMirrorValue("#json-edit-field-httpProxies", '[{"urlStart":"http://localhost:8001","httpProxy":"http://localhost:8000/"}, {"urlStart":"http://localhost:8002","httpProxy":"http://localhost:9000/"}]'); + cy.get('[data-cy="parsingError"]').should('not.exist'); cy.get('button[type="submit"]').click(); // Check if the query works @@ -153,32 +184,32 @@ SELECT ?name ?birthDate_int WHERE { }); it("Check if all possible parameters are filled in with parameterized URL", () => { - - //templatedQueryCheck // Navigate to the URL of a saved query with completely filled-in form cy.visit("/#/customQuery?name=Query+Name&description=Query+Description&queryString=Sparql+query+text&comunicaContextCheck=on&source=The+Comunica+Source&comunicaContext=%7B%22Advanced+Comunica+Context%22%3Atrue%7D&sourceIndexCheck=on&indexSourceUrl=Index+Source&indexSourceQuery=Index+Query+&askQueryCheck=on&askQuery=%7B%22trueText%22%3A%22+filled+in%22%2C%22falseText%22%3A%22not+filled+in%22%7D&directVariablesCheck=on&variables=%7B%22firstvariables%22%3A%5B%22only+one%22%5D%7D&httpProxiesCheck=on&httpProxies=%5B%7B%22urlStart%22%3A%22http%3A%2F%2Flocalhost%3A8001%22%2C%22httpProxy%22%3A%22http%3A%2F%2Flocalhost%3A8000%2F%22%7D%5D") // Verify that every field is correctly filled-in cy.get('input[name="name"]').should('have.value', 'Query Name'); cy.get('textarea[name="description"]').should('have.value', 'Query Description'); + cy.checkCodeMirrorValue("#sparql-edit-field-queryString", 'Sparql query text'); cy.get('input[name="source"]').should('have.value', "The Comunica Source"); - cy.get('textarea[name="comunicaContext"]').should('have.value', `{"Advanced Comunica Context":true}`); + cy.checkCodeMirrorValue("#json-edit-field-comunicaContext", `{"Advanced Comunica Context":true}`); cy.get('input[name="indexSourceUrl"]').should('have.value', "Index Source"); cy.checkCodeMirrorValue("#sparql-edit-field-indexSourceQuery", "Index Query "); - cy.get('textarea[name="askQuery"]').should('have.value', `{"trueText":" filled in","falseText":"not filled in"}`); + cy.checkCodeMirrorValue("#json-edit-field-askQuery", `{"trueText":" filled in","falseText":"not filled in"}`); - cy.get('textarea[name="httpProxies"]').should('have.value', `[{"urlStart":"http://localhost:8001","httpProxy":"http://localhost:8000/"}]`); + cy.checkCodeMirrorValue("#json-edit-field-httpProxies", `[{"urlStart":"http://localhost:8001","httpProxy":"http://localhost:8000/"}]`); - cy.get('textarea[name="variables"]').should('have.value', `{"firstvariables":["only one"]}`); + cy.checkCodeMirrorValue("#json-edit-field-variables", `{"firstvariables":["only one"]}`); - }) + // The first error + cy.contains("Invalid SPARQL query."); + }); it("Successfully edit a query to make it work", () => { - cy.visit("/#/customQuery"); // First create a wrong query @@ -191,6 +222,7 @@ SELECT ?name ?birthDate_int WHERE { cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list"); // Submit the incomplete query + cy.get('[data-cy="parsingError"]').should('not.exist'); cy.get('button[type="submit"]').click(); cy.contains("Custom queries").click(); @@ -218,6 +250,7 @@ SELECT * WHERE { }`); // Submit the correct query + cy.get('[data-cy="parsingError"]').should('not.exist'); cy.get('button[type="submit"]').click(); // Now we should be on the page of the fixed query @@ -225,11 +258,9 @@ SELECT * WHERE { // Check if the resulting list appears cy.contains("Colleen Hoover").should('exist'); - - }) + }); it("Shares the correct URL", () => { - cy.visit("/#/customQuery"); // First create a simple query @@ -246,7 +277,10 @@ SELECT * WHERE { ] ]. }`); + cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list"); + + cy.get('[data-cy="parsingError"]').should('not.exist'); cy.get('button[type="submit"]').click(); cy.get('button').contains("Share Query").click(); @@ -254,12 +288,9 @@ SELECT * WHERE { cy.get('textarea[name="queryURL"]').invoke('val').then((val) => { expect(orderedUrl(val)).to.equal(orderedUrl(Cypress.config('baseUrl') + '#/customQuery?name=new+query&description=new+description&queryString=PREFIX+schema%3A+%3Chttp%3A%2F%2Fschema.org%2F%3E+%0ASELECT+*+WHERE+%7B%0A++++%3Flist+schema%3Aname+%3FlistTitle%3B%0A++++++schema%3AitemListElement+%5B%0A++++++schema%3Aname+%3FbookTitle%3B%0A++++++schema%3Acreator+%5B%0A++++++++schema%3Aname+%3FauthorName%0A++++++%5D%0A++++%5D.%0A%7D&source=http%3A%2F%2Flocalhost%3A8080%2Fexample%2Fwish-list')); }); - - - }) + }); it("Custom templated query", () => { - cy.visit("/#/customQuery"); cy.get('input[name="name"]').type("custom template"); @@ -278,28 +309,39 @@ SELECT ?name ?sameAs_url WHERE { cy.get('input[name="source"]').type("http://localhost:8080/example/favourite-musicians"); cy.get('input[name="directVariablesCheck"]').click() - cy.get('textarea[name="variables"]').clear() - cy.get('textarea[name="variables"]').type(`{ + cy.setCodeMirrorValue("#json-edit-field-variables", `{ "genre": [ "\\"Romantic\\"", "\\"Baroque\\"", "\\"Classical\\"" - ] }`) + + cy.contains("Invalid fixed templated variables specification."); + cy.get('input[name="directVariablesCheck"]').click() + cy.get('[data-cy="parsingError"]').should('not.exist'); + cy.get('input[name="directVariablesCheck"]').click() cy.get('button[type="submit"]').click(); + cy.contains("Invalid fixed templated variables specification."); + cy.setCodeMirrorValue("#json-edit-field-variables", `{ + "genre": [ + "\\"Romantic\\"", + "\\"Baroque\\"", + "\\"Classical\\"" + ] + }`) + + cy.get('[data-cy="parsingError"]').should('not.exist'); + cy.get('button[type="submit"]').click(); cy.get('.ra-input-genre').click(); cy.get('li').contains('Baroque').click(); - - // Comfirm query cy.get('button[type="submit"]').click(); cy.get('.column-name').find('span').contains("Antonio Caldara").should('exist'); - }) + }); it("Custom Query With Index File", () => { - cy.visit("/#/customQuery"); cy.get('input[name="name"]').type("custom with index file"); @@ -337,6 +379,15 @@ ORDER BY ?componentName` cy.get('input[name="sourceIndexCheck"]').click() cy.get('input[name="indexSourceUrl"]').type("http://localhost:8080/example/index-example-texon-only") + cy.setCodeMirrorValue("#sparql-edit-field-indexSourceQuery", "this is not a valid SPARQL query") + + cy.contains("Invalid indirect sources SPARQL query."); + cy.get('input[name="sourceIndexCheck"]').click() + cy.get('[data-cy="parsingError"]').should('not.exist'); + cy.get('input[name="sourceIndexCheck"]').click() + cy.get('button[type="submit"]').click(); + cy.contains("Invalid indirect sources SPARQL query."); + cy.setCodeMirrorValue("#sparql-edit-field-indexSourceQuery", `PREFIX rdf: PREFIX rdfs: PREFIX example: @@ -346,65 +397,14 @@ WHERE { example:index-example rdfs:seeAlso ?object . }` ) - cy.get('button[type="submit"]').click(); - - cy.contains("https://www.example.com/data/component-c01").should('exist'); - - }) - - - - it("Custom Query With Index File; bad SPARQL syntax in indexSourceQuery", () => { - - cy.visit("/#/customQuery"); - - cy.get('input[name="name"]').type("custom with index file bad indexSourceQuery"); - cy.get('textarea[name="description"]').type("description for index"); - - // Query handling a variable - cy.setCodeMirrorValue("#sparql-edit-field-queryString", `# Query Texon's components and their materials -# Datasources: https://css5.onto-deside.ilabt.imec.be/texon/data/dt/out/components.ttl https://css5.onto-deside.ilabt.imec.be/texon/data/dt/out/boms.ttl https://css5.onto-deside.ilabt.imec.be/texon/data/dt/out/materials.ttl -PREFIX oo: -PREFIX ao: -PREFIX rdf: -PREFIX rdfs: -PREFIX d: -PREFIX o: - -SELECT ?component ?componentName ?material ?materialName ?percentage -WHERE { - ?component - a o:Component ; - o:name ?componentName ; - o:has-component-bom [ - o:has-component-material-assoc [ - o:percentage ?percentage ; - o:has-material ?material ; - ]; - ]; - . - ?material o:name ?materialName ; -} -ORDER BY ?componentName` - ); - - // No Comunica Sources Required - cy.get('input[name="sourceIndexCheck"]').click() - cy.get('input[name="indexSourceUrl"]').type("http://localhost:8080/example/index-example-texon-only") - - cy.setCodeMirrorValue("#sparql-edit-field-indexSourceQuery", "this is not a valid SPARQL query") + cy.get('[data-cy="parsingError"]').should('not.exist'); cy.get('button[type="submit"]').click(); - cy.contains("Invalid query. Check the SPARQL fields."); - - }) - - + cy.contains("https://www.example.com/data/component-c01").should('exist'); + }); it("Make a templated query, then edit it to make it a normal query", () => { - - // First create the query cy.visit("/#/customQuery"); cy.get('input[name="name"]').type("custom template"); @@ -423,21 +423,19 @@ ORDER BY ?componentName` cy.get('input[name="source"]').type("http://localhost:8080/example/favourite-musicians"); cy.get('input[name="directVariablesCheck"]').click() - cy.get('textarea[name="variables"]').clear() - cy.get('textarea[name="variables"]').type(`{ + cy.setCodeMirrorValue("#json-edit-field-variables", `{ "genre": [ "\\"Romantic\\"", "\\"Baroque\\"", "\\"Classical\\"" ] }`) - cy.get('button[type="submit"]').click(); + cy.get('[data-cy="parsingError"]').should('not.exist'); + cy.get('button[type="submit"]').click(); cy.get('.ra-input-genre').click(); cy.get('li').contains('Baroque').click(); - - // Comfirm query cy.get('button[type="submit"]').click(); cy.get('.column-name').find('span').contains("Antonio Caldara").should('exist'); @@ -457,18 +455,17 @@ ORDER BY ?componentName` // Remove the templated options cy.get('input[name="directVariablesCheck"]').click() + cy.get('[data-cy="parsingError"]').should('not.exist'); cy.get('button[type="submit"]').click(); cy.get('form').should('not.exist') cy.get('.column-name').find('span').contains("Ludwig van Beethoven").should('exist'); - }) + }); // Reverse logic it("Make a normal query, then edit it to make it a templated query", () => { - - // First create the query cy.visit("/#/customQuery"); cy.get('input[name="name"]').type("custom template"); @@ -486,15 +483,13 @@ ORDER BY ?componentName` cy.get('input[name="source"]').type("http://localhost:8080/example/favourite-musicians"); + cy.get('[data-cy="parsingError"]').should('not.exist'); cy.get('button[type="submit"]').click(); cy.get('form').should('not.exist') cy.get('.column-name').find('span').contains("Ludwig van Beethoven").should('exist'); - - - // Now that this normal one works, lets edit it to make a templated query from it cy.get('button').contains("Edit Query").click(); @@ -509,8 +504,7 @@ ORDER BY ?componentName` cy.get('input[name="directVariablesCheck"]').click() - cy.get('textarea[name="variables"]').clear() - cy.get('textarea[name="variables"]').type(`{ + cy.setCodeMirrorValue("#json-edit-field-variables", `{ "genre": [ "\\"Romantic\\"", "\\"Baroque\\"", @@ -518,21 +512,17 @@ ORDER BY ?componentName` ] }`) + cy.get('[data-cy="parsingError"]').should('not.exist'); cy.get('button[type="submit"]').click(); - cy.get('.ra-input-genre').click(); cy.get('li').contains('Baroque').click(); - - // Comfirm query cy.get('button[type="submit"]').click(); cy.get('.column-name').find('span').contains("Antonio Caldara").should('exist'); - }) + }); it("Custom templated query with 1 indirect variable", () => { - - // Create the indirect variable cy.visit("/#/customQuery"); cy.get('input[name="name"]').type("custom indirect template"); @@ -551,67 +541,37 @@ schema:sameAs ?sameAs_url; cy.get('input[name="source"]').type("http://localhost:8080/example/favourite-musicians"); cy.get('input[name="indirectVariablesCheck"]').click() - cy.setCodeMirrorValue("#sparql-edit-field-indirectVariablesQuery-0", "PREFIX schema: SELECT DISTINCT ?genre WHERE { ?list schema:genre ?genre; }") - cy.get('button[type="submit"]').click(); - - - cy.get('.ra-input-genre').click(); - cy.get('li').contains('Baroque').click(); - - // Comfirm query - cy.get('button[type="submit"]').click(); - - cy.get('.column-name').find('span').contains("Antonio Caldara").should('exist'); - cy.get('.column-name').find('span').contains("Pietro Locatelli").should('exist'); - - cy.get('.column-name').find('span').contains("Franz Schubert").should("not.exist"); - cy.get('.column-name').find('span').contains("Ludwig van Beethoven").should("not.exist"); - - - }); - - it("Custom templated query with 1 indirect variable; bad SPARQL syntax in indirectVariablesQuery-1", () => { - - // Create the indirect variable - cy.visit("/#/customQuery"); - - cy.get('input[name="name"]').type("custom indirect template bad indirectVariablesQuery-1"); - cy.get('textarea[name="description"]').type("description for an indirect templated query"); - - // Query handling a variable - cy.setCodeMirrorValue("#sparql-edit-field-queryString", `PREFIX schema: -SELECT ?name WHERE { -?list schema:name ?listTitle; -schema:name ?name; -schema:genre $genre; -schema:sameAs ?sameAsUrl; -}` - ); - - cy.get('input[name="source"]').type("http://localhost:8080/example/favourite-musicians"); - cy.get('input[name="indirectVariablesCheck"]').click() - cy.setCodeMirrorValue("#sparql-edit-field-indirectVariablesQuery-0", "PREFIX schema: SELECT DISTINCT ?genre WHERE { ?list schema:genre ?genre; }") // Test the bad SPARQL syntax on a second indirect variable query cy.get('button').contains("Add another query").click(); cy.setCodeMirrorValue("#sparql-edit-field-indirectVariablesQuery-1", "this is not a valid SPARQL query") + cy.contains("Invalid SPARQL query to retrieve variable(s) from source(s)."); + cy.get('input[name="indirectVariablesCheck"]').click() + cy.get('[data-cy="parsingError"]').should('not.exist'); + cy.get('input[name="indirectVariablesCheck"]').click() cy.get('button[type="submit"]').click(); - - cy.contains("Invalid query. Check the SPARQL fields."); + cy.contains("Invalid SPARQL query to retrieve variable(s) from source(s)."); // Deleting the second indirect variable query should also clear the error status cy.get('[data-testid="DeleteIcon"]').last().click(); + cy.get('[data-cy="parsingError"]').should('not.exist'); + cy.get('button[type="submit"]').click(); + + cy.get('.ra-input-genre').click(); + cy.get('li').contains('Baroque').click(); cy.get('button[type="submit"]').click(); - cy.get('.ra-input-genre').should("exist"); + cy.get('.column-name').find('span').contains("Antonio Caldara").should('exist'); + cy.get('.column-name').find('span').contains("Pietro Locatelli").should('exist'); + + cy.get('.column-name').find('span').contains("Franz Schubert").should("not.exist"); + cy.get('.column-name').find('span').contains("Ludwig van Beethoven").should("not.exist"); }); it("Custom templated query with 2 indirect variables", () => { - - // Create the indirect variable cy.visit("/#/customQuery"); cy.get('input[name="name"]').type("custom indirect template 2"); @@ -634,20 +594,17 @@ schema:sameAs $sameAsUrl; cy.get('button').contains("Add another query").click(); cy.setCodeMirrorValue("#sparql-edit-field-indirectVariablesQuery-1", "PREFIX schema: SELECT DISTINCT ?sameAsUrl WHERE { ?list schema:sameAs ?sameAsUrl; }") + cy.get('[data-cy="parsingError"]').should('not.exist'); cy.get('button[type="submit"]').click(); // Run some testcases now - // Existing combination (only Mozart) cy.get('.ra-input-genre').click(); cy.get('li').contains('Classical').click(); - cy.get('.ra-input-sameAsUrl').click(); cy.get('li').contains('Mozart').click(); - cy.get('button[type="submit"]').click(); - cy.contains("Finished in:"); cy.get('.column-name').find('span').contains("Wolfgang Amadeus Mozart").should("exist"); @@ -661,27 +618,20 @@ schema:sameAs $sameAsUrl; cy.get('.ra-input-genre').click(); cy.get('li').contains('Baroque').click(); - cy.get('.ra-input-sameAsUrl').click(); cy.get('li').contains('Beethoven').click(); - cy.get('button[type="submit"]').click(); cy.get('span').contains("The result list is empty.").should("exist"); - - // Change variables and make another existing combination - cy.get('button').contains("Change Variables").should("exist"); cy.get('button').contains("Change Variables").click(); cy.get('.ra-input-genre').click(); cy.get('li').contains('Romantic').click(); - cy.get('.ra-input-sameAsUrl').click(); cy.get('li').contains('Schubert').click(); - cy.get('button[type="submit"]').click(); cy.get('span').contains("The result list is empty.").should("not.exist"); @@ -689,14 +639,9 @@ schema:sameAs $sameAsUrl; cy.get('.column-name').find('span').contains("Johann Sebastian Bach").should("not.exist"); cy.get('.column-name').find('span').contains("Antonio Vivaldi").should("not.exist"); cy.get('.column-name').find('span').contains("Franz Schubert").should("exist"); - - }); it("Make a custom templated query with 1 indirect variable and edit into a query with 2 indirect variables", () => { - - - // Create a custom query with one variable cy.visit("/#/customQuery"); cy.get('input[name="name"]').type("custom indirect template"); @@ -712,21 +657,19 @@ schema:sameAs ?sameAs_url; cy.get('input[name="indirectVariablesCheck"]').click() cy.setCodeMirrorValue("#sparql-edit-field-indirectVariablesQuery-0", "PREFIX schema: SELECT DISTINCT ?genre WHERE { ?list schema:genre ?genre; }") - cy.get('button[type="submit"]').click(); + cy.get('[data-cy="parsingError"]').should('not.exist'); + cy.get('button[type="submit"]').click(); // Check if the query works cy.get('.ra-input-genre').click(); cy.get('li').contains('Baroque').click(); - cy.get('button[type="submit"]').click(); cy.get('.column-name').find('span').contains("Antonio Caldara").should('exist'); cy.get('.column-name').find('span').contains("Franz Schubert").should("not.exist"); - // Now edit the query into one with 2 variables - cy.get('button').contains("Edit Query").click(); // Check if the values are correctly filled in @@ -741,32 +684,28 @@ schema:genre $genre; schema:sameAs $sameAsUrl; }` ); + // add source for the second variable cy.get('button').contains("Add another query").click(); cy.setCodeMirrorValue("#sparql-edit-field-indirectVariablesQuery-1", "PREFIX schema: SELECT DISTINCT ?sameAsUrl WHERE { ?list schema:sameAs ?sameAsUrl; }") - // The changes are done, now submit it + cy.get('[data-cy="parsingError"]').should('not.exist'); cy.get('button[type="submit"]').click(); // Try an existing combination cy.get('.ra-input-genre').click(); cy.get('li').contains('Classical').click(); - cy.get('.ra-input-sameAsUrl').click(); cy.get('li').contains('Beethoven').click(); - cy.get('button[type="submit"]').click(); cy.contains("Finished in:"); cy.get('.column-name').find('span').contains("Ludwig van Beethoven").should("exist"); cy.get('.column-name').find('span').contains("Wolfgang Amadeus Mozart").should("not.exist"); - }); it("Custom templated query with 1 indirect variable and sources from an index file", () => { - - // Create the indirect variable cy.visit("/#/customQuery"); cy.get('input[name="name"]').type("custom indirect template with index"); @@ -809,8 +748,6 @@ WHERE { example:index-example rdfs:seeAlso ?object . }`); - - cy.get('input[name="indirectVariablesCheck"]').click() cy.setCodeMirrorValue("#sparql-edit-field-indirectVariablesQuery-0", `PREFIX rdf: @@ -825,14 +762,11 @@ WHERE { } ORDER BY ?componentName`) - + cy.get('[data-cy="parsingError"]').should('not.exist'); cy.get('button[type="submit"]').click(); cy.get('.ra-input-componentName').click(); cy.get('li').contains('Component 1').click(); - - - // Comfirm query cy.get('button[type="submit"]').click(); // Check that it is correctly loaded with and only the correct data appears @@ -845,13 +779,9 @@ ORDER BY ?componentName`) cy.get('.column-componentName').find('span').contains("Component 2").should("not.exist"); cy.get('.column-componentName').find('span').contains("Component 3").should("not.exist"); cy.get('.column-materialName').find('span').contains("Material 6").should("not.exist"); - - }); it("Custom templated query with 2 indirect variables and sources from an index file", () => { - - // Create the indirect variable cy.visit("/#/customQuery"); cy.get('input[name="name"]').type("custom indirect template with index"); @@ -882,7 +812,6 @@ WHERE { ORDER BY ?componentName `); - cy.get('input[name="sourceIndexCheck"]').click() cy.get('input[name="indexSourceUrl"]').type("http://localhost:8080/example/index-example-texon-only") @@ -895,8 +824,6 @@ WHERE { example:index-example rdfs:seeAlso ?object . }`); - - cy.get('input[name="indirectVariablesCheck"]').click() cy.setCodeMirrorValue("#sparql-edit-field-indirectVariablesQuery-0", `PREFIX rdf: @@ -925,16 +852,13 @@ WHERE { } ORDER BY ?materialName`) - + cy.get('[data-cy="parsingError"]').should('not.exist'); cy.get('button[type="submit"]').click(); cy.get('.ra-input-componentName').click(); cy.get('li').contains('Component 1').click(); - cy.get('.ra-input-materialName').click(); cy.get('li').contains('Material 2').click(); - - // Comfirm query cy.get('button[type="submit"]').click(); // Check that it is correctly loaded with and only the correct data appears @@ -947,7 +871,5 @@ ORDER BY ?materialName`) cy.get('.column-componentName').find('span').contains("Component 2").should("not.exist"); cy.get('.column-componentName').find('span').contains("Component 3").should("not.exist"); cy.get('.column-materialName').find('span').contains("Material 6").should("not.exist"); - }); - -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/test/cypress/e2e/customize-existing-query.cy.js b/test/cypress/e2e/customize-existing-query.cy.js index 66baaeb..4244f8b 100644 --- a/test/cypress/e2e/customize-existing-query.cy.js +++ b/test/cypress/e2e/customize-existing-query.cy.js @@ -52,7 +52,7 @@ SELECT ?name ?sameAs_url WHERE { schema:sameAs ?sameAs_url; }`); - cy.get('textarea[name="variables"]').should('have.value', `{"genre":["\\"Romantic\\"","\\"Baroque\\"","\\"Classical\\""]}`) + cy.checkCodeMirrorValue("#json-edit-field-variables", `{"genre":["\\"Romantic\\"","\\"Baroque\\"","\\"Classical\\""]}`) @@ -163,7 +163,7 @@ SELECT DISTINCT ?source WHERE { cy.url().should('include', 'customQuery'); - cy.get('textarea[name="askQuery"]').should('have.value', `{"trueText":"Yes, there is at least one artist influenced by Picasso!","falseText":"No, there is not a single artist influenced by Picasso."}`); + cy.checkCodeMirrorValue("#json-edit-field-askQuery", `{"trueText":"Yes, there is at least one artist influenced by Picasso!","falseText":"No, there is not a single artist influenced by Picasso."}`); }) it("http proxies", () => { @@ -175,7 +175,7 @@ SELECT DISTINCT ?source WHERE { cy.url().should('include', 'customQuery'); - cy.get('textarea[name="httpProxies"]').should('have.value', `[{"urlStart":"http://localhost:8001","httpProxy":"http://localhost:8000/"}]`); + cy.checkCodeMirrorValue("#json-edit-field-httpProxies", `[{"urlStart":"http://localhost:8001","httpProxy":"http://localhost:8000/"}]`); }) })