Skip to content
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

URO-206 getting started with orcidlink integration #206

Merged
merged 8 commits into from
May 2, 2024
Merged

Conversation

eapearson
Copy link
Contributor

This set of changes introduces a primitive orcidlink feature. A link is displayed on the left nav area, a route is in place, the feature is invoked, and a message is displayed indicating whether the user has an orcid link or not. If so, some orcid link information is shown.

What is not included? Tests, actual (other than stub) redux store data definitions (state types), api calls in the the redux layer (they are in the components themselves), proper error handling.

Some bespoke layout (loading indicator, box around the content) was added just to keep have an implementation - I just couldn’t find anything satisfying to grab quickly.

Some light modifications were made to the jsonrpc api machinery. The orcidlink service is JSON-RPC 2.0, and the existing JSON-RPC support is not fully 2.0 compatible. I also made a more extensive revision of JSON-RPC, because error response support is also needs some improvement, but is not addressed in this ticket.

tweaks to jsonrpc to better support 2.0, detects whether linked, shows 3 fields from link if so, redux state not developed
@dauglyon dauglyon self-requested a review April 30, 2024 04:15
Copy link
Collaborator

@dauglyon dauglyon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! The only things blocking approval are overrideExisting & hiding the icon from src/features/layout/LeftNavBar.tsx. (We're merging into main, so everything should be presentable if it's directly accessible, just in case we cut a release in the meantime. We have some nascent feature flag support, too, which could hide the page altogether if that's preferable.) I would also recommend stubbing out a test file with an "OrcidLink Renders" test before merging, but it'd just be a formality.

I figure if the parseError comment works out, it can be in the next PR.

Also, Re: queries in components, that's the way to do it with rtk-query. It handles caching for queries with the same query key. Sometimes this requires memoization to leverage but often not. Custom thunks are a good solution if there are dependant queries, however.

fetchArgs?: FetchArgs;
}

export interface JSONRPC11Body {
version: string;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: You can hardcode the version string here, or use a template string type if we want to support minor versions

It will make discrimination (type guarding) between this type and the 2.0 type easier downstream

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms of versions, as far as I know there are only two relevant JSON-RPC versions for us: 1.1 and 2.0. Also, 1.1 is not technically official (although it is used in many places in KBase). That being said, official documents describing their operation do exist, and I suggest that we hew as close to them as possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, agreed @dakotablair, although our JSON-RPC 1.1 is not the same as the working draft that inspired it. So our reference for that should be our implementation.

@dakotablair Yes, the version could be hardcoded for sure; I did have another set of changes that addressed this more fundamentally, but more loc.

.injectEndpoints({
// because many apis have "status", some have "info", and there may be
// other random clashes.
overrideExisting: true,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to do this, as it will squash any same-named endpoints. Just use unique names for your endpoints.

Copy link
Collaborator

@dauglyon dauglyon Apr 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eg "orcidLinkStatus"

injectEndpoints does not create a new API, but rather adds enpoints to the common baseAPI instance. We set it up like that so that we can expire query tags across services. So, moral of the story, setting this to true will probably cause runtime errors if a collision occurs.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also means you can export the endpoints themselves (like we do elsewhere) without worrying about duplicates

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dauglyon thanks, you answered the question I had, though I thought my original researched showed this was safe. Thanks, Google!

// It is mostly a JSONRPC 2.0 service, although the oauth flow is rest-ish.
const orcidlinkService = jsonRpcService({
url: '/services/orcidlink/api/v1',
version: '2.0',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

@@ -37,6 +38,7 @@ const LeftNavBar: FC = () => {
<NavItem path="/legacy/search" desc="Search" icon={faSearch} />
<NavItem path="/legacy/jobbrowser" desc="Jobs" icon={faSuitcase} />
<NavItem path="/legacy/account" desc="Account" icon={faIdCard} />
<NavItem path="/orcidlink" desc="ORCID Link" icon={faLink} />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should put this behind a feature flag or hide it from the sidebar whilst orcidlink is in development.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<NavItem path="/orcidlink" desc="ORCID Link" icon={faLink} />
{/*<NavItem path="/orcidlink" desc="ORCID Link" icon={faLink} />*/}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and my thought exactly; are there feature flags used in the codebase?

@@ -172,19 +189,35 @@ export const kbaseBaseQuery: (
// Generate JsonRpc request id
const reqId = Math.random().toString();

const body = ((): JSONRPCBody => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Comment on lines +11 to +27
const message = (() => {
if ('status' in error) {
switch (error.status) {
case 'JSONRPC_ERROR':
return error.data.error.message;
case 'FETCH_ERROR':
return 'Fetch Error';
case 'CUSTOM_ERROR':
return error.error;
case 'PARSING_ERROR':
return error.error;
case 'TIMEOUT_ERROR':
return error.error;
}
} else {
return error.message || 'Unknown Error';
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check out the parseError utility for this functionality. It may need to be updated to support exactly what you want, but will be reusable

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/common/api/utils/parseError.ts

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We tend to avoid switch statements. My personal preference is that if we are going to use them then we should make use of their special features, in particular, fall-through and default cases. For example:

switch (error.status) {
    case 'JSONRPC_ERROR':
        return error.data.error.message;
    case 'FETCH_ERROR':
        return 'Fetch Error';
    case 'CUSTOM_ERROR':
    case 'PARSING_ERROR':
    case 'TIMEOUT_ERROR':
    default:
        return error.error;
}

I think many other switch users write code as you have to avoid confusion about these features, and it is this situation that has led us to avoid them on this project. There is another open PR with what I think is a good alternative.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can abide by the codebase conventions. Yeah, first pass was not to use fallthrough because I hadn't explored those error conditions to see if there was more specialized information before; e.t. timeout in theory should have information about the timeout like the timeout limit and actual elapsed time. But, like I said, I didn't have time on this batch to create a test to exercise the conditions.

} else if (isSuccess) {
return renderLink(data);
} else {
return <div>???</div>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is to appease typescript re: the function return type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is something I still wanted to follow up on, as I don't yet understand the full set of states for the various query state flags.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have been putting all the API defs in src/common/api/
That said, I can see the argument for feature-specific APIs like this and collections being in the feature folder itself. Thoughts? It's probably something to quickly talk about on the UI call

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I meant to move the api over to the centralized api directory, but forgot that piece of housekeeping.

@eapearson
Copy link
Contributor Author

Looks good! The only things blocking approval are overrideExisting & hiding the icon from src/features/layout/LeftNavBar.tsx. (We're merging into main, so everything should be presentable if it's directly accessible, just in case we cut a release in the meantime. We have some nascent feature flag support, too, which could hide the page altogether if that's preferable.) I would also recommend stubbing out a test file with an "OrcidLink Renders" test before merging, but it'd just be a formality.

I figure if the parseError comment works out, it can be in the next PR.

Also, Re: queries in components, that's the way to do it with rtk-query. It handles caching for queries with the same query key. Sometimes this requires memoization to leverage but often not. Custom thunks are a good solution if there are dependant queries, however.

Yeah, I think there is a need for sequenced queries like this that I would usually do in an async thunk if working w/ redux.

@eapearson eapearson merged commit 38d1743 into main May 2, 2024
4 checks passed
@eapearson eapearson deleted the URO-206 branch May 2, 2024 23:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants