Skip to content
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- The possibility to create a custom query from an existing query and to clone a custom query (#141).

### Changed

### Fixed


## [1.3.0] - 2024-08-07

### Added
Expand Down
30 changes: 20 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,16 +224,26 @@ Nevertheless, you can use any React component you want, just make sure it's a fu

## Custom queries

Besides the prepared queries in the configuration file, a user can edit custom queries:

- To create a custom query, open "Custom Query Editor" from the menu on the left.
- Complete the custom query editor form and click the "CREATE QUERY" button when ready.
- Your new query is added to the "Custom queries" group and you are redirected to the query's result view.
- If not satisfied with the query result, you can click "EDIT QUERY" to further edit your query.
When saving changes, the result is recalculated.
- Because the custom query only lives as long as your browser remembers it, a "SAVE QUERY LINK" button is provided.
Use it to generate a unique URL for this custom query. Copy that URL to your clipboard and save it.
You can then visit that URL any time later, to recreate this query.
The configuration file contains prepared, fixed queries.
In addition, a user can create and edit custom queries, either from scratch or based on an existing query.

- To create a new custom query from scratch:
- Open "Custom Query Editor" from the menu on the left.
- Complete the custom query editor form and click the "CREATE QUERY" button when ready.
- Your new query is added to the "Custom queries" group and you are redirected to the query's result view.
- If not satisfied with the query result, you can click "EDIT QUERY" to further edit your query.
When saving changes, the result is recalculated.

- To create a new custom query based on an existing query:
- Open the existing query.
- Click "CLONE AS CUSTOM QUERY" (in a normal query) or "CLONE" (in a custom query).
- Make the desired changes in the form and click the "CREATE QUERY" button when ready. The new custom query behaves as if it were created from scratch.

- To reproduce a custom query later, a "SAVE QUERY LINK" button is provided.
Use it to generate a unique URL for this custom query.
Visiting that URL any time later, recreates a custom query with the same specifications.
This may be useful to forward a custom query to another user.

- To clean up an unwanted custom query, there is always a button "DELETE QUERY"...

## Representation Mapper
Expand Down
208 changes: 208 additions & 0 deletions cypress/e2e/customize-existing-query.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
describe("Customize existing query", () => {

it("simple query", () => {
cy.visit("/");
cy.contains("General examples").click();
cy.contains("A public list of books I'd love to own").click();

cy.get('button').contains("Clone as custom query").click();

cy.url().should('include', 'customQuery');


cy.get('input[name="name"]').should('have.value', "(Cloned from) A public list of books I'd love to own");

cy.get('textarea[name="queryString"]').should('have.value', `PREFIX schema: <http://schema.org/>

SELECT * WHERE {
?list schema:name ?listTitle;
schema:itemListElement [
schema:name ?bookTitle;
schema:creator [
schema:name ?authorName
]
].
}`);
cy.get('input[name="source"]').should('have.value', "http://localhost:8080/example/wish-list");

})

it("templated query - fixed variables", () => {
cy.visit("/");
cy.contains("General examples").click();
cy.contains("A templated query about musicians").click();

cy.get('form').within(() => {
cy.get('#genre').click();
});
cy.get('li').contains('Baroque').click();

cy.get('button[type="submit"]').click();

cy.get('button').contains("Clone as custom query").click();
cy.url().should('include', 'customQuery');

cy.get('input[name="name"]').should('have.value', "(Cloned from) A templated query about musicians");

cy.get('textarea[name="queryString"]').should('have.value', `PREFIX schema: <http://schema.org/>

SELECT ?name ?sameAs_url WHERE {
?list schema:name ?listTitle;
schema:name ?name;
schema:genre $genre;
schema:sameAs ?sameAs_url;
}`);

cy.get('textarea[name="variables"]').should('have.value', `{"genre":["\\"Romantic\\"","\\"Baroque\\"","\\"Classical\\""]}`)





})

it("templated query - indirect variables", () => {

cy.visit("/");
cy.contains("For testing only").click();
cy.contains("A templated query about musicians, two variables (indirect variables)").click();

cy.get('form').within(() => {
cy.get('#genre').click();
});
cy.get('li').contains('Baroque').click();

cy.get('form').within(() => {
cy.get('#sameAsUrl').click();
});
cy.get('li').contains('Vivaldi').click();

cy.get('button[type="submit"]').click();

cy.get('button').contains("Clone as custom query").click();
cy.url().should('include', 'customQuery');

cy.get('input[name="name"]').should('have.value', "(Cloned from) A templated query about musicians, two variables (indirect variables)");

cy.get('textarea[name="queryString"]').should('have.value', `PREFIX schema: <http://schema.org/>

SELECT ?name WHERE {
?list schema:name ?listTitle;
schema:name ?name;
schema:genre $genre;
schema:sameAs $sameAsUrl;
}
`);

cy.get('textarea[name="indirectQuery1"]').should('have.value', `PREFIX schema: <http://schema.org/>

SELECT DISTINCT ?genre
WHERE {
?list schema:genre ?genre
}
ORDER BY ?genre
`);

cy.get('textarea[name="indirectQuery2"]').should('have.value', `PREFIX schema: <http://schema.org/>

SELECT DISTINCT ?sameAsUrl
WHERE {
?list schema:sameAs ?sameAsUrl
}
ORDER BY ?sameAsUrl
`);




})

it("index file", () => {
cy.visit("/");
cy.contains("General examples").click();
cy.contains("Sources from an index file").click();

cy.get('button').contains("Clone as custom query").click({ force: true }); // Button is out of FoV so we gotta force the click

cy.url().should('include', 'customQuery');


cy.get('input[name="name"]').should('have.value', "(Cloned from) Sources from an index file");

cy.get('textarea[name="queryString"]').should('have.value', `PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX o: <https://www.example.com/ont/>

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
`);

cy.get('input[name="indexSourceUrl"]').should('have.value', `http://localhost:8080/example/index-example-texon-only`)
cy.get('textarea[name="indexSourceQuery"]').should('have.value', `PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX example: <http://localhost:8080/example/index-example-texon-only#>

SELECT ?object
WHERE {
example:index-example rdfs:seeAlso ?object .
}
`)

})


})

describe("Clone and customize existing query, clone the custom after", () => {

it("clone simple query", () => {
cy.visit("/");
cy.contains("General examples").click();
cy.contains("A public list of books I'd love to own").click();

cy.get('button').contains("Clone as custom query").click();

cy.url().should('include', 'customQuery');


cy.get('input[name="name"]').should('have.value', "(Cloned from) A public list of books I'd love to own");

cy.get('textarea[name="queryString"]').should('have.value', `PREFIX schema: <http://schema.org/>

SELECT * WHERE {
?list schema:name ?listTitle;
schema:itemListElement [
schema:name ?bookTitle;
schema:creator [
schema:name ?authorName
]
].
}`
);
cy.get('input[name="source"]').should('have.value', "http://localhost:8080/example/wish-list");

cy.get('button[type="submit"]').click();

cy.contains("Colleen Hoover").should("exist");

cy.get('button').contains("Clone").click();

cy.url().should('include', 'customQuery');


cy.get('input[name="name"]').should('have.value', "(Cloned) (Cloned from) A public list of books I'd love to own");

})
})
2 changes: 1 addition & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import InteractionLayout from "./components/InteractionLayout/InteractionLayout"
import TemplatedListResultTable from "./components/ListResultTable/TemplatedListResultTable.jsx";

import { Route } from "react-router-dom";
import CustomEditor from "./components/Dashboard/CustomQueryEditor/customEditor.jsx";
import CustomEditor from "./components/CustomQueryEditor/customEditor.jsx";

import configManager from "./configManager/configManager.js";

Expand Down
8 changes: 7 additions & 1 deletion src/IconProvider/IconProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import TuneIcon from '@mui/icons-material/Tune';
import SaveAsIcon from '@mui/icons-material/SaveAs';
import InfoIcon from '@mui/icons-material/Info';
import CloseIcon from '@mui/icons-material/Close';
import SettingsSuggestIcon from '@mui/icons-material/SettingsSuggest';
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
import FilterNoneIcon from '@mui/icons-material/FilterNone';

export default {
BrushIcon,
Expand All @@ -45,5 +48,8 @@ export default {
TuneIcon,
SaveAsIcon,
InfoIcon,
CloseIcon
CloseIcon,
SettingsSuggestIcon,
ChevronLeftIcon,
FilterNoneIcon
};
Loading