Custom swagger endpoint for docs (#3869)

* custom swagger endpoint
metadata graphql
remove /rest from endpoint

* fixed pseudo scheme creation

* move graphql playground creation to own file, added navbar to change baseurl and token

* add schema switcher, fix changing url not applied, add invalid overlay

* fix link color

* removed path on Graphql Playground, naming fixes subdoc

* - fixed overflow issue Rest docs

* history replace & goBack

* Small fix GraphQL playground broken

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
brendanlaschke
2024-02-08 16:54:20 +01:00
committed by GitHub
parent 719da29795
commit c53b593ea6
13 changed files with 338 additions and 113 deletions

View File

@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react';
import { TbLoader2 } from 'react-icons/tb';
import { useHistory, useLocation } from '@docusaurus/router';
import { TbApi, TbChevronLeft, TbLink } from '@theme/icons';
import { parseJson } from 'nx/src/utils/json';
import tokenForm from '!css-loader!./token-form.css';
@ -7,21 +8,38 @@ import tokenForm from '!css-loader!./token-form.css';
export type TokenFormProps = {
setOpenApiJson?: (json: object) => void;
setToken?: (token: string) => void;
setBaseUrl?: (baseUrl: string) => void;
isTokenValid: boolean;
setIsTokenValid: (boolean) => void;
setLoadingState: (boolean) => void;
subDoc?: string;
};
const TokenForm = ({
setOpenApiJson,
setToken,
setBaseUrl: submitBaseUrl,
isTokenValid,
setIsTokenValid,
subDoc,
setLoadingState,
}: TokenFormProps) => {
const history = useHistory();
const location = useLocation();
const [isLoading, setIsLoading] = useState(false);
const [baseUrl, setBaseUrl] = useState(
parseJson(localStorage.getItem('baseUrl'))?.baseUrl ??
'https://api.twenty.com',
);
const token =
parseJson(localStorage.getItem('TryIt_securitySchemeValues'))?.bearerAuth ??
'';
const updateLoading = (loading: boolean) => {
setIsLoading(loading);
setLoadingState(loading);
};
const updateToken = async (event: React.ChangeEvent<HTMLInputElement>) => {
localStorage.setItem(
'TryIt_securitySchemeValues',
@ -30,22 +48,33 @@ const TokenForm = ({
await submitToken(event.target.value);
};
const validateToken = (openApiJson) => setIsTokenValid(!!openApiJson.tags);
const updateBaseUrl = (baseUrl) => {
setBaseUrl(baseUrl);
submitBaseUrl?.(baseUrl);
localStorage.setItem('baseUrl', JSON.stringify({ baseUrl: baseUrl }));
};
const validateToken = (openApiJson) => {
setIsTokenValid(!!openApiJson.tags);
};
const getJson = async (token: string) => {
setIsLoading(true);
updateLoading(true);
return await fetch('https://api.twenty.com/open-api', {
return await fetch(baseUrl + '/open-api/' + (subDoc ?? 'core'), {
headers: { Authorization: `Bearer ${token}` },
})
.then((res) => res.json())
.then((result) => {
validateToken(result);
setIsLoading(false);
updateLoading(false);
return result;
})
.catch(() => setIsLoading(false));
.catch(() => {
updateLoading(false);
setIsTokenValid(false);
});
};
const submitToken = async (token) => {
@ -60,6 +89,7 @@ const TokenForm = ({
useEffect(() => {
(async () => {
updateBaseUrl(baseUrl);
await submitToken(token);
})();
}, []);
@ -74,46 +104,61 @@ const TokenForm = ({
}, []);
return (
!isTokenValid && (
<div>
<div className="container">
<form className="form">
<label>
To load your playground schema,{' '}
<a
className="link"
href="https://app.twenty.com/settings/developers"
>
generate an API key
</a>{' '}
and paste it here:
</label>
<p>
<input
className={token && !isLoading ? 'input invalid' : 'input'}
type="text"
readOnly={isLoading}
placeholder="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMD..."
defaultValue={token}
onChange={updateToken}
/>
<span
className={`token-invalid ${
(!token || isLoading) && 'not-visible'
}`}
>
Token invalid
</span>
<div className="loader-container">
<TbLoader2
className={`loader ${!isLoading && 'not-visible'}`}
/>
</div>
</p>
</form>
<div className="form-container">
<form className="form">
<div className="backButton" onClick={() => history.goBack()}>
<TbChevronLeft size={18} />
<span>Back</span>
</div>
</div>
)
<div className="inputWrapper">
<div className="inputIcon" title="Api Key">
<TbApi size={20} />
</div>
<input
className={!isTokenValid && !isLoading ? 'input invalid' : 'input'}
type="text"
readOnly={isLoading}
placeholder="API Key"
defaultValue={token}
onChange={updateToken}
/>
</div>
<div className="inputWrapper">
<div className="inputIcon" title="Base URL">
<TbLink size={20} />
</div>
<input
className={'input'}
type="text"
readOnly={isLoading}
placeholder="Base URL"
defaultValue={baseUrl}
onChange={(event) => updateBaseUrl(event.target.value)}
onBlur={() => submitToken(token)}
/>
</div>
{!location.pathname.includes('rest-api') && (
<div className="inputWrapper" style={{ maxWidth: '100px' }}>
<select
className="select"
onChange={(event) =>
history.replace(
'/' +
location.pathname.split('/').at(-2) +
'/' +
event.target.value,
)
}
value={location.pathname.split('/').at(-1)}
>
<option value="core">Core</option>
<option value="metadata">Metadata</option>
</select>
</div>
)}
</form>
</div>
);
};