Fixes ApiSpec content syncing (#4443)

Co-authored-by: Opender Singh <opender.singh@konghq.com>
This commit is contained in:
Dimitri MitropoulosandOpender Singh authored and GitHub committed 2022-02-07 14:00:31 -05:00
1 parent 109eababcf
commit c73d2c1c1d
2 files changed
+64 -43

No files matched your search

@@ -6,7 +6,6 @@ import styled from 'styled-components';
import { documentationLinks } from '../../common/documentation';
import { selectFileOrFolder } from '../../common/select-file-or-folder';
import * as models from '../../models';
import { faint } from '../css/css-in-js';
import { selectActiveApiSpec } from '../redux/selectors';
import { showPrompt } from './modals';
@@ -41,25 +40,10 @@ interface Props {
onUpdateContents: (contents: string) => void;
}
const useUpdateApiSpecContents = () => {
const activeApiSpec = useSelector(selectActiveApiSpec);
return useCallback(async (contents: string) => {
if (!contents) {
return;
}
if (!activeApiSpec) {
return;
}
await models.apiSpec.update(activeApiSpec, { contents });
}, [activeApiSpec]);
};
const ImportSpecButton: FC<Props> = ({ onUpdateContents }) => {
const updateApiSpecContents = useUpdateApiSpecContents();
const handleImportFile = useCallback(async () => {
const { canceled, filePath } = await selectFileOrFolder({
extensions: ['yml', 'yaml'],
extensions: ['yml', 'yaml', 'json'],
itemTypes: ['file'],
});
// Exit if no file selected
@@ -68,8 +52,8 @@ const ImportSpecButton: FC<Props> = ({ onUpdateContents }) => {
}
const contents = String(await fs.promises.readFile(filePath));
await updateApiSpecContents(contents);
}, [updateApiSpecContents]);
onUpdateContents(contents);
}, [onUpdateContents]);
const handleImportUri = useCallback(async () => {
showPrompt({
@@ -83,15 +67,14 @@ const ImportSpecButton: FC<Props> = ({ onUpdateContents }) => {
return;
}
const contents = await response.text();
await updateApiSpecContents(contents);
onUpdateContents(contents);
},
});
}, [updateApiSpecContents, onUpdateContents]);
}, [onUpdateContents]);
const button = (
<StyledButton variant="outlined" bg="surprise" className="margin-left">
Import
Import OpenAPI
<i className="fa fa-caret-down pad-left-sm" />
</StyledButton>
);
@@ -117,21 +100,19 @@ const ImportSpecButton: FC<Props> = ({ onUpdateContents }) => {
const SecondaryAction: FC<Props> = ({ onUpdateContents }) => {
const PETSTORE_EXAMPLE_URI = 'https://gist.githubusercontent.com/gschier/4e2278d5a50b4bbf1110755d9b48a9f9/raw/801c05266ae102bcb9288ab92c60f52d45557425/petstore-spec.yaml';
const updateApiSpecContents = useUpdateApiSpecContents();
const onClick = useCallback(async () => {
const response = await window.fetch(PETSTORE_EXAMPLE_URI);
if (!response) {
return;
}
const contents = await response.text();
await updateApiSpecContents(contents);
onUpdateContents(contents);
}, [updateApiSpecContents, onUpdateContents]);
}, [onUpdateContents]);
return (
<div>
<div>
Or import existing an OpenAPI spec or <ExampleButton onClick={onClick}>start from an example</ExampleButton>
Or import an existing OpenAPI spec or <ExampleButton onClick={onClick}>start from an example</ExampleButton>
</div>
<ImportSpecButton onUpdateContents={onUpdateContents} />
</div>
@@ -1,12 +1,13 @@
import { IRuleResult } from '@stoplight/spectral';
import { Button, Notice, NoticeTable } from 'insomnia-components';
import React, { createRef, FC, Fragment, ReactNode, RefObject, useCallback, useState } from 'react';
import React, { createRef, FC, Fragment, ReactNode, RefObject, useCallback, useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useAsync, useDebounce } from 'react-use';
import { useAsync } from 'react-use';
import styled from 'styled-components';
import SwaggerUI from 'swagger-ui-react';
import { parseApiSpec, ParsedApiSpec } from '../../common/api-specs';
import { debounce } from '../../common/misc';
import { initializeSpectral, isLintError } from '../../common/spectral';
import * as models from '../../models/index';
import { superFaint } from '../css/css-in-js';
@@ -74,24 +75,65 @@ interface LintMessage {
range: IRuleResult['range'];
}
const RenderEditor: FC<{ editor: RefObject<UnconnectedCodeEditor> }> = ({ editor }) => {
const useDesignEmptyState = () => {
const activeApiSpec = useSelector(selectActiveApiSpec);
const contents = activeApiSpec?.contents;
const [forceRefreshCounter, setForceRefreshCounter] = useState(0);
const [lintMessages, setLintMessages] = useState<LintMessage[]>([]);
const contents = activeApiSpec?.contents ?? '';
const [contentsState, setContentsState] = useState(contents);
const [shouldIncrementCounter, setShouldIncrementCounter] = useState(false);
const uniquenessKey = `${forceRefreshCounter}::${activeApiSpec?._id}`;
useEffect(() => {
if (contents && shouldIncrementCounter){
setForceRefreshCounter(forceRefreshCounter => forceRefreshCounter + 1);
setShouldIncrementCounter(false);
}
}, [contents, shouldIncrementCounter]);
useDebounce(async () => {
const onUpdateContents = useCallback((value: string) => {
if (!activeApiSpec) {
return;
}
await models.apiSpec.update({ ...activeApiSpec, contents: contentsState });
setForceRefreshCounter(forceRefreshCounter => forceRefreshCounter + 1);
// eslint-disable-next-line react-hooks/exhaustive-deps -- this is a problem with react-use
}, 500, [contentsState]);
const fn = async () => {
await models.apiSpec.update({ ...activeApiSpec, contents: value });
};
fn();
// Because we can't await activeApiSpec.contents to have propageted to redux, we flip a toggle to decide if we should do something when redux does eventually change
setShouldIncrementCounter(true);
}, [activeApiSpec]);
const emptyStateNode = contents ? null : (
<DesignEmptyState
onUpdateContents={onUpdateContents}
/>
);
const uniquenessKey = `${forceRefreshCounter}::${activeApiSpec?._id}`;
return { uniquenessKey, emptyStateNode };
};
const RenderEditor: FC<{ editor: RefObject<UnconnectedCodeEditor> }> = ({ editor }) => {
const activeApiSpec = useSelector(selectActiveApiSpec);
const [lintMessages, setLintMessages] = useState<LintMessage[]>([]);
const contents = activeApiSpec?.contents ?? '';
const { uniquenessKey, emptyStateNode } = useDesignEmptyState();
const onCodeEditorChange = useMemo(() => {
const handler = (contents: string) => {
const fn = async () => {
if (!activeApiSpec) {
return;
}
await models.apiSpec.update({ ...activeApiSpec, contents });
};
fn();
};
return debounce(handler, 500);
}, [activeApiSpec]);
useAsync(async () => {
// Lint only if spec has content
@@ -136,13 +178,11 @@ const RenderEditor: FC<{ editor: RefObject<UnconnectedCodeEditor> }> = ({ editor
ref={editor}
lintOptions={{ delay: 1000 }}
mode="openapi"
defaultValue={contentsState}
onChange={setContentsState}
defaultValue={contents}
onChange={onCodeEditorChange}
uniquenessKey={uniquenessKey}
/>
<DesignEmptyState
onUpdateContents={setContentsState}
/>
{emptyStateNode}
</div>
{lintMessages.length > 0 && (
<NoticeTable