mock-extraction feedback (#7207)

* save point

* both cases work

* use action and hack

* throw error on naming collision

* fix type

* fix test
This commit is contained in:
Jack Kavanagh 2024-04-02 15:42:39 +02:00 committed by GitHub
parent 39bc37bf10
commit e38badfbf3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 154 additions and 32 deletions

View File

@ -1,22 +1,15 @@
import { loadFixture } from '../../playwright/paths';
import { test } from '../../playwright/test';
test('can make a mock route', async ({ app, page }) => {
test('can make a mock route', async ({ page }) => {
test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms');
const text = await loadFixture('smoke-test-collection.yaml');
await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text);
await page.getByRole('button', { name: 'Create in project' }).click();
await page.getByRole('menuitemradio', { name: 'Import' }).click();
await page.locator('[data-test-id="import-from-clipboard"]').click();
await page.getByRole('button', { name: 'Scan' }).click();
await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click();
await page.getByLabel('New Mock Server').click();
await page.getByRole('button', { name: 'Create', exact: true }).click();
await page.getByRole('button', { name: 'New Mock Route' }).click();
await page.getByText('GET/').click();
await page.getByTestId('CodeEditor').getByRole('textbox').fill('123');
await page.getByLabel('Project Actions').click();
await page.getByText('Rename').click();
await page.locator('#prompt-input').fill('/123');
await page.getByRole('button', { name: 'Rename' }).click();
await page.getByRole('button', { name: 'Test' }).click();
await page.getByText('No body returned for response').click();

View File

@ -2,21 +2,32 @@ import fs from 'fs/promises';
import React, { useState } from 'react';
import { Button } from 'react-aria-components';
import { useNavigate, useParams } from 'react-router-dom';
import { useRouteLoaderData } from 'react-router-dom';
import {
useFetcher,
useRouteLoaderData,
} from 'react-router-dom';
import { invariant } from '../../../utils/invariant';
import { useMockRoutePatcher } from '../../routes/mock-route';
import { RequestLoaderData } from '../../routes/request';
import { WorkspaceLoaderData } from '../../routes/workspace';
import { HelpTooltip } from '../help-tooltip';
import { Icon } from '../icon';
import { showPrompt } from '../modals';
export const MockResponseExtractor = () => {
const {
activeWorkspace,
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
const { mockServerAndRoutes, activeResponse } = useRouteLoaderData('request/:requestId') as RequestLoaderData;
const patchMockRoute = useMockRoutePatcher();
const navigate = useNavigate();
const {
organizationId,
projectId,
workspaceId,
} = useParams();
const fetcher = useFetcher();
const [selectedMockServer, setSelectedMockServer] = useState('');
const [selectedMockRoute, setSelectedMockRoute] = useState('');
return (
@ -30,21 +41,84 @@ export const MockResponseExtractor = () => {
<form
onSubmit={async e => {
e.preventDefault();
if (!selectedMockServer || !selectedMockRoute) {
if (selectedMockServer && selectedMockRoute) {
if (activeResponse) {
// TODO: move this out of the renderer, and upsert mock
const body = await fs.readFile(activeResponse.bodyPath);
patchMockRoute(selectedMockRoute, {
body: body.toString(),
mimeType: activeResponse.contentType,
statusCode: activeResponse.statusCode,
headers: activeResponse.headers,
});
}
return;
}
let path = '/new-route';
try {
path = activeResponse ? new URL(activeResponse.url).pathname : '/new-route';
} catch (e) {
console.log(e);
}
if (!selectedMockServer) {
showPrompt({
title: 'Create Mock Route',
defaultValue: path,
label: 'Name',
onComplete: async name => {
invariant(activeResponse, 'Active response must be defined');
const body = await fs.readFile(activeResponse.bodyPath);
// TODO: consider setting selected mock server here rather than redirecting
fetcher.submit(
JSON.stringify({
name: name,
body: body.toString(),
mimeType: activeResponse.contentType,
statusCode: activeResponse.statusCode,
headers: activeResponse.headers,
mockServerName: activeWorkspace.name,
}),
{
encType: 'application/json',
method: 'post',
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/mock-server/mock-route/new`,
}
);
},
});
return;
}
if (!selectedMockRoute) {
showPrompt({
title: 'Create Mock Route',
defaultValue: path,
label: 'Name',
onComplete: async name => {
invariant(activeResponse, 'Active response must be defined');
const body = await fs.readFile(activeResponse.bodyPath);
if (activeResponse) {
// TODO: move this out of the renderer, and upsert mock
const body = await fs.readFile(activeResponse.bodyPath);
// setSelectedMockRoute(newRoute._id);
patchMockRoute(selectedMockRoute, {
body: body.toString(),
mimeType: activeResponse.contentType,
statusCode: activeResponse.statusCode,
headers: activeResponse.headers,
fetcher.submit(
JSON.stringify({
name: name,
parentId: selectedMockServer,
body: body.toString(),
mimeType: activeResponse.contentType,
statusCode: activeResponse.statusCode,
headers: activeResponse.headers,
}),
{
encType: 'application/json',
method: 'post',
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/mock-server/mock-route/new`,
}
);
},
});
}
}}
>
<div className="form-row">
@ -61,7 +135,7 @@ export const MockResponseExtractor = () => {
setSelectedMockServer(selected);
}}
>
<option value="">-- Select... --</option>
<option value="">-- Create new... --</option>
{mockServerAndRoutes
.map(w => (
<option key={w._id} value={w._id}>
@ -87,7 +161,7 @@ export const MockResponseExtractor = () => {
setSelectedMockRoute(selected);
}}
>
<option value="">-- Select... --</option>
<option value="">-- Create new... --</option>
{mockServerAndRoutes.find(s => s._id === selectedMockServer)?.routes
.map(w => (
<option key={w._id} value={w._id}>
@ -112,10 +186,9 @@ export const MockResponseExtractor = () => {
</Button>
<Button
type="submit"
isDisabled={!selectedMockServer || !selectedMockRoute}
className="hover:no-underline bg-[--color-surprise] hover:bg-opacity-90 border border-solid border-[--hl-md] py-2 px-3 text-[--color-font-surprise] transition-colors rounded-sm"
>
Export
Extract to mock route
</Button>
</div>
</form>

View File

@ -1211,9 +1211,26 @@ export const createMockRouteAction: ActionFunction = async ({ request, params })
const patch = await request.json();
invariant(typeof patch.name === 'string', 'Name is required');
invariant(typeof patch.parentId === 'string', 'parentId is required');
// TODO: remove this hack
if (patch.mockServerName) {
const activeWorkspace = await models.workspace.getById(workspaceId);
invariant(activeWorkspace, 'Active workspace not found');
const workspace = await models.workspace.create({
name: activeWorkspace.name,
scope: 'mock-server',
parentId: projectId,
});
invariant(workspace, 'Workspace not found');
// create a mock server under the workspace with the same name
const newServer = await models.mockServer.getOrCreateForParentId(workspace._id, { name: activeWorkspace.name });
// TODO: filterout the mockServerName from the patch, or use an alternate method to create new workspace and server
const mockRoute = await models.mockRoute.create({ ...patch, parentId: newServer._id });
return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${newServer.parentId}/mock-server/mock-route/${mockRoute._id}`);
}
const mockServer = await models.mockServer.getById(patch.parentId);
invariant(mockServer, 'Mock server not found');
const mockRoute = await models.mockRoute.create(patch);
return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/mock-server/mock-route/${mockRoute._id}`);
return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${mockServer.parentId}/mock-server/mock-route/${mockRoute._id}`);
};
export const updateMockRouteAction: ActionFunction = async ({ request, params }) => {
const { mockRouteId } = params;

View File

@ -17,10 +17,12 @@ import { CodeEditor } from '../components/codemirror/code-editor';
import { MockResponseHeadersEditor } from '../components/editors/mock-response-headers-editor';
import { MockResponsePane } from '../components/mocks/mock-response-pane';
import { MockUrlBar } from '../components/mocks/mock-url-bar';
import { showAlert } from '../components/modals';
import { showAlert, showModal } from '../components/modals';
import { AlertModal } from '../components/modals/alert-modal';
import { EmptyStatePane } from '../components/panes/empty-state-pane';
import { Pane, PaneBody, PaneHeader } from '../components/panes/pane';
import { SvgIcon } from '../components/svg-icon';
import { MockServerLoaderData } from './mock-server';
import { useRootLoaderData } from './root';
export interface MockRouteLoaderData {
@ -91,6 +93,8 @@ export const useMockRoutePatcher = () => {
export const MockRouteRoute = () => {
const { mockServer, mockRoute } = useRouteLoaderData(':mockRouteId') as MockRouteLoaderData;
const { mockRoutes } = useRouteLoaderData('mock-server') as MockServerLoaderData;
const { userSession } = useRootLoaderData();
const patchMockRoute = useMockRoutePatcher();
const mockbinUrl = mockServer.useInsomniaCloud ? getMockServiceURL() : mockServer.url;
@ -142,6 +146,15 @@ export const MockRouteRoute = () => {
});
const upsertMockbinHar = async (pathInput?: string) => {
const hasRouteInServer = mockRoutes.filter(m => m._id !== mockRoute._id).find(m => m.name === pathInput);
if (hasRouteInServer) {
showModal(AlertModal, {
title: 'Error',
message: `Path "${pathInput}" must be unique. Please enter a different name.`,
});
return;
};
const compoundId = mockRoute.parentId + pathInput;
const error = await upsertBinOnRemoteFromResponse(compoundId);
if (error) {
@ -163,6 +176,15 @@ export const MockRouteRoute = () => {
});
};
const onSend = async (pathInput: string) => {
const hasRouteInServer = mockRoutes.filter(m => m._id !== mockRoute._id).find(m => m.name === pathInput);
if (hasRouteInServer) {
showModal(AlertModal, {
title: 'Error',
message: `Path "${pathInput}" must be unique. Please enter a different name.`,
});
return;
};
await upsertMockbinHar(pathInput);
const compoundId = mockRoute.parentId + pathInput;
createandSendPrivateRequest({

View File

@ -11,17 +11,18 @@ import { WorkspaceSyncDropdown } from '../components/dropdowns/workspace-sync-dr
import { EditableInput } from '../components/editable-input';
import { Icon } from '../components/icon';
import { showModal, showPrompt } from '../components/modals';
import { AlertModal } from '../components/modals/alert-modal';
import { AskModal } from '../components/modals/ask-modal';
import { EmptyStatePane } from '../components/panes/empty-state-pane';
import { SidebarLayout } from '../components/sidebar-layout';
import { SvgIcon } from '../components/svg-icon';
import { formatMethodName } from '../components/tags/method-tag';
import { MockRouteResponse, MockRouteRoute, useMockRoutePatcher } from './mock-route';
interface LoaderData {
export interface MockServerLoaderData {
mockServerId: string;
mockRoutes: MockRoute[];
}
export const loader: LoaderFunction = async ({ params }): Promise<LoaderData> => {
export const loader: LoaderFunction = async ({ params }): Promise<MockServerLoaderData> => {
const { organizationId, projectId, workspaceId } = params;
invariant(organizationId, 'Organization ID is required');
invariant(projectId, 'Project ID is required');
@ -46,7 +47,7 @@ const MockServerRoute = () => {
workspaceId: string;
mockRouteId: string;
};
const { mockServerId, mockRoutes } = useLoaderData() as LoaderData;
const { mockServerId, mockRoutes } = useLoaderData() as MockServerLoaderData;
const fetcher = useFetcher();
const navigate = useNavigate();
const patchMockRoute = useMockRoutePatcher();
@ -66,6 +67,14 @@ const MockServerRoute = () => {
defaultValue: mockRoutes.find(s => s._id === id)?.name,
submitName: 'Rename',
onComplete: name => {
const hasRouteInServer = mockRoutes.filter(m => m._id !== id).find(m => m.name === name);
if (hasRouteInServer) {
showModal(AlertModal, {
title: 'Error',
message: `Path "${name}" must be unique. Please enter a different name.`,
});
return;
};
name && patchMockRoute(id, { name });
},
});
@ -195,6 +204,14 @@ const MockServerRoute = () => {
});
}}
onSubmit={name => {
const hasRouteInServer = mockRoutes.filter(m => m._id !== item._id).find(m => m.name === name);
if (hasRouteInServer) {
showModal(AlertModal, {
title: 'Error',
message: `Path "${name}" must be unique. Please enter a different name.`,
});
return;
};
name && fetcher.submit(
{ name },
{