A knowledge organization system (KOS) service for Heliophysics.
Executable functional tests describe target use cases. Currently, the first two tests pass, so you can follow their bodies as usage examples. The rest of the functional test suite can be considered a roadmap for this tool.
You can pip install heliokos
to get the last-released version,
or you can git clone
this repository and pip install .
to build the tool using the current main
-branch head.
git clone
this repository, and in the root directory,
# in a python 3.11 env
pip install -e '.[dev,tests,linting]'
Ensure you configure access to an accessible MongoDB instance. To quickly achieve this with Docker (link to install),
docker run --name heliokos-mongo -d -v "$(pwd)/heliokos-mongo-data:/data/db" -p 23456:27017 mongo:6
The above will initialize and run a container based on DockerHub's mongo:6
docker image, that is,
the latest mongo
image with major version 6
. It maps your local port 23456
to the container's
port 27017
(what mongo exposes), and maps a local folder heliokos-mongo-data
in your
current directory to the container's /data/db
folder (where mongo stores its data). The folder
("volume") map is so that your mongo data can persist when stopping the docker container. The
container is called heliokos-mongo
, and this process will detach (via -d
) so that you don't need
to keep the terminal window open. You can check the status of the container with
docker ps -f name=heliokos-mongo
and stop and remove the container with
docker stop heliokos-mongo && docker remove heliokos-mongo
To re-initialize and run the container, recovering the last database state, re-run the docker run
command above.
Before starting the Web server, ensure relevant environment variables (e.g. MONGO_HOST
) are set in your shell session:
cp .env.example .env
# verify e.g. that a line `MONGO_HOST=localhost:23456` is present
export $(grep -v '^#' .env | xargs)
To start the Web server for development:
uvicorn heliokos.ui.main:app --reload
To load and step through the heliokos_repl.ipynb
Jupyter notebook:
python -m ipykernel install --user --name heliokos
jupyter lab
# open `heliokos_repl.ipynb`, and ensure the kernel "Kernel > Change Kernel" is set to `heliokos`.
All software dependencies are open-source and free. Because no funds are required to acquire these, this package is not subject to Supply Chain Risk Management.
# Example: run linting and tests for single module
tox run -e lint,py311 -- tests/test_units.py
# Example run single test by name
tox run -e py311 -- -k test_harmonizing_two_concept_schemes
# Example: run all tests
tox
HelioKOS data is modeled as RDF quads, persisted and queried using MongoDB via this schema:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://heliokos.example/node.schema.json",
"title": "Node",
"description": "A set of RDF quads sharing the same named graph (g) and subject (s).",
"type": "object",
"properties": {
"g": {
"description": "this node's named graph",
"type": "string",
"format": "iri"
},
"s": {
"description": "this node's subject",
"type": "string",
"format": "iri"
},
"edge": {
"type": "array",
"items": {
"type": "object",
"properties": {
"p": {
"description": "this quad's predicate",
"type": "string",
"format": "iri"
},
"o": {
"description": "this quad's object"
}
},
"required": ["p", "o"],
"minItems": 1
}
},
"env": {
"description": "system-internal envelope metadata",
"type": "object",
"properties": {
"lu": {
"description": "last-updated datetime (stored as bson `ISODate` type)",
"type": "string",
"format": "date-time"
}
}
}
}
}
and ensuring these MongoDB indexes:
[
{
"v" : 2.0,
"key" : {
"s" : 1.0,
"edge.p" : 1.0,
"edge.o" : 1.0,
"g" : 1.0
},
"name" : "s_1_edge.p_1_edge.o_1_g_1"
},
{
"v" : 2.0,
"key" : {
"edge.p" : 1.0,
"edge.o" : 1.0,
"s" : 1.0,
"g" : 1.0
},
"name" : "edge.p_1_edge.o_1_s_1_g_1"
},
{
"v" : 2.0,
"key" : {
"edge.o" : 1.0,
"edge.p" : 1.0,
"s" : 1.0,
"g" : 1.0
},
"name" : "edge.o_1_edge.p_1_s_1_g_1"
},
{
"v" : 2.0,
"key" : {
"g" : 1.0,
"s" : 1.0,
"edge.p" : 1.0,
"edge.o" : 1.0
},
"name" : "g_1_s_1_edge.p_1_edge.o_1"
},
{
"v" : 2.0,
"key" : {
"g" : 1.0,
"edge.p" : 1.0,
"edge.o" : 1.0,
"s" : 1.0
},
"name" : "g_1_edge.p_1_edge.o_1_s_1"
},
{
"v" : 2.0,
"key" : {
"g" : 1.0,
"edge.o" : 1.0,
"edge.p" : 1.0,
"s" : 1.0
},
"name" : "g_1_edge.o_1_edge.p_1_s_1"
}
]
The front-end UI uses the NASA Web Design System (NASAWDS), a rethemed version of the US Web Design System (USWDS). Use the USWDS site for documentation on how to style things.
HTML files for the various routes can be found in the /ui/templates
directory. They use python for templating.
Client-side assets are located in the /ui/static
directory. It includes subdirectories for /css
, /js
, and /img
files. The NASAWDS files are located in a dedicated /nasawds
subdirectory.
|- /ui
|- /templates
|- /static
|- /css
|- /img
|- /js
|- /nasawds
There is no build step for front end assets.
The entire app is server-rendered and progressively enhanced.
All UI interactions are either links or <form>
elements that submit to the server, which handles processing and redirects. Without JavaScript, the app works perfectly fine.
If JavaScript is available, a custom <hk-form>
Web Component adds Ajax support for <form>
elements and provides a more modern experience (more details on how that works below).
The HelioKOS app loads two stylesheets.
/nasawds/css/styles.css
- the main NASAWDS stylesheet./css/app.css
- customizations, overrides, and utility classes to extend the base stylesheet.
The HelioKOS app loads two JavaScript files.
/nasawds/js/uswds.min.js
- the main NASAWDS component JavaScript./js/app.js
- the main app-specific JavaScript.
All UI interactions are either links (<a href="...">
) or <form>
elements that submit to the server.
Every <form>
element should have a method
attribute that defines the HTTP method, and an action
attribute that defines the URL the <form>
should submit to.
<form method="post" action="/concept">
<!-- Form fields... -->
</form>
The app.js
file loads and initiates several Web Components that adds dynamic features and Ajax capabilities to interactive elements.
Note: for readability, all of the required NASAWDS classes have been removed from the examples below.
Adds Ajax support to form submissions.
Wrap <form>
elements in the <hk-form>
custom element to progressively add Ajax support.
By default, the <hk-form>
element will add an accessible status notification element, show a Submitting...
message, and prevent the user from submitting the form twice while waiting for a server response.
<hk-form>
<form method="post" action="/concept">
<!-- Form fields... -->
</form>
</hk-form>
The <hk-form>
element can also be customized with a handful of attributes...
prevent-default
- Stop the form from redirecting/reloading the page. Use this if you want to keep the user on the current page after the form submits.msg-submitting
- Customize the "submitting" message.msg-success
- Customize the "success" message.msg-error
- Customize the "error" message.target
- If you want to update the current UI after the form successfully submits, provide one or more selector strings (comma-separated) for the element to update. Each selector should be identical in the current UI and the returned HTML from the server.
With the msg-submitting
, msg-success
, and msg-error
attributes, you can include ${field_name}
in the attribute string to include the value of the matching field in your message.
<!--
This form includes some customizations.
After submitting, it will update the [data-hk-list="concepts"] element with new HTML.
-->
<hk-form
prevent-default
msg-submitting='Creating the "${pref_label}" concept...'
msg-success='The "${pref_label}" concept was added.'
target='[data-hk-list="concepts"]'
>
<form method="post" action="/concept">
<label for="pref_label">Preferred Label</label>
<input id="pref_label" name="pref_label" type="text" required>
<button>
Create Concept
</button>
</form>
</hk-form>
<!-- This gets updated after the form submits -->
<div data-hk-list="concepts">
<h2>Your Concepts</h2>
<p>You don't have any concepts yet.</p>
</div>
The <hk-form>
element emits several custom events that you can hook into to extend functionality in your app.
hk-form:submit
is emitted when the form is submitting. An object of field names and values is assigned to theevent.detail
property.hk-form:success
is emitted when the form is successfully submitted. Theevent.detail
contains the responsehtml
and form fielddata
as properties.hk-form:error
is emitted when there's an error submitting the form. Theevent.detail
contains the error object.
document.addEventListener('hk-form:success', function (event) {
// The submitted form web component
let form = event.target;
// The response HTML
let html = event.detail.html;
// The form data
let data = event.detail.data;
});
Dynamically restricts certain scheme concept relationships.
Wrap the <select>
elements and radio buttons used for defining data relationships in the <hk-relationships>
custom element to add a bit of extra functionality.
When the value of the <select>
element changes, the <hk-relationships>
component will disable that same value in the other <select>
element. A [key]
attribute is required.
<hk-relationships key="scheme-relationships">
<label for="subject_concept_id">Pick a Concept</label>
<select id="subject_concept_id" name="subject_concept_id" required>
<option value>- Select -</option>
<!-- ... -->
</select>
<fieldset>
<legend>Define how it's related</legend>
<input id="relation_narrower" type="radio" name="relation" value="narrower" checked required>
<label for="relation_narrower">is a broader concept than (<code>skos:narrower</code>)</label>
<input id="relation_related" type="radio" name="relation" value="related" required>
<label for="relation_related">is related to (<code>skos:related</code>)</label>
</fieldset>
<label for="object_concept_id">Pick another Concept</label>
<select id="object_concept_id" name="object_concept_id" required>
<option value>- Select -</option>
<!-- ... -->
</select>
</hk-relationships>
The <hk-relationships>
element can also be customized with two attributes...
deny
- An array of relationship combinations to disallow.key
- A unique identified for the element. When the form is submitted, this is used to reset disabled fields and update the[deny]
attribute.
Dynamically updates a <select>
menu after form submission.
Wrap the <select>
element in the <hk-update-select>
custom element to dynamically update it's <option>
elements after the form is submitted. A [key]
attribute is required.
<hk-update-select key="add-concept">
<label for="selected_concept">Pick a Concept</label>
<select id="selected_concept" name="selected_concept" required>
<option value>- Select -</option>
<option value="earth">Earth</option>
<option value="mars">Mars</option>
<option value="jupiter">Jupiter</option>
</select>
</hk-update-select>
Convert an input
into an API-driven type-ahead component.
Wrap the <input>
element in the <hk-combo-box>
custom element, and provide an [endpoint]
attribute that points to the data API. You can optionally set a [delay]
attribute to specify how many milliseconds to wait before querying the API.
<hk-combo-box endpoint="/concepts-search" delay="500">
<label for="search">Text input label</label>
<input id="search" name="search" required>
</hk-combo-box>
The component expects the API endpoint
to return an array of objects. Each object should have an id
and value
that will be used to generate additional markup.
As the user types, a list of available options will be displayed in a <datalist>
. A [pattern]
attribute will be added to the <input>
element with all of the available options for form validation purposes.
Additionally, a type="hidden"
field will be added. It will have the same name as the <input>
, with -id
added to the end (ex name="search-id"
). It's value will be dynamically set to the matching ID for the selected user text.
- bump
version
in pyproject.toml. - git commit
- git tag v$(pyproject.toml.version) # e.g.
git tag v0.0.5
. - python -m build
- python -m twine upload dist/*
- rm -rf dist