-
Notifications
You must be signed in to change notification settings - Fork 7
Adding SHACL validation #161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,14 +12,15 @@ import { getFileBaseName } from "../../utils/helpers.ts"; | |
| interface Props { | ||
| transformationResult: string; | ||
| refinedCsv: Matrix | undefined; | ||
| shaclConforms: boolean | null, | ||
| shaclReport?: string | ||
| } | ||
| const DownloadResults: React.FC<Props> = ({ transformationResult, refinedCsv }) => { | ||
| const DownloadResults: React.FC<Props> = ({ transformationResult, refinedCsv, shaclConforms, shaclReport }) => { | ||
| const downloadRef = React.useRef<HTMLAnchorElement>(null); | ||
| const source = useRecoilValue(sourceState); | ||
|
|
||
| const transformationConfig = useRecoilValue(transformationConfigState); | ||
|
|
||
|
|
||
| const downloadFile = (content: File | string | undefined, defaultName: string, mediaType: string) => { | ||
| if (!downloadRef.current || !content) return; | ||
| if (typeof content === "string") { | ||
|
|
@@ -87,8 +88,12 @@ const DownloadResults: React.FC<Props> = ({ transformationResult, refinedCsv }) | |
| <CardHeader title="Download RDF" avatar={<FontAwesomeIcon icon="file" />} /> | ||
| <CardContent className={styles.downloadContent}> | ||
| Download the transformed Linked Data (RDF) to your local machine. | ||
|
|
||
| {shaclConforms === false ? <span><br /><br /><FontAwesomeIcon icon={["fas", "xmark"]} /> The data does not conform to the selected SHACL shape</span> : null} | ||
| {shaclConforms === true ? <span><br /><br /><FontAwesomeIcon icon={["fas", "check"]} /> The data does conform to the selected SHACL shape</span> : null} | ||
|
Comment on lines
+91
to
+93
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be nice to use the mui Alert component for this. This might also offer a "better" place to allow the user to download the SHACL report (either as an action or as a button in the alert) |
||
| </CardContent> | ||
| <CardActions> | ||
| {shaclConforms !== false ? | ||
| <Button | ||
| onClick={() => downloadFile(transformationResult, rdfFileName, "application/n-triples")} | ||
| component="span" | ||
|
|
@@ -97,7 +102,20 @@ const DownloadResults: React.FC<Props> = ({ transformationResult, refinedCsv }) | |
| style={{textTransform: 'none'}} | ||
| > | ||
| Download RDF | ||
| </Button> | ||
| </Button> : | ||
| <SplitButton | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think UX wise, splitting into two makes a bit more sense, as they download separate results. a SHACL report and the transformed output. The split button approach makes more sense if we're talking about the same product. (Unless I missed something and the SHACL report does include the transformed result) |
||
| actions={["rdf", "shacl"]} | ||
| getOptionsLabel={(option) => option === 'rdf' ? `RDF` : 'SHACL report'} | ||
| getButtonlabel={(selectedOption) => selectedOption === 'rdf' ? `Download RDF` : 'Download SHACL report'} | ||
| onActionSelected={(result) => { | ||
| if (result === 'rdf') { | ||
| downloadFile(transformationResult, rdfFileName, "application/n-triples") | ||
| } | ||
| else { | ||
| downloadFile(shaclReport, 'shacl-validation-report.nt', "application/n-triples") | ||
| } | ||
| }} | ||
| />} | ||
| </CardActions> | ||
| </Card> | ||
| <Card variant="outlined" className={styles.downloadSegment}> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,10 @@ import { currentTokenState } from "../../state/clientJs.ts"; | |
| import DownloadResults from "./DownloadResults.tsx"; | ||
| import { wizardAppConfig, PublishElement } from "../../config/index.ts"; | ||
| import { Matrix } from "../../Definitions.ts"; | ||
| import { Validator } from 'shacl-engine' | ||
| import rdf from 'rdf-ext' | ||
| import { Parser, Store, Writer } from 'n3' | ||
|
|
||
| interface Props { } | ||
| export const Step = 3; | ||
| const Publish: React.FC<Props> = ({ }) => { | ||
|
|
@@ -21,6 +25,9 @@ const Publish: React.FC<Props> = ({ }) => { | |
| const [transformationResult, setTransformationResult] = React.useState<string>(); | ||
| const [transformationError, setTransformationError] = React.useState<string>(); | ||
| const [progress, setProgress] = React.useState(0); | ||
| const [conforms, setConforms] = React.useState(null); | ||
| const [shaclReportTurtle, setShaclReportTurtle] = React.useState(''); | ||
|
|
||
|
|
||
| function LinearProgressWithLabel(props: LinearProgressProps & { value: number }) { | ||
| return ( | ||
|
|
@@ -252,6 +259,25 @@ function LinearProgressWithLabel(props: LinearProgressProps & { value: number }) | |
| source: tempRefinedCsv || parsedCsv, | ||
| type: "rml", | ||
| }); | ||
|
|
||
| if (transformationConfig.shaclShape) { | ||
| const shaclShapes = await wizardAppConfig.getShaclShapes() | ||
|
|
||
| const selectedShaclShape = shaclShapes.find(shaclShape => shaclShape.iri === transformationConfig.shaclShape) | ||
| const parser = new Parser() | ||
| const quads = await parser.parse(transformationResult) | ||
| const store = new Store(quads) | ||
| const validator = new Validator(selectedShaclShape.store, { factory: rdf }) | ||
| const report = await validator.validate({ dataset: store }) | ||
|
|
||
| setConforms(report.conforms) | ||
|
|
||
| if (!report.conforms) { | ||
| const writer = new Writer() | ||
| for (const quad of report.dataset) writer.addQuad(quad) | ||
| writer.end((error, result) => setShaclReportTurtle(result)) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add a comment that this function is synchronous. In most functions, giving a callback indicates asynchronous functionality |
||
| } | ||
| } | ||
| setTransformationResult(transformationResult); | ||
| } | ||
| }; | ||
|
|
@@ -296,7 +322,7 @@ function LinearProgressWithLabel(props: LinearProgressProps & { value: number }) | |
| } | ||
| const publishOptions: { [P in PublishElement]: React.ReactElement } = { | ||
| download: ( | ||
| <DownloadResults refinedCsv={refinedCsv} transformationResult={transformationResult} key="Download-to-browser" /> | ||
| <DownloadResults shaclReport={shaclReportTurtle} shaclConforms={conforms} refinedCsv={refinedCsv} transformationResult={transformationResult} key="Download-to-browser" /> | ||
| ), | ||
| triplyDB: ( | ||
| <ErrorBoundary | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,4 +28,4 @@ | |
| body { | ||
| margin: 0; | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to solve this using CSS's
:hoverselector? The only issue I can forsee is that you need to be quite specific with the selector?