Skip to content
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
3 changes: 1 addition & 2 deletions .storybook/api.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ class BaseMockAdapter<T extends ParamNode> implements BaseAdapter {
console.log(this.getValueFromMetadata(path));
let metadata = this.getValueFromMetadata(path)[paramName] as MetadataValue<U>;
console.log(metadata);
console.groupEnd() // VALIDATE
if (!(metadata?.writeable ?? true)) {
throw new AdapterError(`Parameter ${paramName} is read-only`, "read_only");
}
Expand Down Expand Up @@ -193,8 +194,6 @@ class BaseMockAdapter<T extends ParamNode> implements BaseAdapter {
`Type mismatch setting ${paramName}: got ${typeof val} expected ${metadata?.type ?? "dict"}`,
"type_mismatch"
)
} finally {
console.groupEnd() // VALIDATE
}

if (metadata?.allowed_values != undefined && !metadata.allowed_values.includes(val)) {
Expand Down
13 changes: 8 additions & 5 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import type { Preview } from '@storybook/react-vite';
import { themes } from 'storybook/theming';
import { useDarkMode } from 'storybook-dark-mode';
import { DocsContainer } from '@storybook/addon-docs/blocks';
import { http, passthrough } from 'msw';
import { http, HttpResponse, passthrough } from 'msw';
import { initialize, mswLoader } from 'msw-storybook-addon';
import { OdinErrorContext } from '../lib/components/OdinErrorContext';
import { apiVersionHandler, getHandler, getHandlerUpdate, putHandler, putHandlerUpdate } from './stories.mock';

import 'bootstrap/dist/css/bootstrap.min.css';
import type { ParamNode } from '../lib/components/AdapterEndpoint';
import { useEffect } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';


/*
* Initializes MSW
Expand Down Expand Up @@ -54,8 +56,9 @@ const preview: Preview = {
http.get<{ adapter: string, path: string[] }>("http://localhost:1337/api/0.1/:adapter/:path+", getHandler),
http.put<{ adapter: string }, ParamNode>("http://localhost:1337/api/0.1/:adapter", putHandler),
http.put<{ adapter: string, path: string[] }, ParamNode>("http://localhost:1337/api/0.1/:adapter/:path+", putHandler),
http.get<{ adapter: string }>("http://localhost:1337/api/:adapter", () => { return HttpResponse.error() }),

// Odin 2.0 GET/PUT requests. Note differnt path (no API version)
// Odin 2.0 GET/PUT requests. Note different path (no API version)
http.get<{ adapter: string }>("http://localhost:1338/api/:adapter", getHandlerUpdate),
http.get<{ adapter: string, path: string[] }>("http://localhost:1338/api/:adapter/:path+", getHandlerUpdate),
http.put<{ adapter: string }, ParamNode>("http://localhost:1338/api/:adapter", putHandlerUpdate),
Expand Down Expand Up @@ -111,11 +114,11 @@ const preview: Preview = {
const isDarkMode = useDarkMode();
useEffect(() => document.documentElement?.setAttribute(
"data-bs-theme", isDarkMode ? "dark" : "light"
))
))
return (
<OdinErrorContext>
<OdinErrorContext>
<Story />
</OdinErrorContext>
</OdinErrorContext>
)
}
],
Expand Down
40 changes: 6 additions & 34 deletions docs/Endpoint.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ using typescript to specify what Parameters we expect to get from the Adapter

```typescript

import type { ParamNode } from "odin-react";
import { type ParamNode } from "odin-react";
import { useAdapterEndpoint } from "odin-react";

// We extend the type "ParamNode" so that the AdapterEndpoint knows
// that we are specifying a nested Dictionary shaped oject
Expand Down Expand Up @@ -122,28 +123,18 @@ The object returned by the `useAdapterEndpoint` hook has the following relevant
</td>
</tr>
<tr>
<td>updateFlag</td>
<td>symbol</td>
<td>loading</td>
<td>boolean</td>
<td>
Flag token that will change whenever the data has changed,
to alert components using the data to re-render.
</td>
</tr>
<tr>
<td>status</td>
<td>"init" | "connected" | "error"</td>
<td>
Connection status for the AdapterEndpoint.
State of the http connection. If `true`, the Endpoint is awaiting
a response to a PUT request
</td>
</tr>
<tr>
<td>get</td>
<td>async \<T = ParamNode\>(param_path, config) => Promise\<T\></td>
<td>
Async http GET method. Request the provided value(s) from the parameter tree.

This method does NOT automatically merge the response into the
`Endpoint.Data` object.

- `param_path`: the path of the data desired. defaults to an Empty String
to get the entire param tree.
Expand All @@ -170,25 +161,6 @@ The object returned by the `useAdapterEndpoint` hook has the following relevant

Returns a Promise that will resolve to the data within the HTTP response.
</td>
</tr>
<tr>
<td>refreshData</td>
<td>() => void</td>
<td>
Function that performs a top level GET request to refresh the
`endpoint.data` object.
</td>
</tr>
<tr>
<td>mergeData</td>
<td>(newData, param_path) => void</td>
<td>
A method to merge one chunk of (potentially nested) data into the
`endpoint.data` object, to apply any changes.

Often used after a PUT or GET request to update the `endpoint.data`
object with the response.
</td>
</tr>
</tbody>
</table>
Expand Down
4 changes: 2 additions & 2 deletions docs/FAQ.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ the port number.
- Ensure that Odin Control has [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS)
enabled in the config file. This allows Odin React to access the API resources
from another server.
- This is only required when running Odin React in development mode. When
- This is only required when running Odin React in development mode. If
served statically, it is running on the same server interface and thus
does not need CORS.

Expand All @@ -25,7 +25,7 @@ from another server.
Odin React uses Bootstrap's components and for a lot of it's styling.
- Ensure that you have not imported a custom CSS file that may be overwriting
the normal styling for components.
- If you want to apply custom CSS styling to a page or component, refer to
- If you want to apply custom CSS styling to a page or component, refer to the
[Page on Styling](./?path=/docs/layout-and-styling--docs)

## The AdapterEndpoints in my GUI are running twice when a page is loaded.
Expand Down
51 changes: 16 additions & 35 deletions lib/components/AdapterEndpoint/AdapterEndpoint.types.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import type { AxiosRequestConfig } from "axios";

type status = "init" | "connected" | "error";

interface AdapterEndpoint<T extends ParamNode = ParamNode> {
interface AdapterEndpoint<T extends Record<string, ParamTree> = ParamNode> {
/**
* Recursive Nested dictionary structure representing the adapter Param Tree. Should be read only
* from this interface
*/
data: Readonly<T>;
data?: Readonly<T>;

/**
* Dictionary structure containing the adapter Metadata, if its implimented by the adapter
* Dictionary structure containing the adapter Metadata, if its implemented by the adapter
*/
metadata: Readonly<Metadata<T>>;
metadata?: Readonly<Metadata<T>>;
/**
* Any Errors that occur during http methods or otherwise will be accessible here
*/
error: null | Error;
// error: null | Error;
/**
* State of the http client connection to the adapter.
* If true, the AdapterEndpoint is awaiting a response from the adapter.
Expand All @@ -26,18 +24,18 @@ interface AdapterEndpoint<T extends ParamNode = ParamNode> {
/**
* Flag token that will change whenever the data has changed, to alert WithEndpoint components
*/
updateFlag: symbol;
// updateFlag: symbol;

/**
* Connection status for the AdapterEndpoint.
*/
status: status;
// status: status;

/**
* Api String. Used to differentiate between Odin Control versions.
* If blank, Odin Control is v2.*
*/
apiVersion: string;
apiVersion: "" | "0.1";
/**
* Async http GET method. Request the provided value(s) from the parameter tree.
* It is worth noting that this method does NOT automatically merge the response into the Endpoint.Data object.
Expand All @@ -46,14 +44,15 @@ interface AdapterEndpoint<T extends ParamNode = ParamNode> {
* @returns An Async promise, that when resolved will return the data within the HTTP response
*/
get: <T = ParamNode>(param_path?: string, config?: getConfig) => Promise<T>;

/**
* Async http PUT method. Modify the data in the param tree at the provided path
* It is worth noting that this method does NOT automatically merge the response into the Endpoint.Data object.
* @param {ParamNode} data - The data, with a key, that you wish to PUT to the Param Tree
* @param {string} param_path - the path you want to PUT to. Defaults to an empty string for a top level PUT
* @returns An Async promise, that when resolved will return the data within the HTTP response
*/
put: <T = Parameter>(data: {[key: string]: T}, param_path?: string) => Promise<{[Property in keyof typeof data]: T}>;
put: <T extends ParamNode>(data: T, param_path?: string) => Promise<T>;

/**
* Async http POST method. Not often implemented by Adapters, but potentially used to post data
Expand All @@ -65,36 +64,23 @@ interface AdapterEndpoint<T extends ParamNode = ParamNode> {
post: (data: ParamNode, param_path?: string) => Promise<ParamNode>;

/**
* Async http DELETE method. Renamed because 'delete' is a reserved word in javascript. Not often
* implemented by adapters.
* Async http DELETE method. Not often implemented by adapters, but potentially used to remove
* some part of a mutable Parameter Tree.
* @param param_path the path to the data you want to DELETE. Defaults to an empty string
* @returns An Async promise, that when resolved will return the data within the HTTP response
*/
remove: (param_path?: string) => Promise<ParamNode>;
/**
* A method to automatically perform a top level GET request and refresh the AdapterEndpoint's current view of the data
* @returns
*/
refreshData: () => void;
/**
* A method to merge one chunk of (potentially nested) data into the AdapterEndpoint's current view of the data,
* to update the data without having to GET from the entire adapter
* @param {ParamNode} newData - The new data to be merged in
* @param {string} param_path - the location that data within the ParamTree to merge it
* @returns
*/
mergeData: (newData: ParamNode, param_path: string) => void;
delete: (param_path?: string) => Promise<ParamNode>;

}

/** Defines allowed values for paramater primatives. */
/** Defines allowed values for parameter primitives. */
type Parameter = string | number | boolean | null | undefined;

/** Dict structure for the Parameters. */
type ParamNode = {[key:string]: ParamTree};

/** Any possible value from the Param Tree (Basically any possible JSON value)
* This could be a primative value, a dict structure, or an array of any of these values.
* This could be a primitive value, a dict structure, or an array of any of these values.
* This flexibility allows for the recursive nested structure of the Parameter Tree
*/
type ParamTree = Parameter | ParamTree[] | ParamNode;
Expand Down Expand Up @@ -144,9 +130,4 @@ interface getConfig {
responseType?: AxiosRequestConfig['responseType'];
}

export type { AdapterEndpoint, Metadata, MetadataValue, Parameter, ParamNode, ParamTree, getConfig, status};

/**
* @deprecated This is the old name for this type and should be replaced with "AdapterEndpoint"
*/
export type AdapterEndpoint_t<T extends ParamNode> = AdapterEndpoint<T>;
export type { AdapterEndpoint, Metadata, MetadataValue, Parameter, ParamNode, ParamTree, getConfig};
24 changes: 1 addition & 23 deletions lib/components/AdapterEndpoint/index.mock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,56 +171,34 @@ const MockedEndpoint: AdapterEndpoint<EndpointData> = {
get: fn(mockGet).mockName("get"),
// @ts-ignore
put: fn(mockPut).mockName("put"),
post: fn().mockName("post"),
remove: fn(),
refreshData: fn(),
mergeData: fn(),
data: testAdapterData,
metadata: testMetadata,
status: "connected",
apiVersion: "",
error: null,
loading: false,
updateFlag: Symbol("mocked")
}

const MockedLiveEndpoint: AdapterEndpoint<LiveViewData> = {
// @ts-ignore
get: fn(mockGet).mockName("get"),
// @ts-ignore
put: fn(mockPut).mockName("put"),
post: fn().mockName("post"),
remove: fn(),
refreshData: fn(),
mergeData: fn(),
data: testLiveData,
// @ts-ignore
metadata: testLiveData,
status: "connected",
metadata: testLiveData,
apiVersion: "",
error: null,
loading: false,
updateFlag: Symbol("mocked")

}

const MockedLiveEndpointNoControls: AdapterEndpoint<{}> = {
// @ts-ignore
get: fn(mockGet).mockName("get"),
// @ts-ignore
put: fn(mockPut).mockName("put"),
post: fn().mockName("post"),
remove: fn(),
refreshData: fn(),
mergeData: fn(),
data: {},
// @ts-ignore
metadata: {},
status: "connected",
apiVersion: "",
error: null,
loading: false,
updateFlag: Symbol("mocked")
}

const useAdapterEndpoint = fn(
Expand Down
Loading