-
Notifications
You must be signed in to change notification settings - Fork 2
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
feat: search projects #1330
feat: search projects #1330
Conversation
🦋 Changeset detectedLatest commit: bbdce1d The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codecov Report
@@ Coverage Diff @@
## master #1330 +/- ##
==========================================
- Coverage 81.41% 81.03% -0.39%
==========================================
Files 194 195 +1
Lines 13453 13509 +56
Branches 753 754 +1
==========================================
- Hits 10953 10947 -6
- Misses 2492 2554 +62
Partials 8 8
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
<cube-projects/status> { | ||
<cube-projects/status> a ${hydra.Collection}, ${schema.DefinedTermSet} ; | ||
${hydra.member} ${Draft}, ${Published} ; | ||
. | ||
|
||
${Published} a ${schema.DefinedTerm} ; | ||
${rdfs.label} "Published" . | ||
|
||
${Draft} a ${schema.DefinedTerm} ; | ||
${rdfs.label} "Draft" . | ||
} |
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.
A static collection of statuses to populate the search dropdown
${shape('cube-project/search')} { | ||
${shape('cube-project/search')} a ${sh.NodeShape}, ${hydra.Resource} ; | ||
${sh.property} [ | ||
${sh.name} "Keyword Search" ; | ||
${sh.order} 10 ; | ||
${sh.path} ${hydra.freetextQuery} ; | ||
${sh.minCount} 1 ; | ||
${sh.maxCount} 1 ; | ||
], | ||
[ | ||
${sh.name} "Author" ; | ||
${sh.path} ${dcterms.creator} ; | ||
${sh.order} 20 ; | ||
${sh.minCount} 1 ; | ||
${sh.maxCount} 1 ; | ||
${dash.editor} ${dash.InstancesSelectEditor} ; | ||
${hydra.collection} </users> ; | ||
], | ||
[ | ||
${sh.name} "Status" ; | ||
${sh.path} ${schema.creativeWorkStatus} ; | ||
${sh.order} 30 ; | ||
${sh.minCount} 1 ; | ||
${sh.maxCount} 1 ; | ||
${dash.editor} ${dash.InstancesSelectEditor} ; | ||
${hydra.collection} </cube-projects/status> ; | ||
] ; | ||
. |
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.
Shape which defines the fields for search form
<users> { | ||
<users> a ${hydra.Collection} ; | ||
${hydra.manages} [ | ||
${hydra.property} ${rdf.type} ; | ||
${hydra.object} ${schema.Person} | ||
] . | ||
} | ||
` |
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.
This reflects a bigger change where users actually become independent resources in their own named graphs. Here, the collection is added to retrieve them
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.
I love this change!
apis/core/lib/auth.ts
Outdated
function updateUserResource(): RequestHandler { | ||
const users = new TermSet() | ||
|
||
return (req, res, next) => { | ||
if (req.user?.id && !users.has(req.user.id)) { | ||
const { id, name, sub } = req.user | ||
users.add(id) | ||
|
||
req.once('end', () => { | ||
DELETE` | ||
GRAPH ${id} { | ||
${id} ${schema.name} ?name . | ||
} | ||
`.INSERT` | ||
GRAPH ${id} { | ||
${id} ${schema.name} "${name || sub}"; a ${schema.Person} . | ||
} | ||
`.WHERE` | ||
OPTIONAL { | ||
GRAPH ${id} { | ||
${id} ${schema.name} ?name . | ||
} | ||
} | ||
`.execute(req.labyrinth.sparql.query) | ||
.catch(warning) | ||
}) | ||
} | ||
next() | ||
} | ||
} |
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.
Since we want users as resources, they need to be created at some point.
I added this middleware to run after authentication, so that any one that successfully logs in will have their user graph created automatically.
Importantly, the users
set ensures that as long the API process is running, every user will be created only once.
A future improvement could be to actually load existing users in the beginning. On the other hand, this way will also keep the names updated, shall they change on the auth provider...
@@ -103,7 +101,7 @@ export async function importProject({ | |||
const projectNode = await store.createMember(projectsCollection.term, id.cubeProject(label)) | |||
|
|||
const project = createMinimalProject(projectNode, { | |||
creator: { id: user, name: userName }, | |||
creator: user, |
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.
Since we now have graphs for users, only the URI will be stored with the project now, unlike the lazy duplication we've had so far
export function byFreeText({ subject, object }: Pattern) { | ||
return sparql` | ||
${subject} ${rdfs.label}|${schema.alternateName}|${schema.disambiguatingDescription} ?freetextSearched . | ||
|
||
FILTER(REGEX(?freetextSearched, "${object.value}", "i")) | ||
` | ||
} | ||
|
||
export function byStatus({ subject, predicate, object }: Pattern) { | ||
return sparql` | ||
${subject} ${cc.dataset} ?dataset . | ||
|
||
graph ?dataset { | ||
?dataset ${predicate} ${object.term} . | ||
} | ||
` | ||
} | ||
|
||
export function byUser({ subject, predicate, object }: Pattern) { | ||
return sparql` | ||
${subject} ${predicate} ${object.term} . | ||
` | ||
} |
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.
Functions which provide the SPARQL patterns to search by respective query params (see apis/core/hydra/projects.ttl)
+ })) | ||
}) | ||
|
||
req.dataset = () => { |
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.
A temporary patch to hydra-box which fixes handling of +
in query string values. See zazuko/kopflos#104
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.
Could you provide me a link to learn more about these kind of patches? I haven't seen this before
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.
This is done using patch-package
When you modify a library in node_modules/foo-bar
, you the run npx patch-package foo-bar
. That will create the patch file.
Running patch-package
in postinstall script applies the package when installed
|
||
export const displayLanguage = ['en', 'de', 'fr', ''] | ||
|
||
export function serializeProjectsCollection (collection: ProjectsCollection): ProjectsCollection { | ||
return Object.freeze({ | ||
...serializeResource(collection), | ||
searchParams: clone(collection.pointer), |
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.
The server echoes the query string params as RDF. This makes a copy so that the form does modify the representation retrieved from the API
ui/src/views/CubeProjects.vue
Outdated
onSearch (e: CustomEvent) { | ||
const template = this.operation?.get<IriTemplate>(hydraBox.variables) | ||
if (template && e.detail?.value) { | ||
const url = template.expand(e.detail.value) | ||
const search = new URLSearchParams(url.substring(url.indexOf('?'))) | ||
const entries = [...search.entries()].filter(([, value]) => !!value && value !== '""') | ||
|
||
this.$router.push({ | ||
query: Object.fromEntries(entries), | ||
}) | ||
} | ||
} |
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.
I don't exactly like this.
The search is triggered by router query param change. In a different scenario I would construct the URL from template
and then use that as a whole to change the "route"
My best solution here is to round-trip the expanded template back into the query params and push them to the router
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.
yeah, it's difficult to understand. Perhaps you could factorize it in a function with a name ('pushSearchRoute'?) to explain the intent?
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.
All right, I moved that logic to a function exported from @/router
.
A bit of a mouthful. That might be the longest function name in this project now 🙈
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.
well, they don't charge for number of chars in function names ;)
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.
Else Java people would be richest :D
@l00mi a bigger change to be aware of is that users become their own resources. Until now the user names were simply stored with projects themselves graph <project> {
<project> dcterms:creator <user> .
<user> schema:name "John Doe" .
} Now it will be like graph <project> {
<project> dcterms:creator <user> .
}
graph <user> {
<user> schema:name "John Doe" ; a schema:Person , hydra:Resource .
} Projects already referred to users with URIs so I don't find this a breaking change per se. prefix dcterms: <http://purl.org/dc/terms/>
prefix schema: <http://schema.org/>
prefix cc: <https://cube-creator.zazuko.com/vocab#>
prefix hydra: <http://www.w3.org/ns/hydra/core#>
DELETE {
GRAPH ?project {
?user schema:name ?name
}
}
INSERT {
GRAPH ?user {
?user schema:name ?name ; a schema:Person , hydra:Resource .
}
} WHERE {
GRAPH ?project {
?project a cc:CubeProject ; dcterms:creator ?user .
?user schema:name ?name .
}
} |
req.once('end', () => { | ||
DELETE` | ||
GRAPH ${id} { | ||
${id} ${schema.name} ?name . |
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.
If users live in their own named graph, wouldn't it be cleaner to delete the graph as a whole?
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.
Well, this is but not deleting the whole graph. Only schema:name
:)
@@ -17,6 +17,11 @@ | |||
Cancel | |||
</o-button> | |||
</div> | |||
<div class="control" v-if="showClear"> |
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.
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.
Yes, I expected you'd say that. Unfortunately, oruga select does not appear to allow that
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.
Looks ok to me!
Only (optional) comments
search.mp4