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
26 changes: 25 additions & 1 deletion src/app/sidebar/app-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import {
SidebarRail,
} from "@/components/ui/sidebar";
import { useAppDispatch, useAppSelector } from "@/hooks/store";
import { selectHeaders, selectRootResources, setHeaders } from "@/state/store";
import { selectHeaders, selectRootResources, setHeaders, selectMockServerEnabled, setMockServerEnabled } from "@/state/store";
import { Label } from "../../components/ui/label";
import { Checkbox } from "../../components/ui/checkbox";
import { ResourceTypeList } from "@/components/resource_types/resource_type_list";

// The AppSidebar. This fetches the list of root resources from the schema and displays them.
Expand All @@ -21,6 +22,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
<Sidebar {...props}>
<SidebarHeader>
<HeadersInput />
<MockServerToggle />
</SidebarHeader>
<SidebarContent>
<ResourceTypeList resources={resources} />
Expand Down Expand Up @@ -56,3 +58,25 @@ export function HeadersInput() {
</form>
);
}

export function MockServerToggle() {
const mockServerEnabled = useAppSelector(selectMockServerEnabled);
const dispatch = useAppDispatch();

const handleToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setMockServerEnabled(event.target.checked));
};

return (
<SidebarGroup className="py-2">
<SidebarGroupContent>
<Checkbox
id="mock-server"
checked={mockServerEnabled}
onChange={handleToggle}
label="Use Mock Server"
/>
</SidebarGroupContent>
</SidebarGroup>
);
}
4 changes: 2 additions & 2 deletions src/components/custom_method.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useMemo } from "react";
import { CustomMethod } from "@aep_dev/aep-lib-ts";
import { ResourceInstance } from "@/state/fetch";
import { ResourceInstance, mockAwareFetch } from "@/state/fetch";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
Expand Down Expand Up @@ -37,7 +37,7 @@
// Construct the URL for the custom method
const url = `${props.resourceInstance.schema.server_url}/${props.resourceInstance.path}:${props.customMethod.name}`;

const response = await fetch(url, {
const response = await mockAwareFetch(url, {
method: props.customMethod.method,
headers: {
'Content-Type': 'application/json',
Expand Down Expand Up @@ -128,7 +128,7 @@
return Object.entries(properties).map(([name, schema]) =>
renderField(name, schema)
);
}, [props.customMethod, form.control]);

Check warning on line 131 in src/components/custom_method.tsx

View workflow job for this annotation

GitHub Actions / checks

React Hook useMemo has a missing dependency: 'renderField'. Either include it or remove the dependency array

Check warning on line 131 in src/components/custom_method.tsx

View workflow job for this annotation

GitHub Actions / test (20)

React Hook useMemo has a missing dependency: 'renderField'. Either include it or remove the dependency array

Check warning on line 131 in src/components/custom_method.tsx

View workflow job for this annotation

GitHub Actions / test (18)

React Hook useMemo has a missing dependency: 'renderField'. Either include it or remove the dependency array

return (
<Card>
Expand Down
39 changes: 39 additions & 0 deletions src/components/ui/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as React from "react"
import { cn } from "@/lib/utils"

export interface CheckboxProps
extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string
}

const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
({ className, label, ...props }, ref) => {
return (
<div className="flex items-center gap-2">
<input
type="checkbox"
className={cn(
"h-4 w-4 rounded border border-input bg-background ring-offset-background",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
"disabled:cursor-not-allowed disabled:opacity-50",
"cursor-pointer",
className
)}
ref={ref}
{...props}
/>
{label && (
<label
htmlFor={props.id}
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
>
{label}
</label>
)}
</div>
)
}
)
Checkbox.displayName = "Checkbox"

export { Checkbox }
85 changes: 80 additions & 5 deletions src/state/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { toast } from "@/hooks/use-toast";
import { ResourceSchema } from "./openapi";
import MockResourceStore from "./mock-store";
import { store } from "./store";

// Helper function to check if mock server is enabled
function isMockServerEnabled(): boolean {
return store.getState().mockServer.enabled;
}

// Helper function to check for API errors in response body
function checkForApiErrors(responseData: any): void {
Expand Down Expand Up @@ -118,6 +125,21 @@ function getHeaders(headers: string): object {

async function List(url: string, r: ResourceSchema, headersString: string = ""): Promise<ResourceInstance[]> {
try {
// Use mock server if enabled
if (isMockServerEnabled()) {
const mockStore = MockResourceStore.getInstance();
const list_response = mockStore.list(url);

const results: ResourceInstance[] = [];
if (list_response?.results && Array.isArray(list_response.results)) {
for(const result of list_response.results) {
results.push(new ResourceInstance(result['id'], result['path'], result, r));
}
}
return results;
}

// Real network call
const response = await fetch(url, {headers: getHeaders(headersString) as HeadersInit});
const list_response = await handleResponse(response, 'List');

Expand All @@ -135,11 +157,19 @@ async function List(url: string, r: ResourceSchema, headersString: string = ""):

async function Delete(url: string, headers: string = ""): Promise<void> {
try {
// Use mock server if enabled
if (isMockServerEnabled()) {
const mockStore = MockResourceStore.getInstance();
mockStore.delete(url);
return;
}

// Real network call
const response = await fetch(url, {
method: 'DELETE',
headers: getHeaders(headers) as HeadersInit
});

await handleResponse(response, 'Delete');
} catch (error) {
handleError(error, 'delete resource');
Expand All @@ -148,9 +178,17 @@ async function Delete(url: string, headers: string = ""): Promise<void> {

async function Get(url: string, r: ResourceSchema, headersString: string = ""): Promise<ResourceInstance> {
try {
// Use mock server if enabled
if (isMockServerEnabled()) {
const mockStore = MockResourceStore.getInstance();
const result = mockStore.get(url);
return new ResourceInstance(result['id'], result['path'], result, r);
}

// Real network call
const response = await fetch(url, { headers: getHeaders(headersString) as HeadersInit});
const result = await handleResponse(response, 'Get');

return new ResourceInstance(result['id'], result['path'], result, r);
} catch (error) {
handleError(error, 'get resource');
Expand All @@ -159,12 +197,20 @@ async function Get(url: string, r: ResourceSchema, headersString: string = ""):

async function Create(url: string, contents: object, headersString: string = ""): Promise<void> {
try {
// Use mock server if enabled
if (isMockServerEnabled()) {
const mockStore = MockResourceStore.getInstance();
mockStore.create(url, contents);
return;
}

// Real network call
const response = await fetch(url, {
method: 'POST',
headers: getHeaders(headersString) as HeadersInit,
body: JSON.stringify(contents),
});

await handleResponse(response, 'Create');
} catch (error) {
handleError(error, 'create resource');
Expand All @@ -173,16 +219,45 @@ async function Create(url: string, contents: object, headersString: string = "")

async function Patch(url: string, contents: object, headersString: string = ""): Promise<void> {
try {
// Use mock server if enabled
if (isMockServerEnabled()) {
const mockStore = MockResourceStore.getInstance();
mockStore.update(url, contents);
return;
}

// Real network call
const response = await fetch(url, {
method: 'PATCH',
headers: getHeaders(headersString) as HeadersInit,
body: JSON.stringify(contents),
});

await handleResponse(response, 'Patch');
} catch (error) {
handleError(error, 'patch resource');
}
}

export {ResourceInstance, List, Create, Get, Delete, Patch}
/**
* Mock-aware fetch wrapper for custom methods
* Custom methods are executed but return a success response from the mock server
*/
async function mockAwareFetch(url: string, options?: RequestInit): Promise<Response> {
if (isMockServerEnabled()) {
// For mock server, simulate a successful custom method call
// Return a mock response
return new Response(
JSON.stringify({ success: true, message: 'Custom method executed (mock)' }),
{
status: 200,
headers: { 'Content-Type': 'application/json' }
}
);
}

// Real network call
return fetch(url, options);
}

export {ResourceInstance, List, Create, Get, Delete, Patch, mockAwareFetch}
Loading
Loading