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-163, URO-165, URO-166, URO-167: API calls for Copy, Link, Rename and Restore components #107

Merged
merged 6 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/common/api/narrativeService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* narrativeService */
import { dynamicService } from './serviceWizardApi';
import { baseApi } from './index';

const narrativeService = dynamicService({
name: 'NarrativeService',
release: 'release',
});

export interface NarrativeServiceParams {
copyNarrative: { nameNew: string; workspaceRef: string; workspaceId: number };
getStatus: void;
renameNarrative: { nameNew: string; narrativeRef: string };
restoreNarrative: { objId: number; version: number; wsId: number };
}

interface NarrativeServiceResults {
copyNarrative: unknown;
getStatus: { state: string }[];
renameNarrative: unknown;
restoreNarrative: unknown;
}

export const narrativeServiceApi = baseApi.injectEndpoints({
endpoints: (builder) => ({
copyNarrative: builder.mutation<
NarrativeServiceResults['copyNarrative'],
NarrativeServiceParams['copyNarrative']
>({
query: ({ nameNew, workspaceRef, workspaceId }) =>
narrativeService({
method: 'NarrativeService.copy_narrative',
params: [{ newName: nameNew, workspaceRef, workspaceId }],
}),
}),
getStatus: builder.query<
NarrativeServiceResults['getStatus'],
NarrativeServiceParams['getStatus']
>({
query: () =>
narrativeService({
method: 'NarrativeService.status',
params: [],
}),
}),
renameNarrative: builder.mutation<
NarrativeServiceResults['renameNarrative'],
NarrativeServiceParams['renameNarrative']
>({
query: ({ narrativeRef, nameNew }) =>
narrativeService({
method: 'NarrativeService.rename_narrative',
params: [{ narrative_ref: narrativeRef, new_name: nameNew }],
}),
}),
restoreNarrative: builder.mutation<
NarrativeServiceResults['restoreNarrative'],
NarrativeServiceParams['restoreNarrative']
>({
query: ({ objId, version, wsId }) =>
narrativeService({
method: 'NarrativeService.revert_narrative_object',
params: [{ ver: version, objid: objId, wsid: wsId }],
}),
}),
}),
});

export const { copyNarrative, getStatus, renameNarrative, restoreNarrative } =
narrativeServiceApi.endpoints;
15 changes: 14 additions & 1 deletion src/common/api/orgsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ export interface OrgMemberInfo {
export interface OrgsParams {
getNarrativeOrgs: number;
getUserOrgs: void;
linkNarrative: { orgId: string; wsId: number };
}

export interface OrgsResults {
getNarrativeOrgs: OrgInfo[];
getUserOrgs: OrgMemberInfo[];
linkNarrative: unknown;
dakotablair marked this conversation as resolved.
Show resolved Hide resolved
}

export const orgsApi = baseApi
Expand Down Expand Up @@ -56,8 +58,19 @@ export const orgsApi = baseApi
}),
providesTags: ['Orgs'],
}),
linkNarrative: builder.mutation<
OrgsResults['linkNarrative'],
OrgsParams['linkNarrative']
>({
query: ({ orgId, wsId }) =>
orgsService({
method: 'POST',
url: `group/${orgId}/resource/workspace/${wsId}`,
}),
}),
}),
});

export const { getNarrativeOrgs, getUserOrgs } = orgsApi.endpoints;
export const { getNarrativeOrgs, getUserOrgs, linkNarrative } =
orgsApi.endpoints;
export const clearCacheAction = orgsApi.util.invalidateTags(['Orgs']);
42 changes: 24 additions & 18 deletions src/common/api/serviceWizardApi.ts
Original file line number Diff line number Diff line change
@@ -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. */
dakotablair marked this conversation as resolved.
Show resolved Hide resolved
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({
Expand All @@ -42,4 +48,4 @@ export const serviceWizardApi = baseApi.injectEndpoints({

setConsumedService('serviceWizardApi', serviceWizardApi);

export const { serviceStatus } = serviceWizardApi.endpoints;
export const { getServiceStatus } = serviceWizardApi.endpoints;
67 changes: 67 additions & 0 deletions src/common/api/utils/common.ts
Original file line number Diff line number Diff line change
@@ -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;
};
70 changes: 2 additions & 68 deletions src/common/api/utils/kbaseBaseQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 = <T extends keyof typeof consumedServices>(
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/common/api/utils/parseError.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/common/api/utils/parseError.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/common/api/workspaceApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type TimeParams = (
);

interface wsParams {
deleteWorkspace: { reqId?: number | string; wsId: number };
deleteWorkspace: { wsId: number };
getwsNarrative: { upa: string };
getwsObjectByName: { upa: string };
getwsPermissions: { wsId: number };
Expand Down Expand Up @@ -103,7 +103,7 @@ const wsApi = baseApi.injectEndpoints({
wsResults['deleteWorkspace'],
wsParams['deleteWorkspace']
>({
query: ({ wsId, reqId }) =>
query: ({ wsId }) =>
ws({
method: 'Workspace.delete_workspace',
params: [{ id: wsId }],
Expand Down
10 changes: 6 additions & 4 deletions src/common/components/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import { v4 as uuidv4 } from 'uuid';
import classes from './Input.module.scss';

interface InputInterface extends ComponentProps<'input'> {
label?: ReactElement;
errors?: boolean;
label?: ReactElement;
maxLength?: number;
validated?: boolean;
}

export const Input = forwardRef<HTMLInputElement, InputInterface>(
(props, ref) => {
const { className, errors, validated, label, ...rest } = props;
const { className, errors, label, maxLength, validated, ...rest } = props;
const { name } = rest; // react-hook-form internals
const idForLabel = useMemo(() => `input-${uuidv4()}`, []);
const statusClass = errors ? classes.error : classes.success;
Expand Down Expand Up @@ -60,9 +61,10 @@ export const Input = forwardRef<HTMLInputElement, InputInterface>(
{...rest}
className={classes.input}
id={idForLabel}
type={'text'}
onFocus={handleFocus}
maxLength={maxLength}
onBlur={handleBlur}
onFocus={handleFocus}
type={'text'}
dakotablair marked this conversation as resolved.
Show resolved Hide resolved
/>
</span>
);
Expand Down
Loading
Loading