by Richard Taylor : 2020-03-10
The use of Links to explicitly represent different types of connections between Items has several advantages. One of them is that you can use the resulting connectivity to help you find useful groups of things.
For example, if we want to say that an EPIC contributes to a PROJECT, then we can represent that relationship with a CONTAINS link.
Item(type=PROJECT) + Link(type=CONTAINS) + Item(type=EPIC)
This is much more powerful than simply adding a "project" property to the EPIC. Firstly, the PROJECT can be a first-class citizen of our data world, with as many of its own properties and relationships as we need. And secondly, the EPIC can easily be associated with more than one PROJECT, and in different ways; it may have a CONTAINS link with one PROJECT and an ASSISTS link with another.
To start exploring you need an entry point. So the first thing to do is implement a very simple search that can find Items based on their properties.
You can see in this commit I have gone super simple. The basic search for Items is of this form:
GET /v1/items?type=PROJECT
Where "type" is a property name and "PROJECT" is the property value. For the time being only one property is queried, but it is easy to see how this could be extended.
What is not so obvious is how this could be extended to advanced queries. Say we wanted to find something using a syntax like a programming language:
type in {EPIC, STORY} and created_at during 2020-02
How would that fit into a URL query string? With all the escaping required it would be pretty unreadable. I think it is likely that advanced queries will be better sent as base64 encoded strings.
Anyway, I digress, that question is for another day. Take a look at the search_items method in the api.py module:
items = storage.find_items(key, value)
(user, filtered_items) = \
authority.filter_readable_items(token, items)
logging.item_search(filtered_items, user)
return filtered_items
Notice that we need the Authority object to filter the search results and remove any Items that the user does not have permission to see. Obviously this could leave us with an empty list; or a NotAuthorisedError if the user does not have permission to carry out searches at all.
Before returning the list we log the IDs of all the Items in the result.
Once we have a starting point we can expand our exploration by following Links from an Item. To do this we don't need to search for Links by their properties, but by their "source" and "target" attributes:
GET /v1/links?source=123456
GET /v1/links?target=123456
GET /v1/links?either=123456
Notice that we can explicitly say "Item 123456 is the source" or "Item 123456 is the target" or we can use "either" to say that we want all the Links where "Item 123456 is the source or the target".
Again in the code we use the Authority object to "hide" any Links that the user should not see:
(user, filtered_links) = \
authority.filter_readable_links(token, links)
logging.link_search(filtered_links, user)
return filtered_links
And log the IDs of the Links that we return.
As you would expect there is a new test module api_tests_search.py which sets up a few Items with Links between them and then does a variety of successful and unsuccessful searches.
Now we have enough of an API to play with. It is pretty minimal, on purpose. This is the Agile way. Make enough to try out, then learn what you need to do next by using what you already have.