-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
API: hosting integrations endpoint versioning/structure #10216
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
Conversation
Review initial JSON response to keep consistency with APIv3 resources. It uses a small modified version of the APIv3 serializers for known resources, removing some URL fields that can't be resolved from El Proxito. Sharing the same serializers that our APIv3 make our response a lot more consitent between the endpoints and allow us to expand the behavior by adding more fields in a structured way. Besides, these changes include a minimal checking of the version coming from `X-RTD-Hosting-Integrations-Version` header to decide which JSON structure return, allowing us to support multiple version at the same time in case we require to do some breaking changes. The JavaScript client is already sending this header in all of its requests. See readthedocs/addons#28
63cf0dd
to
4abfe61
Compare
Follow changes from readthedocs/readthedocs.org#10216
I feel like we may have had similar conversations in the past, but are we using any versioning patterns like this anywhere else? I think we maybe talked about what this looks like for APIv3 or our configuration file in the past |
@agjohnson yeah, we talked about this pattern multiple times. We are using "something similar" in EmbedAPIv3 where we are also getting a HTTP header with the doctool and the version of it. However, we have only v1 response there for now and we haven't started performing the check yet. This time, I want to start from start with the versioning so it's pretty clear what structure belongs to what version from the beginning. We can always change the implementation in the future if we want to, but making usage of the HTTP header from the start is great. |
Yeah agreed that versioning is a great place to start from. On the implementation side, I could see how maybe a class based approach could eventually work better, especially if we anticipate changing parts of the response slightly. For example if we wanted to alter projects response slightly in v1, I could see this pattern helping: class AddonsV0(AddonsBase):
def get_projects(self):
# Return v0 projects structure
class AddonsV1(AddonsBase):
def get_projects(self):
# Return v1 projects structure This helps avoid rewriting everything else in that data structure. Just my immediate thought though, it certainly can shift over time and doesn't need to start this mutable of a structure. |
That pattern looks good. I understand the idea would be to have "one method per main key" in the JSON, right? |
I just wanted to note that this will add a non-trivial overhead from the start, if we want to go in that direction. Maybe this can be a later refactor? |
Something like that, probably similar to Django model views or similar.
Yup, absolutely, it's not a now thing. Just raised here in case we immediately anticipate problems maintaining one method per response structure. It's not even a pattern to work about until after we have users relying on the API response. |
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.
With the client -> addons conversation, there are some naming changes here, but otherwise this is looking great.
Most of my notes are just future ideas, the only immediate change I see is that we likely want the version name in the flyout versions structure. All the other pieces are later pieces, after we have some features public and usable.
"default_filter": "subprojects:project/latest", | ||
"filters": [ | ||
["Search only in this project", "project:project/latest"], | ||
["Search subprojects", "subprojects:project/latest"] |
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 like this pattern. It also helps push the translations into the application which I like. Translations will be A Thing though, so we can punt on that conversation either way.
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 just a hard coded/default example.
In theory, what I understood from the work that @stsewd did, is that the author of the docs is whom will decide the names and the values of the filters. So, we need to keep talking how that data will be passed (see readthedocs/addons#13)
"state": { | ||
"code": "finished", | ||
"name": "Finished" | ||
}, |
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 probably commentary on the build serializer and not this PR, but I found myself wanting some helpers here around the build state data. The data is all here, but I had to replicate a good deal of backend logic to determine things like is_finished
, is_cancelled
, is_active
, etc.
Consider this a side request I supposed 🙂
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.
For example, I had to sort through a lot of build logic with the API response this the build detail view, for example: https://github.com/readthedocs/ext-theme/blob/main/src/js/build/detail.js#L236-L283
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 data is all here, but I had to replicate a good deal of backend logic to determine things like is_finished, is_cancelled, is_active, etc.
This is a pretty common discussion between backend and frontend teams 😄 . IMHO, the data is there and it's the FE who should create "the helpers" required to present the data as they need --but I guess this is kind of a philosophical discussion. We can continue talking about this outside this PR.
In any case, if we decide changing anything here, it should be done in the BuildSerializer
.
"id": 1, | ||
"language": { | ||
"code": "en", | ||
"name": "English" |
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.
project.language.name
here is an example of where we could support a translated API response, probably based on accept headers. If the addition of a language aware API endpoint changes the implementation here drastically, maybe this is a now conversation. But I'm assuming it's not and we can come back to this as we give users more access into this response structure.
# NOTE: the following serializers are required only to remove some fields we | ||
# can't expose yet in this API endpoint because it running under El Proxito | ||
# which cannot resolve some dashboard URLs because they are not defined on El | ||
# Proxito. |
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'm guessing this is also probably helpful to not point users of this response structure towards resources on APIv3, where they will likely need to use an authenticated API client.
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.
You have a good point here, yes.
However, there are other URLs that El Proxito is not able to resolve currently;
- project's dashboard page
- dashboard build's detail page
- downloads
- etc
So, we still need to find a way to resolve those and/or hard code them if we think that's fine for now.
@@ -64,6 +179,7 @@ def get(self, request): | |||
}, | |||
"features": { | |||
"analytics": { | |||
# TODO: consider adding this field into the ProjectSerializer itself. |
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 haven't touched the structure of I didn't change those "just yet" because it will also require a change in the client to keep working. We can coordinate both changes in a following up PR if it's not urgent. |
That makes sense. Maybe a todo on that response code would be a good addition then. We can return when we want to break our own usage of the API 😄 |
I'm thinking a little more ahead in time here and just thought that we could have better top keys as well. Mainly for {
"projects": {
"current": { ... },
"translations": [{ ... }], // list of all translations for this project
...
},
"versions": {
"current": { ... },
"active": [{ ... }], // list of all active versions for this project
...
},
"builds": {
"current": { ... },
...
}
} This will allow us to expand these objects in the future by returning more categorized results. "Active versions" and "Translations" are the most clear here, since they will be used for the flyout and with the current structure from this PR I feel we will end up having Also, since this is "common data" that's not specifically tied to a particular "addon" itself --because it's re-usable data, I think it makes more to be at a top level key than under |
I think it's a solid improvement. I'd agree that the version instances don't need to be nested in the addon configuration section, which feels more like it should be user data and defaults. I could see us referencing versions from an |
I'm not sure to follow this part. Can you expand why we would need these version |
I could see this being useful if we want to include a complete list of versions in the top level of the data response, and only want to reference a few versions for the flyout. We might want to return all active versions at the top level listing (including hidden versions and/or pull request versions), but the version selector only shows active, non-pull request versions. |
In that case, we should have In fact, this was my whole point when suggesting this new structure with another level: "We can add more filtered/categorized data for the top keys in the future without breaking the structure" |
Thinking about this pattern more, it actually seems fairly complex, most especially from the user/author perspective though. I'd probably lean back towards a single list of versions instead multiple lists of versions. One problem I see with this approach is that we/users/authors will have to iterate over multiple lists of versions to look up a single project/version/etc. This will also be confusing if projects/versions can exist in more than one listing.
Perhaps to step back, what use case do you see here? Perhaps it's not clear why you think this could be helpful to us/users/authors.
I mentioned an explicit list of version ids that we expect in the flyout because without that list, each user/theme author/etc needs to have a fair amount of understanding about our models to accurately show a list of projects even. That is, the version must be active, not a PR version, not hidden, not private, and the reader needs to have access to the version. Unless we want dozens of non-exclusive lists of versions, a lookup field like |
I think we both want something similar but we are getting confused because of the naming used to explain this. It seems that The main point here is to have a extendable structure in the future when we need to add more values. Have the ability to put "all Project objects" under a top key I'll move forward with the structure |
BTW, we cannot return "all the versions" because that will destroy our database. So, there is going to be always an implicit filter. I tried to make those filter explicit by adding a the name of the filter in the sub-key, but it seems that they weren't 100% accurated. |
New `analytics` field that exposes: - the Google code if the project has it configured - whether or not the global Google/Plausible Read the Docs analytics is enabled This field is going to be useful for the new "Analytics Addon". Related #10216
Agreed, I like this part of the pattern you outlined.
I also like this part of the pattern 👍 It was just sublists like
I think I understand this and probably agree here. But to clarify, I wouldn't return inactive versions or versions we don't expect to use in display of the version selector/etc. This is where users/authors should have to use our API directly.
Also agreed here, we can come back to this decision/discussion later. My issue was probably mostly with building out the API too much to start, we only really need a single list of projects to continue immediately. |
Yes. I gave However, that
and I think there was where the confusion came. It's not "just In any case, we can move forward with the latest changes. However, I didn't get an approve 🤷🏼 |
Fix a test failure from #10216
Review initial JSON response to keep consistency with APIv3 resources. It uses a small modified version of the APIv3 serializers for known resources, removing some URL fields that can't be resolved from El Proxito.
Sharing the same serializers that our APIv3 make our response a lot more consitent between the endpoints and allow us to expand the behavior by adding more fields in a structured way.
Besides, these changes include a minimal checking of the version coming from
X-RTD-Hosting-Integrations-Version
header to decide which JSON structure return, allowing us to support multiple version at the same time in case we require to do some breaking changes.The JavaScript client is already sending this header in all of its requests. See readthedocs/addons#28
Closes #10211