Skip to content

Commit 8d8864b

Browse files
authored
Merge pull request #210 from kbase/URO-208
URO-208: orcidlink main view
2 parents dd08bd2 + 04defd8 commit 8d8864b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1978
-236
lines changed

package-lock.json

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Loading

src/common/api/orcidLinkCommon.ts

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
export interface ORCIDAuthPublic {
2+
expires_in: number;
3+
name: string;
4+
orcid: string;
5+
scope: string;
6+
}
7+
8+
export interface LinkRecordPublic {
9+
created_at: number;
10+
expires_at: number;
11+
retires_at: number;
12+
username: string;
13+
orcid_auth: ORCIDAuthPublic;
14+
}
15+
16+
export interface ORCIDAuthPublicNonOwner {
17+
orcid: string;
18+
name: string;
19+
}
20+
21+
export interface LinkRecordPublicNonOwner {
22+
username: string;
23+
orcid_auth: ORCIDAuthPublicNonOwner;
24+
}
25+
26+
// ORCID User Profile (our version)
27+
28+
export interface Affiliation {
29+
name: string;
30+
role: string;
31+
startYear: string;
32+
endYear: string | null;
33+
}
34+
35+
export interface ORCIDFieldGroupBase {
36+
private: boolean;
37+
}
38+
39+
export interface ORCIDFieldGroupPrivate extends ORCIDFieldGroupBase {
40+
private: true;
41+
fields: null;
42+
}
43+
44+
export interface ORCIDFieldGroupAccessible<T> extends ORCIDFieldGroupBase {
45+
private: false;
46+
fields: T;
47+
}
48+
49+
export type ORCIDFieldGroup<T> =
50+
| ORCIDFieldGroupPrivate
51+
| ORCIDFieldGroupAccessible<T>;
52+
53+
export interface ORCIDNameFieldGroup {
54+
firstName: string;
55+
lastName: string | null;
56+
creditName: string | null;
57+
}
58+
59+
export interface ORCIDBiographyFieldGroup {
60+
bio: string;
61+
}
62+
63+
export interface ORCIDEmailFieldGroup {
64+
emailAddresses: Array<string>;
65+
}
66+
67+
export interface ORCIDProfile {
68+
orcidId: string;
69+
nameGroup: ORCIDFieldGroup<ORCIDNameFieldGroup>;
70+
biographyGroup: ORCIDFieldGroup<ORCIDBiographyFieldGroup>;
71+
emailGroup: ORCIDFieldGroup<ORCIDEmailFieldGroup>;
72+
employment: Array<Affiliation>;
73+
}

src/common/api/orcidlinkAPI.ts

+102-46
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,64 @@
11
import { baseApi } from '.';
2+
import { LinkRecordPublic, ORCIDProfile } from './orcidLinkCommon';
23
import { jsonRpcService } from './utils/serviceHelpers';
34

4-
// orcidlink system types
5+
// system info
56

6-
export interface ORCIDAuthPublic {
7-
expires_in: number;
7+
export interface ServiceDescription {
88
name: string;
9-
orcid: string;
10-
scope: string;
9+
title: string;
10+
version: string;
11+
language: string;
12+
description: string;
13+
repoURL: string;
1114
}
1215

13-
export interface LinkRecordPublic {
14-
created_at: number;
15-
expires_at: number;
16-
retires_at: number;
17-
username: string;
18-
orcid_auth: ORCIDAuthPublic;
16+
export interface GitInfo {
17+
commit_hash: string;
18+
commit_hash_abbreviated: string;
19+
author_name: string;
20+
committer_name: string;
21+
committer_date: number;
22+
url: string;
23+
branch: string;
24+
tag: string | null;
1925
}
2026

21-
// Method types
22-
23-
export interface StatusResult {
24-
status: string;
27+
export interface RuntimeInfo {
2528
current_time: number;
26-
start_time: number;
29+
orcid_api_url: string;
30+
orcid_oauth_url: string;
31+
orcid_site_url: string;
2732
}
2833

34+
// TODO: normalize to either kebab or underscore. Pref underscore.
2935
export interface InfoResult {
30-
'service-description': {
31-
name: string;
32-
title: string;
33-
version: string;
34-
};
36+
'service-description': ServiceDescription;
37+
'git-info': GitInfo;
38+
runtime_info: RuntimeInfo;
3539
}
3640

37-
// is-linked
41+
// combined api calls for initial view
42+
43+
export interface ORCIDLinkInitialStateResult {
44+
isLinked: boolean;
45+
info: InfoResult;
46+
}
3847

39-
export interface IsLinkedParams {
48+
export interface ORCIDLinkInitialStateParams {
4049
username: string;
4150
}
4251

43-
export type IsLinkedResult = boolean;
52+
// combined api call for linked user info
4453

45-
// owner-link
46-
export interface OwnerLinkParams {
47-
username: string;
54+
export interface ORCIDLinkLinkedUserInfoResult {
55+
linkRecord: LinkRecordPublic;
56+
profile: ORCIDProfile;
4857
}
4958

50-
export type OwnerLinkResult = LinkRecordPublic;
59+
export interface ORCIDLinkLinkedUserInfoParams {
60+
username: string;
61+
}
5162

5263
// It is mostly a JSONRPC 2.0 service, although the oauth flow is rest-ish.
5364
const orcidlinkService = jsonRpcService({
@@ -62,31 +73,76 @@ export const orcidlinkAPI = baseApi
6273
.enhanceEndpoints({ addTagTypes: ['ORCIDLink'] })
6374
.injectEndpoints({
6475
endpoints: ({ query }) => ({
65-
orcidlinkStatus: query<StatusResult, {}>({
66-
query: () => {
67-
return orcidlinkService({
68-
method: 'status',
69-
});
70-
},
71-
}),
72-
orcidlinkIsLinked: query<IsLinkedResult, IsLinkedParams>({
73-
query: ({ username }) => {
74-
return orcidlinkService({
75-
method: 'is-linked',
76-
params: {
77-
username,
76+
orcidlinkInitialState: query<
77+
ORCIDLinkInitialStateResult,
78+
ORCIDLinkInitialStateParams
79+
>({
80+
async queryFn({ username }, _queryApi, _extraOptions, fetchWithBQ) {
81+
const [isLinked, info] = await Promise.all([
82+
fetchWithBQ(
83+
orcidlinkService({
84+
method: 'is-linked',
85+
params: {
86+
username,
87+
},
88+
})
89+
),
90+
fetchWithBQ(
91+
orcidlinkService({
92+
method: 'info',
93+
})
94+
),
95+
]);
96+
if (isLinked.error) {
97+
return { error: isLinked.error };
98+
}
99+
if (info.error) {
100+
return { error: info.error };
101+
}
102+
return {
103+
data: {
104+
isLinked: isLinked.data as boolean,
105+
info: info.data as InfoResult,
78106
},
79-
});
107+
};
80108
},
81109
}),
82-
orcidlinkOwnerLink: query<OwnerLinkResult, OwnerLinkParams>({
83-
query: ({ username }) => {
84-
return orcidlinkService({
85-
method: 'owner-link',
110+
orcidlinkLinkedUserInfo: query<
111+
ORCIDLinkLinkedUserInfoResult,
112+
ORCIDLinkLinkedUserInfoParams
113+
>({
114+
async queryFn({ username }, _queryApi, _extraOptions, fetchWithBQ) {
115+
const profileQuery = orcidlinkService({
116+
method: 'get-orcid-profile',
86117
params: {
87118
username,
88119
},
89120
});
121+
122+
const [linkRecord, profile] = await Promise.all([
123+
fetchWithBQ(
124+
orcidlinkService({
125+
method: 'owner-link',
126+
params: {
127+
username,
128+
},
129+
})
130+
),
131+
fetchWithBQ(profileQuery),
132+
]);
133+
if (linkRecord.error) {
134+
return { error: linkRecord.error };
135+
}
136+
137+
if (profile.error) {
138+
return { error: profile.error };
139+
}
140+
return {
141+
data: {
142+
linkRecord: linkRecord.data as LinkRecordPublic,
143+
profile: profile.data as ORCIDProfile,
144+
},
145+
};
90146
},
91147
}),
92148
}),

src/common/api/utils/common.ts

+21
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,27 @@ export const isJsonRpcError = (obj: unknown): obj is JsonRpcError => {
4949
return false;
5050
};
5151

52+
export const isJsonRpc20Error = (obj: unknown): obj is JsonRpcError => {
53+
if (
54+
typeof obj === 'object' &&
55+
obj !== null &&
56+
['jsonrpc', 'error', 'id'].every((k) => k in obj)
57+
) {
58+
const { jsonrpc, error } = obj as { jsonrpc: string; error: unknown };
59+
if (jsonrpc !== '2.0') {
60+
return false;
61+
}
62+
if (
63+
typeof error === 'object' &&
64+
error !== null &&
65+
['code', 'message'].every((k) => k in error)
66+
) {
67+
return true;
68+
}
69+
}
70+
return false;
71+
};
72+
5273
/**
5374
* Type predicate to narrow an unknown error to `FetchBaseQueryError`
5475
*/

src/common/api/utils/kbaseBaseQuery.ts

+29-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import {
77
} from '@reduxjs/toolkit/query/react';
88
import { RootState } from '../../../app/store';
99
import { serviceWizardApi } from '../serviceWizardApi';
10-
import { isJsonRpcError, KBaseBaseQueryError } from './common';
10+
import {
11+
isJsonRpc20Error,
12+
isJsonRpcError,
13+
KBaseBaseQueryError,
14+
} from './common';
1115

1216
export interface DynamicService {
1317
name: string;
@@ -41,6 +45,12 @@ export interface JSONRPC20Body {
4145
params?: unknown;
4246
}
4347

48+
export interface JSONRPC20Error {
49+
code: number;
50+
message: string;
51+
data: unknown;
52+
}
53+
4454
export type JSONRPCBody = JSONRPC11Body | JSONRPC20Body;
4555

4656
export interface HttpQueryArgs extends FetchArgs {
@@ -226,6 +236,7 @@ export const kbaseBaseQuery: (
226236
const response = await request;
227237

228238
// identify and better differentiate jsonRpc errors
239+
// This is for KBase's version of JSON-RPC 1.1
229240
if (response.error && response.error.status === 500) {
230241
if (isJsonRpcError(response.error.data)) {
231242
if (response.error.data.id && response.error.data.id !== reqId) {
@@ -245,6 +256,23 @@ export const kbaseBaseQuery: (
245256
};
246257
}
247258
}
259+
if (isJsonRpc20Error(response.data)) {
260+
if (response.data.id && response.data.id !== reqId) {
261+
return {
262+
error: {
263+
status: 'CUSTOM_ERROR',
264+
error: 'JsonRpcProtocolError',
265+
data: `Response ID "${response.data.id}" !== Request ID "${reqId}"`,
266+
},
267+
};
268+
}
269+
return {
270+
error: {
271+
status: 'JSONRPC_ERROR',
272+
data: response.data,
273+
},
274+
};
275+
}
248276

249277
// If another error has occurred preventing a response, return default rtk-query response.
250278
// This appropriately handles rtk-query internal errors

src/common/api/utils/serviceHelpers.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { JsonRpcQueryArgs, HttpQueryArgs } from './kbaseBaseQuery';
1+
import { HttpQueryArgs, JsonRpcQueryArgs } from './kbaseBaseQuery';
22

33
// Helpers for adding service info to each query,
44
export const jsonRpcService = (

0 commit comments

Comments
 (0)