Re: REST API

"John P. Rouillard" <[email protected]>
Newsgroups gmane.comp.bug-tracking.roundup.devel
Message-ID <[email protected]>
Hi Everybody:

My original concept/use case for the REST interface was to use it
to drive a replacement front end for roundup. The front end could be a
javascript single page application written in react, angular, vue
etc. It could be a hybrid of back end HTML and front end javascript
where all the data comes from the REST endpoint.

In this case, some of the functionality we currently build into the
html templates needs to be exposed via the rest interface. I will
return to this thought below. In general I will probably bring up more
questions than answers 8-). Sorry for the length of this email.

However to answer a few questions...

In message <[email protected]>,
Ralf Schlatterbeck writes:
>I'm now taking the private discussion I had with John to the list so
>that more people are informed about what is done.
>
>As far as I can tell what is still open is
>- A version Identifier after /rest, e.g. /rest/.../v1 or /rest/.../1 for
>  the initial release. Your latest commit is ambiguos, it says that
>  /rest returns supported version but then goes on with /rest/data --
>  which should then be /rest/.../0/data?

The metadata returned by /rest:

  {
    "data": {
      "links": [
	{
	  "rel": "summary",
	  "uri": "https://example.com/demo/rest/summary"
	},
	{
	  "rel": "self",
	  "uri": "https://example.com/demo/rest"
	},
	{
	  "rel": "data",
	  "uri": "https://example.com/demo/rest/data"
	}
      ],
      "supported_versions": [
	"1"
      ],
      "default_version": "1"
    }
  }

is meant to define possible versions and the version used if no
specific version is requested. How the client specifies the version is
still an open question.

>  Or should we make the initial version v0?
>  Should we have some prefix before the version (indicated by ... above)
>  This could be done in class Routing, which would become more
>  complicated when we add additional versions.

I originally proposed putting the version specifier in the URL,
e.g. /v1/. However the URL in REST is supposed to be a resource, the
url's

   http://foo/tracker/rest/.../v1/issue/20

and

   http://foo/tracker/rest/.../v2/issue/20

are still the same resource. Consider the roundup web
interface. .../tracker/issue14 is a constant. You can make it look
different by adding ?@template to the constant url.  So I really don't
like the /v1/, /v2/ concept, it's just the first implementation I saw
and has the advantage of being browser friendly.


I think implementing a query parameter to control the version is
similar to @template. So .../rest/data/issue/1?@version=1 is
better. It may also be cleaner to implement when we have multiple
versions.

The "correcter" way would be to set the version in the Accept header
in some form. However controlling the HTTP headers from a browser is
difficult.  By supporting ?@version=1 somebody investigating the rest
interface in a browser could enter:

  rest/data/issue/21?@version=1
  rest/data/issue/21?@version=2

and see the different output. (This is the same reason for supporting
.json or .xml at the end of the url to control the returned data format.)

I think version 0 is fine. The question is what form should the
version take? Should it be 0.0, or just 0. In X.y versioning would
mean that any client that understands version X.0 would be able to
understand version X.1 or newer. Compare to HTTP 1.0 vs 1.1. A version
0.3 client could understand a version 0.99 response but might not
understand a 1.0 response.

This would let us refine the response to add fields that can be
ignored by older clients. With the x.y format, the y allows us to
identify the exact format for bug reports etc.

We could use just plain version numbers 0, 1, 2 etc. However a version
0 client wouldn't know if it could understand a version 7 response. It
would have to assume that it can't. We could make each number a
breaking change, so that version 1 could not be understood by version
0 clients. But we would have no way to track minor changes in bug
reports. So there may be 3 or 4 different outputs all labeled with
version 0.

>- Hard-coded /data: Are you working on this? If not I think I know the
>  Roundup-API well enough to fix this

I am not sure what you mean by this. Do you mean the result of accessing:

  /rest/data/

If so, I am not working on it. Returning the list of URL's and objects
that can be accessed via /rest/data/ would be good.

>- Hard-coded class in /summary: This should also be fixed, I have quite
>  some trackers that simply don't have an 'issue' class.
>  The code there (and the permission checks) should probably be a lot
>  more dynamic, we could leave out the status if no permission etc.

Agreed.

This brings up another point, how to customize the data returned based
on the schema.

Given my original concept with REST driving the UI, let's look at how
we handle changing the status of an issue. A GET on rest/data/issue/21
can return (using pseudojson):

{ data: { @etag: "aaaaa",
          attributes: { 
	    title: "title here",
	    nosy: [ "1", "4" ],
	    status: "1",
	    assignedto: "4",	    
	    ...
	  }
          id: "21",
	  link: "https:..../rest/data/issue/21",
	  type: "issue",
  	  ...
  }
}

Now suppose I want to change status. Also let's assume I have a
workflow set up. Using the HTML interface, I would have an option
list like:

  New (1)
  Open (2)
  [Stalled (3)]
  [Closed (4)]
  Rejected (5)

The []'s indicate that the option is disabled and unselectable. So
from the current state of "New", the only change would be to value 2
state Open, or value 5 state Rejected. Stalled and Closed are right out.

How do we represent the same in REST?

One way might be to implement an @allowed_changes property. So a GET on
rest/data/issue/21/status/@allowed_changes would return:

{ 
  data: {
     @etag: "aaaaa",
     @links: {
     	new: { id: "1",
	       label: "new",
	       rel: "self"
	       uri: "rest/data/issue/21/status"
	}  
        open: { id: "2",
	        label: "open",
		uri: rest/data/issue/21/status
 	}
	rejected: { id: "5",
		    label: "rejected",
		    uri: rest/data/issue/21/status
	}		
     }
   }
}   

this would tell the client that the user is allowed to open or retire
the issue, but nothing else. To change to open, PUTting the id (2) to
the uri will make the change. (I assume the label could be localized
into a different language, but the keys of the @links dictionary would
not be translated.)

I think the rest interface can be augmented from the tracker. This
would be similar to the way that new actions can be defined in the web
interface. So it should be possible to construct a handler for new
@properties that are unique per tracker and can return the proper
elements (id, label, uri etc.) to allow the client code to do
something useful.

In a similar way

  http://roundup.sourceforge.net/docs/customizing.html#restricting-the-list-of-users-that-are-assignable-to-a-task

could be implemented to get back a list of users who are allowed to be
the assignedto user.

As another use case, let's look at displaying the activity, assignedto
user, and title for all open issues.

A GET on: rest/data/issue?status=open (note the current syntax is
different and uses ?where_status=open) would return an array like:

{ data: [
    { id: 1, link: rest/data/issue/1 }
    { id: 2, link: rest/data/issue/2 }
    { id: 3, link: rest/data/issue/3 }
  ]
}

Now I need to do a GET on each link/uri to get the title and activity
to display to the user. So I could do a GET on

  rest/data/issue/1?@fields=title,activity,assignedto

to get those three pieces of info (note the current interface uses
?fields=...). I can use the strings returned for title and activity
directly. However the value of assignedto is:

  "4"

Now I need to guess how to use this string to get a link to the user
represented by "4". Magically, I do a GET on
rest/data/user/4?fields=username to see:

   { data: { attributes: { username: fred },
             id: "4",
	     link: rest/data/user/4,
	     type: "user",
	     @etag: ...
	     }

So now I have the display name for the user.  Assuming I have N
issues, this means I need: 2N+1 trips to the rest endpoint to display:
title, activity and owner to the interface user.

We have two issues here:

  1) lots of round trips and the latency delay
  2) the need to figure out a magic url for the assignedto user

Solving 2 would mean that the response for an issue needs to look more like:

{ data: { @etag: "aaaaa",
          attributes: { 
	    title: "title here",
	    nosy: [ {id: "1", link: rest/data/user/1},
	             {id: "4", link: rest/data/user/4} ],
	    status: "1",
	    assignedto: { id: "4", link: rest/data/user/4 }
	    ...
	  }
          id: "21",
	  link: "https:..../rest/data/issue/21"
	  type: "issue"
  	  ...
  }
}

to provide links for every reference.

This removes the need for magic to figure out how to get additional
data.  Also it is cheap to generate since no additional database
lookups are needed to provide the link. It also implements HATEOAS and
decouples the client from the implementation. The nosy property could
be a multilink to a "nosy_users" class. The client would just blindly
follow the link and still work. However, it does bloat the response.

Solving problem 1 would mean embedding fields and objects inside the
response. Theoretically I could do a GET on:

  rest/data/issue?status=open&@attributes=title,activity,assignedto&@embed=assignedto

which returns:

 { data: [
    { id: 1, attributes: {title: "my title", activity: "2018-05-18.01:54:17",
         assignedto: { id: "4", link: rest/data/user/4, label: "fred" },
      link: rest/data/issue/1 }
    { id: 2, attributes: {title: "A title", activity: "2018-07-18.01:54:17",
         assignedto: { id: "5", link: rest/data/user/5, label: "bob" },
      link: rest/data/issue/2 }
    { id: 3, attributes: {title: "B title", activity: "2018-03-18.01:54:17",
         assignedto: { id: "6", link: rest/data/user/6, label: "jim" },
      link: rest/data/issue/3 }
    ]
 }

This returns all the data I need in one transaction. (The value of
label is the labelprop for the class.) But there is a cost to
this. Getting the related data (e.g. label info for users) results in
a number of additional database accesses and is a concern for larger
databases. But is this a good way to do this?

>- Docs :-)

Yup.

So what other use cases and issues should we consider for the rest
interface?

As I said above I have more questions here, but I hope this has given
people something to think about for the rest interface.

While this is a development discussion, does anybody think the use case
discussion would be useful on the users list?

--
				-- rouilj
John Rouillard
===========================================================================
My employers don't acknowledge my existence much less my opinions.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.