mirror of
https://github.com/Kong/insomnia
synced 2024-11-08 06:39:48 +00:00
Update the design view to use data loading/routing (#5458)
* update design route * Add button to generate a request collection from an api spec * move the generate requests button to the bottom
This commit is contained in:
parent
2a8da1cf40
commit
32e788c49b
@ -1,12 +1,10 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import React, { FC, useCallback } from 'react';
|
import React, { FC, useCallback } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { documentationLinks } from '../../common/documentation';
|
import { documentationLinks } from '../../common/documentation';
|
||||||
import { selectFileOrFolder } from '../../common/select-file-or-folder';
|
import { selectFileOrFolder } from '../../common/select-file-or-folder';
|
||||||
import { faint } from '../css/css-in-js';
|
import { faint } from '../css/css-in-js';
|
||||||
import { selectActiveApiSpec } from '../redux/selectors';
|
|
||||||
import { Dropdown } from './base/dropdown/dropdown';
|
import { Dropdown } from './base/dropdown/dropdown';
|
||||||
import { DropdownButton } from './base/dropdown/dropdown-button';
|
import { DropdownButton } from './base/dropdown/dropdown-button';
|
||||||
import { DropdownItem } from './base/dropdown/dropdown-item';
|
import { DropdownItem } from './base/dropdown/dropdown-item';
|
||||||
@ -123,12 +121,6 @@ const SecondaryAction: FC<Props> = ({ onImport }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DesignEmptyState: FC<Props> = ({ onImport }) => {
|
export const DesignEmptyState: FC<Props> = ({ onImport }) => {
|
||||||
const activeApiSpec = useSelector(selectActiveApiSpec);
|
|
||||||
|
|
||||||
if (!activeApiSpec || activeApiSpec.contents) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<EmptyStatePane
|
<EmptyStatePane
|
||||||
|
@ -34,6 +34,10 @@ export const SwaggerUI: FC<{
|
|||||||
supportedSubmitMethods,
|
supportedSubmitMethods,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
SwaggerUIInstance = null;
|
||||||
|
};
|
||||||
}, [supportedSubmitMethods, spec]);
|
}, [supportedSubmitMethods, spec]);
|
||||||
|
|
||||||
return <div ref={domNodeRef} />;
|
return <div ref={domNodeRef} />;
|
||||||
|
@ -124,11 +124,22 @@ const router = createMemoryRouter(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${ACTIVITY_SPEC}`,
|
path: `${ACTIVITY_SPEC}`,
|
||||||
|
loader: async (...args) => (await import('./routes/design')).loader(...args),
|
||||||
element: (
|
element: (
|
||||||
<Suspense fallback={<AppLoadingIndicator />}>
|
<Suspense fallback={<AppLoadingIndicator />}>
|
||||||
<Design />
|
<Design />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
),
|
),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'update',
|
||||||
|
action: async (...args) => (await import('./routes/actions')).updateApiSpecAction(...args),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'generate-request-collection',
|
||||||
|
action: async (...args) => (await import('./routes/actions')).generateCollectionFromApiSpecAction(...args),
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'test/*',
|
path: 'test/*',
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
import type { IRuleResult } from '@stoplight/spectral-core';
|
||||||
import { generate, runTests, Test } from 'insomnia-testing';
|
import { generate, runTests, Test } from 'insomnia-testing';
|
||||||
import { ActionFunction, redirect } from 'react-router-dom';
|
import { ActionFunction, redirect } from 'react-router-dom';
|
||||||
|
|
||||||
import * as session from '../../account/session';
|
import * as session from '../../account/session';
|
||||||
import { ACTIVITY_DEBUG, ACTIVITY_SPEC } from '../../common/constants';
|
import { ACTIVITY_DEBUG, ACTIVITY_SPEC } from '../../common/constants';
|
||||||
import { database } from '../../common/database';
|
import { database } from '../../common/database';
|
||||||
|
import { importRaw } from '../../common/import';
|
||||||
import * as models from '../../models';
|
import * as models from '../../models';
|
||||||
import * as workspaceOperations from '../../models/helpers/workspace-operations';
|
import * as workspaceOperations from '../../models/helpers/workspace-operations';
|
||||||
import { DEFAULT_ORGANIZATION_ID } from '../../models/organization';
|
import { DEFAULT_ORGANIZATION_ID } from '../../models/organization';
|
||||||
@ -427,3 +429,57 @@ export const runTestAction: ActionFunction = async ({ params }) => {
|
|||||||
|
|
||||||
return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/test/test-suite/${testSuiteId}/test-result/${testResult._id}`);
|
return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/test/test-suite/${testSuiteId}/test-result/${testResult._id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Api Spec
|
||||||
|
export const updateApiSpecAction: ActionFunction = async ({
|
||||||
|
request,
|
||||||
|
params,
|
||||||
|
}) => {
|
||||||
|
const { workspaceId } = params;
|
||||||
|
invariant(typeof workspaceId === 'string', 'Workspace ID is required');
|
||||||
|
const formData = await request.formData();
|
||||||
|
const contents = formData.get('contents');
|
||||||
|
const fromSync = Boolean(formData.get('fromSync'));
|
||||||
|
|
||||||
|
invariant(typeof contents === 'string', 'Contents is required');
|
||||||
|
|
||||||
|
const apiSpec = await models.apiSpec.getByParentId(workspaceId);
|
||||||
|
|
||||||
|
invariant(apiSpec, 'API Spec not found');
|
||||||
|
await database.update({
|
||||||
|
...apiSpec,
|
||||||
|
modified: Date.now(),
|
||||||
|
created: fromSync ? Date.now() : apiSpec.created,
|
||||||
|
contents,
|
||||||
|
}, fromSync);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateCollectionFromApiSpecAction: ActionFunction = async ({
|
||||||
|
params,
|
||||||
|
}) => {
|
||||||
|
const { organizationId, projectId, workspaceId } = params;
|
||||||
|
|
||||||
|
invariant(typeof workspaceId === 'string', 'Workspace ID is required');
|
||||||
|
|
||||||
|
const apiSpec = await models.apiSpec.getByParentId(workspaceId);
|
||||||
|
|
||||||
|
if (!apiSpec) {
|
||||||
|
throw new Error('No API Specification was found');
|
||||||
|
}
|
||||||
|
const isLintError = (result: IRuleResult) => result.severity === 0;
|
||||||
|
const results = (await window.main.spectralRun(apiSpec.contents)).filter(isLintError);
|
||||||
|
if (apiSpec.contents && results && results.length) {
|
||||||
|
throw new Error('Error Generating Configuration');
|
||||||
|
}
|
||||||
|
|
||||||
|
await importRaw(apiSpec.contents, {
|
||||||
|
getWorkspaceId: () => Promise.resolve(workspaceId),
|
||||||
|
enableDiffBasedPatching: true,
|
||||||
|
enableDiffDeep: true,
|
||||||
|
bypassDiffProps: {
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/${ACTIVITY_DEBUG}`);
|
||||||
|
};
|
||||||
|
@ -1,13 +1,23 @@
|
|||||||
import type { IRuleResult } from '@stoplight/spectral-core';
|
import type { IRuleResult } from '@stoplight/spectral-core';
|
||||||
import React, { createRef, FC, RefObject, useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { createRef, FC, useCallback, useMemo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import {
|
||||||
|
LoaderFunction,
|
||||||
|
useFetcher,
|
||||||
|
useLoaderData,
|
||||||
|
useParams,
|
||||||
|
} from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { parseApiSpec, ParsedApiSpec } from '../../common/api-specs';
|
import { parseApiSpec, ParsedApiSpec } from '../../common/api-specs';
|
||||||
import { database } from '../../common/database';
|
import { ACTIVITY_SPEC } from '../../common/constants';
|
||||||
import { debounce } from '../../common/misc';
|
import { debounce } from '../../common/misc';
|
||||||
|
import { ApiSpec } from '../../models/api-spec';
|
||||||
import * as models from '../../models/index';
|
import * as models from '../../models/index';
|
||||||
import { CodeEditor, CodeEditorHandle } from '../components/codemirror/code-editor';
|
import { invariant } from '../../utils/invariant';
|
||||||
|
import {
|
||||||
|
CodeEditor,
|
||||||
|
CodeEditorHandle,
|
||||||
|
} from '../components/codemirror/code-editor';
|
||||||
import { DesignEmptyState } from '../components/design-empty-state';
|
import { DesignEmptyState } from '../components/design-empty-state';
|
||||||
import { ErrorBoundary } from '../components/error-boundary';
|
import { ErrorBoundary } from '../components/error-boundary';
|
||||||
import { Notice, NoticeTable } from '../components/notice-table';
|
import { Notice, NoticeTable } from '../components/notice-table';
|
||||||
@ -15,8 +25,10 @@ import { SidebarLayout } from '../components/sidebar-layout';
|
|||||||
import { SpecEditorSidebar } from '../components/spec-editor/spec-editor-sidebar';
|
import { SpecEditorSidebar } from '../components/spec-editor/spec-editor-sidebar';
|
||||||
import { SwaggerUI } from '../components/swagger-ui';
|
import { SwaggerUI } from '../components/swagger-ui';
|
||||||
import { superFaint } from '../css/css-in-js';
|
import { superFaint } from '../css/css-in-js';
|
||||||
import { useActiveApiSpecSyncVCSVersion, useGitVCSVersion } from '../hooks/use-vcs-version';
|
import {
|
||||||
import { selectActiveApiSpec } from '../redux/selectors';
|
useActiveApiSpecSyncVCSVersion,
|
||||||
|
useGitVCSVersion,
|
||||||
|
} from '../hooks/use-vcs-version';
|
||||||
|
|
||||||
const isLintError = (result: IRuleResult) => result.severity === 0;
|
const isLintError = (result: IRuleResult) => result.severity === 0;
|
||||||
|
|
||||||
@ -29,218 +41,260 @@ const EmptySpaceHelper = styled.div({
|
|||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const Toolbar = styled.div({
|
||||||
|
boxSizing: 'content-box',
|
||||||
|
position: 'sticky',
|
||||||
|
top: 0,
|
||||||
|
zIndex: 1,
|
||||||
|
backgroundColor: 'var(--color-bg)',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
flexDirection: 'row',
|
||||||
|
borderTop: '1px solid var(--hl-md)',
|
||||||
|
height: 'var(--line-height-sm)',
|
||||||
|
fontSize: 'var(--font-size-sm)',
|
||||||
|
'& > button': {
|
||||||
|
color: 'var(--hl)',
|
||||||
|
padding: 'var(--padding-xs) var(--padding-xs)',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface LoaderData {
|
||||||
|
lintMessages: LintMessage[];
|
||||||
|
apiSpec: ApiSpec;
|
||||||
|
swaggerSpec: ParsedApiSpec['contents'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loader: LoaderFunction = async ({
|
||||||
|
params,
|
||||||
|
}): Promise<LoaderData> => {
|
||||||
|
const { workspaceId } = params;
|
||||||
|
invariant(workspaceId, 'Workspace ID is required');
|
||||||
|
const apiSpec = await models.apiSpec.getByParentId(workspaceId);
|
||||||
|
invariant(apiSpec, 'API spec not found');
|
||||||
|
|
||||||
|
let lintMessages: LintMessage[] = [];
|
||||||
|
if (apiSpec.contents && apiSpec.contents.length !== 0) {
|
||||||
|
lintMessages = (await window.main.spectralRun(apiSpec.contents))
|
||||||
|
.filter(isLintError)
|
||||||
|
.map(({ severity, code, message, range }) => ({
|
||||||
|
type: severity === 0 ? 'error' : 'warning',
|
||||||
|
message: `${code} ${message}`,
|
||||||
|
line: range.start.line,
|
||||||
|
// Attach range that will be returned to our click handler
|
||||||
|
range,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let swaggerSpec: ParsedApiSpec['contents'] = {};
|
||||||
|
try {
|
||||||
|
swaggerSpec = parseApiSpec(apiSpec.contents).contents;
|
||||||
|
} catch (err) {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
lintMessages,
|
||||||
|
apiSpec,
|
||||||
|
swaggerSpec,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
interface LintMessage extends Notice {
|
interface LintMessage extends Notice {
|
||||||
range: IRuleResult['range'];
|
range: IRuleResult['range'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const RenderEditor: FC<{ editor: RefObject<CodeEditorHandle> }> = ({ editor }) => {
|
const Design: FC = () => {
|
||||||
const activeApiSpec = useSelector(selectActiveApiSpec);
|
const { organizationId, projectId, workspaceId } = useParams() as {
|
||||||
const [lintMessages, setLintMessages] = useState<LintMessage[]>([]);
|
organizationId: string;
|
||||||
const contents = activeApiSpec?.contents ?? '';
|
projectId: string;
|
||||||
const gitVersion = useGitVCSVersion();
|
workspaceId: string;
|
||||||
const syncVersion = useActiveApiSpecSyncVCSVersion();
|
};
|
||||||
|
const { apiSpec, lintMessages, swaggerSpec } = useLoaderData() as LoaderData;
|
||||||
|
const editor = createRef<CodeEditorHandle>();
|
||||||
|
|
||||||
const onImport = useCallback(async (value: string) => {
|
const updateApiSpecFetcher = useFetcher();
|
||||||
if (!activeApiSpec) {
|
const generateRequestCollectionFetcher = useFetcher();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await database.update({ ...activeApiSpec, modified: Date.now(), created: Date.now(), contents: value }, true);
|
|
||||||
}, [activeApiSpec]);
|
|
||||||
|
|
||||||
const uniquenessKey = `${activeApiSpec?._id}::${activeApiSpec?.created}::${gitVersion}::${syncVersion}`;
|
|
||||||
const onCodeEditorChange = useMemo(() => {
|
const onCodeEditorChange = useMemo(() => {
|
||||||
const handler = async (contents: string) => {
|
const handler = async (contents: string) => {
|
||||||
if (!activeApiSpec) {
|
updateApiSpecFetcher.submit(
|
||||||
return;
|
{
|
||||||
}
|
contents: contents,
|
||||||
|
},
|
||||||
await models.apiSpec.update({ ...activeApiSpec, contents });
|
{
|
||||||
|
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/${ACTIVITY_SPEC}/update`,
|
||||||
|
method: 'post',
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return debounce(handler, 500);
|
return debounce(handler, 500);
|
||||||
}, [activeApiSpec]);
|
}, [organizationId, projectId, updateApiSpecFetcher, workspaceId]);
|
||||||
|
|
||||||
useEffect(() => {
|
const handleScrollToSelection = useCallback(
|
||||||
let isMounted = true;
|
(chStart: number, chEnd: number, lineStart: number, lineEnd: number) => {
|
||||||
const update = async () => {
|
if (!editor.current) {
|
||||||
// Lint only if spec has content
|
return;
|
||||||
if (contents && contents.length !== 0) {
|
|
||||||
const run = await window.main.spectralRun(contents);
|
|
||||||
|
|
||||||
const results: LintMessage[] = run.filter(isLintError)
|
|
||||||
.map(({ severity, code, message, range }) => ({
|
|
||||||
type: severity === 0 ? 'error' : 'warning',
|
|
||||||
message: `${code} ${message}`,
|
|
||||||
line: range.start.line,
|
|
||||||
// Attach range that will be returned to our click handler
|
|
||||||
range,
|
|
||||||
}));
|
|
||||||
isMounted && setLintMessages(results);
|
|
||||||
} else {
|
|
||||||
isMounted && setLintMessages([]);
|
|
||||||
}
|
}
|
||||||
};
|
editor.current.scrollToSelection(chStart, chEnd, lineStart, lineEnd);
|
||||||
update();
|
},
|
||||||
|
[editor]
|
||||||
return () => {
|
|
||||||
isMounted = false;
|
|
||||||
};
|
|
||||||
}, [contents]);
|
|
||||||
|
|
||||||
const handleScrollToSelection = useCallback((notice: LintMessage) => {
|
|
||||||
if (!editor.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!notice.range) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { start, end } = notice.range;
|
|
||||||
editor.current.scrollToSelection(start.character, end.character, start.line, end.line);
|
|
||||||
}, [editor]);
|
|
||||||
|
|
||||||
if (!activeApiSpec) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="column tall theme--pane__body">
|
|
||||||
<div className="tall relative overflow-hidden">
|
|
||||||
<CodeEditor
|
|
||||||
key={uniquenessKey}
|
|
||||||
showPrettifyButton
|
|
||||||
ref={editor}
|
|
||||||
lintOptions={{ delay: 1000 }}
|
|
||||||
mode="openapi"
|
|
||||||
defaultValue={contents}
|
|
||||||
onChange={onCodeEditorChange}
|
|
||||||
uniquenessKey={uniquenessKey}
|
|
||||||
/>
|
|
||||||
{contents ? null : (
|
|
||||||
<DesignEmptyState
|
|
||||||
onImport={onImport}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{lintMessages.length > 0 && (
|
|
||||||
<NoticeTable
|
|
||||||
notices={lintMessages}
|
|
||||||
onClick={handleScrollToSelection}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
const RenderPreview: FC = () => {
|
const handleScrollToLintMessage = useCallback(
|
||||||
const activeApiSpec = useSelector(selectActiveApiSpec);
|
(notice: LintMessage) => {
|
||||||
|
if (!editor.current) {
|
||||||
if (!activeApiSpec) {
|
return;
|
||||||
return null;
|
}
|
||||||
}
|
if (!notice.range) {
|
||||||
|
return;
|
||||||
if (!activeApiSpec.contents) {
|
}
|
||||||
return (
|
const { start, end } = notice.range;
|
||||||
<EmptySpaceHelper>
|
editor.current.scrollToSelection(
|
||||||
Documentation for your OpenAPI spec will render here
|
start.character,
|
||||||
</EmptySpaceHelper>
|
end.character,
|
||||||
);
|
start.line,
|
||||||
}
|
end.line
|
||||||
|
);
|
||||||
let swaggerUiSpec: ParsedApiSpec['contents'] | null = null;
|
},
|
||||||
|
[editor]
|
||||||
try {
|
|
||||||
swaggerUiSpec = parseApiSpec(activeApiSpec.contents).contents;
|
|
||||||
} catch (err) { }
|
|
||||||
|
|
||||||
if (!swaggerUiSpec) {
|
|
||||||
swaggerUiSpec = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div id="swagger-ui-wrapper">
|
|
||||||
<ErrorBoundary
|
|
||||||
invalidationKey={activeApiSpec.contents}
|
|
||||||
renderError={() => (
|
|
||||||
<div className="text-left margin pad">
|
|
||||||
<h3>An error occurred while trying to render Swagger UI</h3>
|
|
||||||
<p>
|
|
||||||
This preview will automatically refresh, once you have a valid specification that
|
|
||||||
can be previewed.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<SwaggerUI
|
|
||||||
spec={swaggerUiSpec}
|
|
||||||
supportedSubmitMethods={[
|
|
||||||
'get',
|
|
||||||
'put',
|
|
||||||
'post',
|
|
||||||
'delete',
|
|
||||||
'options',
|
|
||||||
'head',
|
|
||||||
'patch',
|
|
||||||
'trace',
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</ErrorBoundary>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
const RenderPageSidebar: FC<{ editor: RefObject<CodeEditorHandle> }> = ({ editor }) => {
|
const gitVersion = useGitVCSVersion();
|
||||||
const activeApiSpec = useSelector(selectActiveApiSpec);
|
const syncVersion = useActiveApiSpecSyncVCSVersion();
|
||||||
const handleScrollToSelection = useCallback((chStart: number, chEnd: number, lineStart: number, lineEnd: number) => {
|
const uniquenessKey = `${apiSpec?._id}::${apiSpec?.created}::${gitVersion}::${syncVersion}`;
|
||||||
if (!editor.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
editor.current.scrollToSelection(chStart, chEnd, lineStart, lineEnd);
|
|
||||||
}, [editor]);
|
|
||||||
|
|
||||||
if (!activeApiSpec) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!activeApiSpec.contents) {
|
|
||||||
return (
|
|
||||||
<EmptySpaceHelper>
|
|
||||||
A spec navigator will render here
|
|
||||||
</EmptySpaceHelper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ErrorBoundary
|
|
||||||
invalidationKey={activeApiSpec.contents}
|
|
||||||
renderError={() => (
|
|
||||||
<div className="text-left margin pad">
|
|
||||||
<h4>An error occurred while trying to render your spec's navigation.</h4>
|
|
||||||
<p>
|
|
||||||
This navigation will automatically refresh, once you have a valid specification that
|
|
||||||
can be rendered.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<SpecEditorSidebar
|
|
||||||
apiSpec={activeApiSpec}
|
|
||||||
handleSetSelection={handleScrollToSelection}
|
|
||||||
/>
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WrapperDesign: FC = () => {
|
|
||||||
const editor = createRef<CodeEditorHandle>();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarLayout
|
<SidebarLayout
|
||||||
renderPaneOne={<RenderEditor editor={editor} />}
|
renderPageSidebar={
|
||||||
renderPaneTwo={<RenderPreview />}
|
apiSpec.contents ? (
|
||||||
renderPageSidebar={<RenderPageSidebar editor={editor} />}
|
<ErrorBoundary
|
||||||
|
invalidationKey={apiSpec.contents}
|
||||||
|
renderError={() => (
|
||||||
|
<div className="text-left margin pad">
|
||||||
|
<h4>
|
||||||
|
An error occurred while trying to render your spec's
|
||||||
|
navigation.
|
||||||
|
</h4>
|
||||||
|
<p>
|
||||||
|
This navigation will automatically refresh, once you have a
|
||||||
|
valid specification that can be rendered.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<SpecEditorSidebar
|
||||||
|
apiSpec={apiSpec}
|
||||||
|
handleSetSelection={handleScrollToSelection}
|
||||||
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
|
) : (
|
||||||
|
<EmptySpaceHelper>A spec navigator will render here</EmptySpaceHelper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
renderPaneOne={
|
||||||
|
apiSpec ? (
|
||||||
|
<div className="column tall theme--pane__body">
|
||||||
|
<div className="tall relative overflow-hidden">
|
||||||
|
<CodeEditor
|
||||||
|
key={uniquenessKey}
|
||||||
|
showPrettifyButton
|
||||||
|
ref={editor}
|
||||||
|
lintOptions={{ delay: 1000 }}
|
||||||
|
mode="openapi"
|
||||||
|
defaultValue={apiSpec.contents || ''}
|
||||||
|
onChange={onCodeEditorChange}
|
||||||
|
uniquenessKey={uniquenessKey}
|
||||||
|
/>
|
||||||
|
{apiSpec.contents ? null : (
|
||||||
|
<DesignEmptyState
|
||||||
|
onImport={value => {
|
||||||
|
updateApiSpecFetcher.submit(
|
||||||
|
{
|
||||||
|
contents: value,
|
||||||
|
fromSync: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/${ACTIVITY_SPEC}/update`,
|
||||||
|
method: 'post',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{lintMessages.length > 0 && (
|
||||||
|
<NoticeTable
|
||||||
|
notices={lintMessages}
|
||||||
|
onClick={handleScrollToLintMessage}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{apiSpec.contents ? (
|
||||||
|
<Toolbar>
|
||||||
|
<button
|
||||||
|
disabled={lintMessages.length > 0 || generateRequestCollectionFetcher.state !== 'idle'}
|
||||||
|
className="btn btn--compact"
|
||||||
|
onClick={() => {
|
||||||
|
generateRequestCollectionFetcher.submit(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/${ACTIVITY_SPEC}/generate-request-collection`,
|
||||||
|
method: 'post',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{generateRequestCollectionFetcher.state === 'loading' ? (
|
||||||
|
<i className="fa fa-spin fa-spinner" />
|
||||||
|
) : (
|
||||||
|
<i className="fa fa-file-import" />
|
||||||
|
)} Generate Request
|
||||||
|
Collection
|
||||||
|
</button>
|
||||||
|
</Toolbar>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
renderPaneTwo={
|
||||||
|
apiSpec.contents && swaggerSpec ? (
|
||||||
|
<div id="swagger-ui-wrapper">
|
||||||
|
<ErrorBoundary
|
||||||
|
key={uniquenessKey}
|
||||||
|
invalidationKey={apiSpec.contents}
|
||||||
|
renderError={() => (
|
||||||
|
<div className="text-left margin pad">
|
||||||
|
<h3>An error occurred while trying to render Swagger UI</h3>
|
||||||
|
<p>
|
||||||
|
This preview will automatically refresh, once you have a
|
||||||
|
valid specification that can be previewed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<SwaggerUI
|
||||||
|
spec={swaggerSpec}
|
||||||
|
supportedSubmitMethods={[
|
||||||
|
'get',
|
||||||
|
'put',
|
||||||
|
'post',
|
||||||
|
'delete',
|
||||||
|
'options',
|
||||||
|
'head',
|
||||||
|
'patch',
|
||||||
|
'trace',
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<EmptySpaceHelper>
|
||||||
|
Documentation for your OpenAPI spec will render here
|
||||||
|
</EmptySpaceHelper>
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default WrapperDesign;
|
export default Design;
|
||||||
|
Loading…
Reference in New Issue
Block a user