In Rally Webservices API, if I want to traverse a Story hierarchy, it's necessary to do a query for the parent story, then grab the Children collection(s) off of the returned Stories, and then recursively query on each Child until the process reaches the Leaf node results.
Question - is there a handy way to do this without iterating, by using a single query in the Lookback API?
This is one of the best features of the Lookback API.
Let's say you have this hierarchy:
Story 444
Story 555
Story 666
Defect 777 (via the Requirement field)
Task 12
Task 13
Story 888
The document for Task 12 would look like this:
{
ObjectID: 12,
_Type: "Task",
WorkProduct: 777,
_ItemHierarchy: [444, 555, 666, 777, 12],
...
}
So when you submit a query against a field with an array value (like _ItemHierarchy), it will match any member of the array.
To get everything that descend from 444 your find clause would include _ItemHierarchy: 444. See how that matches the _ItemHierarchy value for Task 12?
To get everything that descend from 333 your find clause would include _ItemHierarchy: 333. This also matches on Task 12.
To get just the Stories that descend from 444 (all Stories) your find clause would include:
_ItemHierarchy: 444,
_Type: "HierarchicalRequirement"
To get just the leaf Stories just add the clause Children: null.
The _ItemHierarchy also goes all the way up to PortfolioItems.
_ItemHierarchy is indexed so these queries should be very efficient.
Related
I am trying to understand how to build an API call where I can get data (json format) for the recipes, ingredients, and procedure as mention here. Anyone who could help me out with this?
https://en.wikibooks.org/wiki/Cookbook:Recipes
This URL has the recipe names and when clicked on each item it gets the ingredients and the procedure.
To get all recipes I would not use Cookbook:Recipes but rather Category:Recipes which is more complete.
The API call to list all recipes which are listed in Category:Recipes is the following:
https://en.wikibooks.org/w/api.php?action=query&generator=categorymembers&gcmtitle=Category:Recipes&gcmlimit=max&format=json&gcmcontinue=.
It will return you 500 recipes but there are more on Wikibooks. To get the remaining ones, use the continue -> gcmcontinue value in the response and append it to the next API call.
To get the ingredients and procedure of a recipe, call for example
https://en.wikibooks.org/w/api.php?action=query&prop=revisions&format=json&rvprop=content&rvslots=%2A&rvsection=1&titles=Cookbook:Biscuits.
You can use the | character to retrieve multiple recipes with the same API call:
https://en.wikibooks.org/w/api.php?action=query&prop=revisions&format=json&rvprop=content&rvslots=%2A&titles=Cookbook:Biscuits|Cookbook:Baklava.
If you want to retrieve only the ingredients or only the procedure of recipes, use the additional parameter rvsection=. Most of the time (but not always) the ingredients are in the first section and the procedure is the second section. So calling
https://en.wikibooks.org/w/api.php?action=query&prop=revisions&format=json&rvprop=content&rvslots=%2A&rvsection=1&titles=Cookbook:Biscuits&rvsection=1 returns you the ingredients for making biscuits and
https://en.wikibooks.org/w/api.php?action=query&prop=revisions&format=json&rvprop=content&rvslots=%2A&rvsection=1&titles=Cookbook:Biscuits&rvsection=2 returns you the procedure for making biscuits.
I'm not sure if it was your question, but in addition to Pascalco's answer, it worth note that you will NOT be able to get structured json data that details ingredients with their quantities and procedures, ie something like:
{ "ingredient": "milk", "quantity": { "number": "1", "unit":"liter"}}
The API will drop the raw mediawiki's syntax page content in one single field, and a few extra metadata about the page.
Moreover, the fact that those pages do not use templates make this type of data very difficult to extract, either with a syntax parser or html parser.
Please bear with me if the title is a bit confusing, I will try my best to explain my question below.
Say I have the following two endpoints
api/companies (returns a list of all companies like below)
[{name: "company1", id: 1}, {name: "company2", id: 2}]
api/companies/{companyeId}/employees (returns a list of all employees for a specific company like below)
[{name: "employee1", id: 1}, {name: "employee2", id: 2}]
What the client side needs is a list of companies, each one of which has a list of employees. The result should looks like this:
[
{
name: "company1",
id: 1,
employees: [ {name: "employee1", id: 1}, {name: "employee2", id: 2} ]
},
{
name: "company2",
id: 2,
employees: [ {name: "employee3", id: 3}, {name: "employee4", id: 4} ]
},
]
There are two ways I can think of to do this:
Get a list of company first and loop through the company list to
make a api call for each company to get its list of employees. (I'm wondering if this is a better way of design because of HATEOAS principle if I understand correctly? Because the smallest unit of resource of api/companies is company but not employees so client is expected to discover companies as the available resource but not employees.)
a REST client should then be able to use server-provided links dynamically to discover all the available actions and resources it needs
Return a list of employees inside each company object and then return a list of companies through api/companies. Maybe add a query parameter to this endpoint called responseHasEmployees which is a boolean default to be false, so when user make a GET through api/companies?responseHasEmployees=true, the response body will have a list of employees inside each company object.
So my question is, which way is a better way to achieve the client side's goal? (Not necessarily has to be the above two.)
Extra info that might be helpful: companies and employees are stored in different tables, and employees table has a company_fk column.
Start by asking yourself a couple of questions:
Is this a common scenario?
Is it logical to request data in this way?
If so, it might make sense to make data available in this way.
Next, do you already have api calls that pass variables implemented?
Based on your HATEOAS principle, you probably shouldn't. Clients shouldn't need to know, or understand, variable values in your url.
If not, stay away from it. Make it as clean to the client side as possible. You could make a third distinct api "api/companiesWithEmployees" This fits your HATEOAS principle, the client doesn't need to know anything about parameters or other workings of the api, only that they will get "Companies with Employees".
Also, the cost is minimal; an additional method in the code base. It's simpler for the client side at a low cost.
Next think about some of the developmental consequences:
Are you opening the door to more specific api requests?
Are you able to maintain a hard line on data you want accessible through the api?
Are you able to maintain your HATEOAS principle in that the clients know everything they need to know based on the api url?
Next incorporate scenarios like this into future api design:
Can you preemptively make similar api calls available? ie (Customers and Orders, would you simply make a single api call available that gets the two related to each other?)
Ultimately, my answer to your question would be to go ahead and make this a new api call. The overhead for setting up, testing, and maintaining this particular change seem extremely small, and the likelihood of data being requested in this way appears high.
I assume that the client you build is going to have an interface to view a list of companies where there will be an option to view employees of the company. So it is best to do it by pull on demand and not load the whole data at once.
If you can consider a property of your resource as a sub-resource, do not add the whole sub-resource data into the main resource API. You may include a referral link which can be used by the client to fetch the sub-resource data.
Here, in your case,
Main-Resource - Companies
Sub-Resource - Employees
Company name, contact number, address - These are properties of the company object and not the sub-resource of a company, whereas, employees can be very well considered as sub-resource.
The documentation describes a meta collection as a combination of 2 (or more) different APIs...
By default, a meta API endpoint returns all data from all of its
sub-APIs, organized by collection.
But, what I find is that it does not do this. It grabs partial data from some sub-APIs and fails silently, without logging an error.
API #1 Result
COLLECTION2
ROW LIST.HREF LIST.TEXT INDEX URL
1 http://www.amazon.com/Org...pollux+organix+canned+dog Canned Dog Food 3 http://www.austinpetsalive.org/donate/wish-list/
2 http://www.amazon.com/Pre...s-Large/lm/R34ISSXSRJPA71 Premier Brand Martingale collars (Pink, Silver, Blue, Red, and Orange) sizes XL or L 4 http://www.austinpetsalive.org/donate/wish-list/
3 http://www.amazon.com/Pre...ords=medium+gentle+leader Medium & Large Gentle Leaders 5 http://www.austinpetsalive.org/donate/wish-list/
API #2 Result
COLLECTION2
ROW LIST.HREF LIST.TEXT INDEX URL
1 Dry kitten and adult dog food (we ask that the first ingredient listed on the bag be meat). Some Brands we love include: Purina One, Pro-Plan, Wellness, Evolve, Blue Buffalo, and Kirklands 9 http://pawsshelter.org/donate/wishlist/
2 Cat Litter 10 http://pawsshelter.org/donate/wishlist/
3 Kongs, Balls, Durable Toys, Puzzle Toys 11 http://pawsshelter.org/donate/wishlist/
Meta API Result Collection 2 -- it's the entire data set from API #2 and none from API #1
COLLECTION2
ROW LIST.HREF LIST.TEXT INDEX URL API
1 Dry kitten and adult dog food (we ask that the first ingredient listed on the bag be meat). Some Brands we love include: Purina One, Pro-Plan, Wellness, Evolve, Blue Buffalo, and Kirklands 132 http://pawsshelter.org/donate/wishlist/ PAWS Shelter and Humane Society
2 Cat Litter 133 http://pawsshelter.org/donate/wishlist/ PAWS Shelter and Humane Society
3 Kongs, Balls, Durable Toys, Puzzle Toys 134 http://pawsshelter.org/donate/wishlist/ PAWS Shelter and Humane Society
The data structure is verbatim the same. Why don't they combine under meta API?
Testing
A meta API combination worked with 2 APIs, each with one collection.
When there is more than one collection defined, certain combinations of APIS combined partially. Each collection filled with the contents of one or the other API, but never both.
The 3 APIs I need to combine result in one site's results in all of the collections, and nothing from the other two in any collection, and no error logged.
The flaky results seem tied to the collections. But I've moved on to a klugey temp fix, which is to call each API separately and combine the JSON results in my app. 3 API calls instead of one. Might be looking for another tool, soon.
Now it works
Since I posted this question in October, KimonoLabs has updated their app and I am now having consistent success with creating and using a Meta API for the set of single APIs I posted above.
That's the positive.
The catch is that you cannot use the URL parameters in a meta API, so if you'd written post-process modify results functions, they will not be executed, even if you use kimmodify=1.
I'm a little confused as to what the collection URI's should return.
Say I have a collection, /users of decently large elements. Then we have the expected:
GET /users/123 // returns user element with identifier 123
But what should
GET /users
return? If the collection is large, and the elements are large, it's probably not a good thing to return all elements. Perhaps instead, GET requests at the /users level should return element summaries (identifiers and possibly a few properties), while GET requests at the /users/ level should return actual elements. Then you could do something like;
GET /users
> [{name: abc, id: 1}, {name: def, id: 2}, {name: ghi, id: 3}, ...]
GET /users/2
> {name: def, prop1: *, prop2: *, ...}
Which could be a good way to lazily load data if you wanted to preview important application-domain properties before requesting them in their entirety. With this, in order to apply queries, you'd do something like
GET /users?prop1=value // returns element summaries of elements with prop1=value
GET /users/?prop1=value // returns elements with prop1 = value
Is this approach OK? Or do the other methods acting on /users then loose meaning.. (ex. PUT /users ?)
I personally like the style where /users does not return all users but returns information needed to then query specific users. So the summaries approach is how I would generally write it.
If you were filtering or querying, I would go with the first one you provided:
GET /users?prop1=value
I don't care for the second one
GET /users/?prop1=value
because it can be easily misunderstood or lead to confusing and unintended bugs (missing one slash still works but completely changes the results).
You might want to go with an approach of an alternative URL for returning specific users based on search parameters such as
GET /users?prop1=value // returns element summaries based on results of prop1 matching
GET /users/find?prop1=value // returns elements with prop1 = value
Obviously your wording could change (find/search) or you could use a completely different URI but I try to avoid things where two different meanings are a single character/symbol apart to avoid unintended mistakes. Another option would be to make sure you outline this clearly in provided documentation so anyone consuming your API is alerted about this potential.
Actually expanding on this I would go with the all construct.
So instead of using find/search I would provide:
GET /users // returns summaries
GET /users/# // returns element
GET /users/all // returns all elements
GET /users/all?prop1=value // returns all elements that match the filter
I have an SQLAlchemy model called Post. A post can have a parent post (foreign key on post.id, adjacency list pattern), and children (posts where its id is the parent_id). Each post also contains its materialized path (post.ancestry) to make querying descendants easy.
>>> post.creation_date
<<< 2013-07-24 20:39:56.158990
>>> post.parent_id
<<< 14
>>> post.ancestry
<<< '1,3,5,11,14,'
My challenge is to get a list of root posts (posts without parents) and sort them by the age of their youngest descendant, the way GMail does with the inbox. The conversation at the top has the newest message, and the conversation at the bottom of the inbox has the oldest message.
What would the query for this look like?