diff --git a/src/common/api/narrativeService.ts b/src/common/api/narrativeService.ts new file mode 100644 index 00000000..f341367b --- /dev/null +++ b/src/common/api/narrativeService.ts @@ -0,0 +1,33 @@ +/* narrativeService */ +import { dynamicService } from './serviceWizardApi'; +import { baseApi } from './index'; + +const narrativeService = dynamicService({ + name: 'NarrativeService', + release: 'release', +}); + +export interface NarrativeServiceParams { + getStatus: void; +} + +interface NarrativeServiceResults { + getStatus: unknown; +} + +export const narrativeServiceApi = baseApi.injectEndpoints({ + endpoints: (builder) => ({ + getStatus: builder.query< + NarrativeServiceResults['getStatus'], + NarrativeServiceParams['getStatus'] + >({ + query: () => + narrativeService({ + method: 'NarrativeService.status', + params: [], + }), + }), + }), +}); + +export const { getStatus } = narrativeServiceApi.endpoints; diff --git a/src/common/api/serviceWizardApi.ts b/src/common/api/serviceWizardApi.ts index 5110059a..35c276ed 100644 --- a/src/common/api/serviceWizardApi.ts +++ b/src/common/api/serviceWizardApi.ts @@ -1,34 +1,40 @@ +/* serviceWizardApi */ import { baseApi } from './index'; import { setConsumedService } from './utils/kbaseBaseQuery'; import { jsonRpcService } from './utils/serviceHelpers'; const serviceWizard = jsonRpcService({ url: 'services/service_wizard' }); +const dynamicService = jsonRpcService; + +/* Use this for dynamic services to ensure serviceWizardApi is set. */ +export { dynamicService }; + +interface ServiceStatus { + git_commit_hash: string; + status: string; + version: string; + hash: string; + release_tags: string[]; + url: string; + module_name: string; + health: string; + up: number; +} + interface ServiceWizardParams { - serviceStatus: { module_name: string; version: string }; + getServiceStatus: { module_name: string; version: string }; } interface ServiceWizardResults { - serviceStatus: [ - { - git_commit_hash: string; - status: string; - version: string; - hash: string; - release_tags: string[]; - url: string; - module_name: string; - health: string; - up: number; - } - ]; + getServiceStatus: ServiceStatus[]; } export const serviceWizardApi = baseApi.injectEndpoints({ endpoints: (builder) => ({ - serviceStatus: builder.query< - ServiceWizardResults['serviceStatus'], - ServiceWizardParams['serviceStatus'] + getServiceStatus: builder.query< + ServiceWizardResults['getServiceStatus'], + ServiceWizardParams['getServiceStatus'] >({ query: ({ module_name, version }) => serviceWizard({ @@ -42,4 +48,4 @@ export const serviceWizardApi = baseApi.injectEndpoints({ setConsumedService('serviceWizardApi', serviceWizardApi); -export const { serviceStatus } = serviceWizardApi.endpoints; +export const { getServiceStatus } = serviceWizardApi.endpoints; diff --git a/src/common/api/utils/common.ts b/src/common/api/utils/common.ts new file mode 100644 index 00000000..860baef2 --- /dev/null +++ b/src/common/api/utils/common.ts @@ -0,0 +1,67 @@ +/* api/utils/common */ +import { FetchBaseQueryError } from '@reduxjs/toolkit/query/react'; +/* +JSONRPC Specification details +JSON-RPC 1.0 - https://www.jsonrpc.org/specification_v1 +JSON-RPC 1.1 wd - https://jsonrpc.org/historical/json-rpc-1-1-wd.html +JSON-RPC 2.0 - https://www.jsonrpc.org/specification +- id + - 2.0 allows id to be string, number (with no fractional part) or null + - 1.1 allows id to be "any JSON type" +- version + - a string in both JSONRPC 1.1 and 2.0. +*/ +// KBase mostly uses strings, or string serializable values, so we can too. +type JsonRpcError = { + version: '1.1'; + id: string; + error: { + name: string; + code: number; + message: string; + }; +}; + +export type KBaseBaseQueryError = + | FetchBaseQueryError + | { + status: 'JSONRPC_ERROR'; + data: JsonRpcError; + }; + +export const isJsonRpcError = (obj: unknown): obj is JsonRpcError => { + if ( + typeof obj === 'object' && + obj !== null && + ['version', 'error', 'id'].every((k) => k in obj) + ) { + const { version, error } = obj as { version: string; error: unknown }; + const versionsSupported = new Set(['1.1', '2.0']); + if (!versionsSupported.has(version)) return false; + if ( + typeof error === 'object' && + error !== null && + ['name', 'code', 'message'].every((k) => k in error) + ) { + return true; + } + } + return false; +}; + +/** + * Type predicate to narrow an unknown error to `FetchBaseQueryError` + */ +export function isFetchBaseQueryError( + error: unknown +): error is FetchBaseQueryError { + return typeof error === 'object' && error !== null && 'status' in error; +} + +export const isKBaseBaseQueryError = ( + error: unknown +): error is KBaseBaseQueryError => { + const fbq = isFetchBaseQueryError(error); + const condition = fbq && isJsonRpcError(error.data); + return condition; +}; diff --git a/src/common/api/utils/kbaseBaseQuery.ts b/src/common/api/utils/kbaseBaseQuery.ts index c1f8b30d..206ca8d8 100644 --- a/src/common/api/utils/kbaseBaseQuery.ts +++ b/src/common/api/utils/kbaseBaseQuery.ts @@ -4,10 +4,10 @@ import { BaseQueryFn, FetchArgs, fetchBaseQuery, - FetchBaseQueryError, } from '@reduxjs/toolkit/query/react'; import { RootState } from '../../../app/store'; import { serviceWizardApi } from '../serviceWizardApi'; +import { KBaseBaseQueryError, isJsonRpcError } from './common'; export interface DynamicService { name: string; @@ -39,72 +39,6 @@ export const isDynamic = ( return (service as StaticService).url === undefined; }; -/* -JSONRPC Specification details -JSON-RPC 1.0 - https://www.jsonrpc.org/specification_v1 -JSON-RPC 1.1 wd - https://jsonrpc.org/historical/json-rpc-1-1-wd.html -JSON-RPC 2.0 - https://www.jsonrpc.org/specification -- id - - 2.0 allows id to be string, number (with no fractional part) or null - - 1.1 allows id to be "any JSON type" -- version - - a string in both JSONRPC 1.1 and 2.0. -*/ -// KBase mostly uses strings, or string serializable values, so we can too. -type JsonRpcError = { - version: '1.1'; - id: string; - error: { - name: string; - code: number; - message: string; - }; -}; - -export const isJsonRpcError = (obj: unknown): obj is JsonRpcError => { - if ( - typeof obj === 'object' && - obj !== null && - ['version', 'error', 'id'].every((k) => k in obj) - ) { - const { version, error } = obj as { version: string; error: unknown }; - const versionsSupported = new Set(['1.1', '2.0']); - if (!versionsSupported.has(version)) return false; - if ( - typeof error === 'object' && - error !== null && - ['name', 'code', 'message'].every((k) => k in error) - ) { - return true; - } - } - return false; -}; - -export type KBaseBaseQueryError = - | FetchBaseQueryError - | { - status: 'JSONRPC_ERROR'; - data: JsonRpcError; - }; - -/** - * Type predicate to narrow an unknown error to `FetchBaseQueryError` - */ -export function isFetchBaseQueryError( - error: unknown -): error is FetchBaseQueryError { - return typeof error === 'object' && error !== null && 'status' in error; -} - -export const isKBaseBaseQueryError = ( - error: unknown -): error is KBaseBaseQueryError => { - const fbq = isFetchBaseQueryError(error); - const condition = fbq && isJsonRpcError(error.data); - return condition; -}; - // These helpers let us avoid circular dependencies when using an API endpoint within kbaseBaseQuery const consumedServices: { serviceWizardApi?: typeof serviceWizardApi } = {}; export const setConsumedService = ( @@ -132,7 +66,7 @@ const getServiceUrl = async ( // get serviceWizardApi while avoiding circular imports // (as serviceWizardApi imports this file) const serviceStatusQuery = - getConsumedService('serviceWizardApi').endpoints.serviceStatus; + getConsumedService('serviceWizardApi').endpoints.getServiceStatus; const wizardQueryArgs = { module_name: name, diff --git a/src/common/api/utils/parseError.test.ts b/src/common/api/utils/parseError.test.ts index fe2cf83c..cb7a7687 100644 --- a/src/common/api/utils/parseError.test.ts +++ b/src/common/api/utils/parseError.test.ts @@ -1,6 +1,6 @@ import { SerializedError } from '@reduxjs/toolkit'; import { FetchBaseQueryError } from '@reduxjs/toolkit/dist/query'; -import { KBaseBaseQueryError } from './kbaseBaseQuery'; +import { KBaseBaseQueryError } from './common'; import { parseError } from './parseError'; describe('parseError', () => { diff --git a/src/common/api/utils/parseError.ts b/src/common/api/utils/parseError.ts index f88e9e18..ad7a994b 100644 --- a/src/common/api/utils/parseError.ts +++ b/src/common/api/utils/parseError.ts @@ -1,5 +1,5 @@ import { SerializedError } from '@reduxjs/toolkit'; -import { KBaseBaseQueryError } from './kbaseBaseQuery'; +import { KBaseBaseQueryError } from './common'; export function parseError(error: KBaseBaseQueryError | SerializedError): { error: KBaseBaseQueryError | SerializedError; diff --git a/src/features/navigator/NarrativeControl/Copy.tsx b/src/features/navigator/NarrativeControl/Copy.tsx index 22bb4456..b80d0306 100644 --- a/src/features/navigator/NarrativeControl/Copy.tsx +++ b/src/features/navigator/NarrativeControl/Copy.tsx @@ -9,6 +9,7 @@ import { import { Input } from '../../../common/components/Input'; import { useAppDispatch } from '../../../common/hooks'; import { TODOAddLoadingState } from '../common'; +import { useNarrativeServiceStatus } from '../hooks'; import { copyNarrative } from '../navigatorSlice'; import { ControlProps } from './common'; @@ -22,6 +23,7 @@ export interface CopyProps extends ControlProps { export const Copy: FC = ({ narrativeDoc, modalClose, version }) => { const dispatch = useAppDispatch(); + useNarrativeServiceStatus(); const { formState, getValues, register } = useForm({ defaultValues: { narrativeCopyName: `${narrativeDoc.narrative_title} - Copy`, diff --git a/src/features/navigator/NarrativeControl/Delete.tsx b/src/features/navigator/NarrativeControl/Delete.tsx index e7330958..9689901a 100644 --- a/src/features/navigator/NarrativeControl/Delete.tsx +++ b/src/features/navigator/NarrativeControl/Delete.tsx @@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom'; import toast from 'react-hot-toast'; import { Button } from '../../../common/components'; import { useAppDispatch, useAppSelector } from '../../../common/hooks'; -import { isKBaseBaseQueryError } from '../../../common/api/utils/kbaseBaseQuery'; +import { isKBaseBaseQueryError } from '../../../common/api/utils/common'; import { parseError } from '../../../common/api/utils/parseError'; import { deleteWorkspace } from '../../../common/api/workspaceApi'; import { diff --git a/src/features/navigator/hooks.ts b/src/features/navigator/hooks.ts index 18cae76c..959f445a 100644 --- a/src/features/navigator/hooks.ts +++ b/src/features/navigator/hooks.ts @@ -2,6 +2,7 @@ import { useEffect, useMemo } from 'react'; import { useAppDispatch, useAppSelector } from '../../common/hooks'; import { getUsers } from '../../common/api/authService'; import { getNarratives, SearchParams } from '../../common/api/searchApi'; +import { getStatus } from '../../common/api/narrativeService'; import { getwsNarrative } from '../../common/api/workspaceApi'; import { Cell } from '../../common/types/NarrativeDoc'; import { authToken } from '../auth/authSlice'; @@ -140,6 +141,16 @@ export const useNarratives = (params: getNarrativesParams) => { }, [dispatch, narrativesPrevious, searchAPIQuery, searchAPIParams, syncd]); }; +export const useNarrativeServiceStatus = () => { + const nsQuery = getStatus.useQuery(); + useEffect(() => { + if (nsQuery.isSuccess && nsQuery.data) { + const data = nsQuery.data; + console.log({ data }); // eslint-disable-line no-console + } + }, [nsQuery.data, nsQuery.isSuccess]); +}; + export const useUsers = (params: { users: string[] }) => { const dispatch = useAppDispatch(); const token = useAppSelector(authToken);