mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
feat: replacement of the legacy rest endpoint details page with the new one
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9821 Co-authored-by: Varun Choudhary <68095256+Varun-Choudhary@users.noreply.github.com> GitOrigin-RevId: 7b50fc7d429c865678837e2ac9391c7b4973fa56
This commit is contained in:
parent
6853688e20
commit
5897262d75
@ -198,7 +198,7 @@ class GraphiQLWrapper extends Component {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dispatch(_push('/api/rest/create'));
|
dispatch(_push('/api/rest/create?from=graphiql'));
|
||||||
};
|
};
|
||||||
|
|
||||||
const _toggleCacheDirective = () => {
|
const _toggleCacheDirective = () => {
|
||||||
|
@ -15,7 +15,8 @@ import globals from '../../../../../Globals';
|
|||||||
import { FaArrowRight, FaMagic } from 'react-icons/fa';
|
import { FaArrowRight, FaMagic } from 'react-icons/fa';
|
||||||
import { IndicatorCard } from '../../../../../new-components/IndicatorCard';
|
import { IndicatorCard } from '../../../../../new-components/IndicatorCard';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { Link } from 'react-router';
|
import { openInGraphiQL } from '../../../../../features/RestEndpoints/components/RestEndpointDetails/utils';
|
||||||
|
import { Analytics } from '../../../../../features/Analytics';
|
||||||
|
|
||||||
const editorOptions = {
|
const editorOptions = {
|
||||||
minLines: 10,
|
minLines: 10,
|
||||||
@ -131,11 +132,19 @@ export const RestEndpointForm: React.FC<RestEndpointFormProps> = ({
|
|||||||
description="Support GraphQL queries and mutations."
|
description="Support GraphQL queries and mutations."
|
||||||
editorOptions={editorOptions}
|
editorOptions={editorOptions}
|
||||||
/>
|
/>
|
||||||
<div className="text-sm absolute top-6 right-0 mt-2 mr-2">
|
<div className="text-sm absolute top-3 right-0 mt-2">
|
||||||
<Link to="/api/api-explorer?mode=rest">
|
<Analytics name="api-tab-rest-endpoint-form-graphiql-link">
|
||||||
{request ? 'Test it in ' : 'Import from '} GraphiQL{' '}
|
<Button
|
||||||
<FaArrowRight />
|
icon={<FaArrowRight />}
|
||||||
</Link>
|
iconPosition="end"
|
||||||
|
size="sm"
|
||||||
|
onClick={e => {
|
||||||
|
openInGraphiQL(request);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{request ? 'Test it in ' : 'Import from '} GraphiQL{' '}
|
||||||
|
</Button>
|
||||||
|
</Analytics>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<InputField name="name" label="Name" placeholder="Name" />
|
<InputField name="name" label="Name" placeholder="Name" />
|
||||||
@ -159,8 +168,8 @@ export const RestEndpointForm: React.FC<RestEndpointFormProps> = ({
|
|||||||
customIcon={FaMagic}
|
customIcon={FaMagic}
|
||||||
headline="No Parameterized variable specification needed"
|
headline="No Parameterized variable specification needed"
|
||||||
>
|
>
|
||||||
All parameterized variables in your GraphQL query will be
|
All parameterized variables (e.g. {prependLabel}example/:id) in your
|
||||||
auto-specifed in the URL
|
GraphQL query will be auto-specifed in the URL
|
||||||
</IndicatorCard>
|
</IndicatorCard>
|
||||||
</div>
|
</div>
|
||||||
<CheckboxesField
|
<CheckboxesField
|
||||||
@ -179,14 +188,18 @@ export const RestEndpointForm: React.FC<RestEndpointFormProps> = ({
|
|||||||
<Button type="button" onClick={onCancel} disabled={loading}>
|
<Button type="button" onClick={onCancel} disabled={loading}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Analytics name={`api-tab-rest-endpoint-form-${mode}-button`}>
|
||||||
type="submit"
|
<Button
|
||||||
mode="primary"
|
type="submit"
|
||||||
isLoading={loading}
|
mode="primary"
|
||||||
loadingText={{ create: 'Creating...', edit: 'Modifying ..' }[mode]}
|
isLoading={loading}
|
||||||
>
|
loadingText={
|
||||||
{{ create: 'Create', edit: 'Modify' }[mode]}
|
{ create: 'Creating...', edit: 'Modifying ..' }[mode]
|
||||||
</Button>
|
}
|
||||||
|
>
|
||||||
|
{{ create: 'Create', edit: 'Modify' }[mode]}
|
||||||
|
</Button>
|
||||||
|
</Analytics>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { RouteComponentProps } from 'react-router';
|
import { RouteComponentProps } from 'react-router';
|
||||||
import { connect, ConnectedProps } from 'react-redux';
|
import { connect, ConnectedProps } from 'react-redux';
|
||||||
|
import queryString from 'query-string';
|
||||||
import {
|
import {
|
||||||
Analytics,
|
Analytics,
|
||||||
REDACT_EVERYTHING,
|
REDACT_EVERYTHING,
|
||||||
} from '../../../../../features/Analytics';
|
} from '../../../../../features/Analytics';
|
||||||
|
import { parse, print } from 'graphql';
|
||||||
import {
|
import {
|
||||||
AllowedRESTMethods,
|
AllowedRESTMethods,
|
||||||
RestEndpointEntry,
|
RestEndpointEntry,
|
||||||
@ -84,8 +86,14 @@ const useRestEndpointFormStateForCreation: RestEndpointFormStateHook = (
|
|||||||
formSubmitHandler: RestEndpointFormSubmitHandler;
|
formSubmitHandler: RestEndpointFormSubmitHandler;
|
||||||
} => {
|
} => {
|
||||||
const formState: RestEndpointFormState = {};
|
const formState: RestEndpointFormState = {};
|
||||||
formState.request = getLSItem(LS_KEYS.graphiqlQuery) ?? undefined;
|
try {
|
||||||
|
const parsedQuery = queryString.parseUrl(window.location.href);
|
||||||
|
if (parsedQuery.query?.from === 'graphiql') {
|
||||||
|
formState.request = print(parse(getLSItem(LS_KEYS.graphiqlQuery) ?? ''));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
return { formState, formSubmitHandler: createEndpoint };
|
return { formState, formSubmitHandler: createEndpoint };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,6 +12,12 @@ type RequestHeadersProps = {
|
|||||||
|
|
||||||
export const RequestHeaders = (props: RequestHeadersProps) => {
|
export const RequestHeaders = (props: RequestHeadersProps) => {
|
||||||
const { headers, setHeaders } = props;
|
const { headers, setHeaders } = props;
|
||||||
|
|
||||||
|
const showRemove = !!(
|
||||||
|
headers.length > 1 ||
|
||||||
|
headers?.[0]?.name ||
|
||||||
|
headers?.[0]?.value
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<Collapsible
|
<Collapsible
|
||||||
defaultOpen
|
defaultOpen
|
||||||
@ -33,7 +39,7 @@ export const RequestHeaders = (props: RequestHeadersProps) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="font-semibold text-muted mb-4">Headers List</div>
|
<div className="font-semibold text-muted mb-4">Headers List</div>
|
||||||
<CardedTable
|
<CardedTable
|
||||||
showActionCell
|
showActionCell={showRemove}
|
||||||
columns={[
|
columns={[
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={headers.every(header => header.selected)}
|
checked={headers.every(header => header.selected)}
|
||||||
@ -49,72 +55,74 @@ export const RequestHeaders = (props: RequestHeadersProps) => {
|
|||||||
'Name',
|
'Name',
|
||||||
'Value',
|
'Value',
|
||||||
]}
|
]}
|
||||||
data={headers.map((header, i) => [
|
data={headers.map((header, i) =>
|
||||||
<Checkbox
|
[
|
||||||
checked={header.selected}
|
<Checkbox
|
||||||
onCheckedChange={checked =>
|
checked={header.selected}
|
||||||
setHeaders(
|
onCheckedChange={checked =>
|
||||||
headers.map(h => ({
|
setHeaders(
|
||||||
...h,
|
headers.map(h => ({
|
||||||
selected: h.name === header.name ? !!checked : h.selected,
|
...h,
|
||||||
}))
|
selected: h.name === header.name ? !!checked : h.selected,
|
||||||
)
|
}))
|
||||||
}
|
)
|
||||||
/>,
|
}
|
||||||
<input
|
/>,
|
||||||
data-testid={`header-name-${i}`}
|
<input
|
||||||
placeholder="Enter name..."
|
data-testid={`header-name-${i}`}
|
||||||
className="w-full"
|
placeholder="Enter name..."
|
||||||
value={header.name}
|
className="w-full"
|
||||||
onChange={e =>
|
value={header.name}
|
||||||
setHeaders(
|
onChange={e =>
|
||||||
headers.map(h => ({
|
setHeaders(
|
||||||
...h,
|
headers.map(h => ({
|
||||||
name: h.name === header.name ? e.target.value : h.name,
|
...h,
|
||||||
}))
|
name: h.name === header.name ? e.target.value : h.name,
|
||||||
)
|
}))
|
||||||
}
|
)
|
||||||
/>,
|
}
|
||||||
<input
|
/>,
|
||||||
data-testid={`header-value-${i}`}
|
<input
|
||||||
placeholder="Enter value..."
|
data-testid={`header-value-${i}`}
|
||||||
className="w-full"
|
placeholder="Enter value..."
|
||||||
value={header.value}
|
className="w-full border-0"
|
||||||
onChange={e =>
|
type={
|
||||||
setHeaders(
|
header.name === 'x-hasura-admin-secret' ? 'password' : 'text'
|
||||||
headers.map(h => ({
|
}
|
||||||
...h,
|
value={header.value}
|
||||||
value: h.name === header.name ? e.target.value : h.value,
|
onChange={e =>
|
||||||
}))
|
setHeaders(
|
||||||
)
|
headers.map(h => ({
|
||||||
}
|
...h,
|
||||||
/>,
|
value: h.name === header.name ? e.target.value : h.value,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>,
|
||||||
|
showRemove && (
|
||||||
|
<Button
|
||||||
|
mode="destructive"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
const newHeaders = headers
|
||||||
|
.slice(0, i)
|
||||||
|
.concat(headers.slice(i + 1));
|
||||||
|
|
||||||
(headers.length > 1 ||
|
if (newHeaders.length === 0) {
|
||||||
headers?.[0]?.name ||
|
newHeaders.push({
|
||||||
headers?.[0]?.value) && (
|
name: '',
|
||||||
<Button
|
value: '',
|
||||||
mode="destructive"
|
selected: true,
|
||||||
size="sm"
|
});
|
||||||
onClick={() => {
|
}
|
||||||
const newHeaders = headers
|
setHeaders(newHeaders);
|
||||||
.slice(0, i)
|
}}
|
||||||
.concat(headers.slice(i + 1));
|
>
|
||||||
|
Remove
|
||||||
if (newHeaders.length === 0) {
|
</Button>
|
||||||
newHeaders.push({
|
),
|
||||||
name: '',
|
].filter(Boolean)
|
||||||
value: '',
|
)}
|
||||||
selected: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setHeaders(newHeaders);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
),
|
|
||||||
])}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import AceEditor from 'react-ace';
|
||||||
import {
|
import {
|
||||||
CheckboxesField,
|
CheckboxesField,
|
||||||
CodeEditorField,
|
CodeEditorField,
|
||||||
@ -10,13 +11,15 @@ import {
|
|||||||
import { Button } from '../../../../new-components/Button';
|
import { Button } from '../../../../new-components/Button';
|
||||||
import { FaArrowRight, FaPlay } from 'react-icons/fa';
|
import { FaArrowRight, FaPlay } from 'react-icons/fa';
|
||||||
import { useRestEndpoint } from '../../hooks/useRestEndpoint';
|
import { useRestEndpoint } from '../../hooks/useRestEndpoint';
|
||||||
import { getSessionVarsFromLS } from '../../../../components/Common/ConfigureTransformation/utils';
|
|
||||||
import { parseQueryVariables } from '../../../../components/Services/ApiExplorer/Rest/utils';
|
import { parseQueryVariables } from '../../../../components/Services/ApiExplorer/Rest/utils';
|
||||||
import { useRestEndpointRequest } from '../../hooks/useRestEndpointRequest';
|
import { useRestEndpointRequest } from '../../hooks/useRestEndpointRequest';
|
||||||
import { IndicatorCard } from '../../../../new-components/IndicatorCard';
|
|
||||||
import { RequestHeaders } from './RequestHeaders';
|
import { RequestHeaders } from './RequestHeaders';
|
||||||
import { Variables } from './Variables';
|
import { Variables } from './Variables';
|
||||||
import { AllowedRESTMethods } from '../../../../metadata/types';
|
import { AllowedRESTMethods } from '../../../../metadata/types';
|
||||||
|
import { openInGraphiQL } from './utils';
|
||||||
|
import { LS_KEYS, getLSItem } from '../../../../utils';
|
||||||
|
import { hasuraToast } from '../../../../new-components/Toasts';
|
||||||
|
import { Analytics } from '../../../Analytics';
|
||||||
|
|
||||||
export type Variable = Exclude<
|
export type Variable = Exclude<
|
||||||
ReturnType<typeof parseQueryVariables>,
|
ReturnType<typeof parseQueryVariables>,
|
||||||
@ -66,10 +69,37 @@ const validationSchema = z.object({
|
|||||||
request: z.string().min(1, { message: 'Please add a GraphQL query' }),
|
request: z.string().min(1, { message: 'Please add a GraphQL query' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const getInitialHeaders = (): Header[] => {
|
||||||
|
const headers = getLSItem(LS_KEYS.apiExplorerConsoleGraphQLHeaders);
|
||||||
|
|
||||||
|
if (headers) {
|
||||||
|
return JSON.parse(headers).map(
|
||||||
|
(header: { key: string; value: string; isDisabled: boolean }) => {
|
||||||
|
const value =
|
||||||
|
header.key === 'x-hasura-admin-secret'
|
||||||
|
? window.__env.adminSecret || getLSItem(LS_KEYS.consoleAdminSecret)
|
||||||
|
: header.value;
|
||||||
|
return {
|
||||||
|
name: header.key,
|
||||||
|
value,
|
||||||
|
selected: !header.isDisabled,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: '',
|
||||||
|
value: '',
|
||||||
|
selected: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
export const RestEndpointDetails = (props: RestEndpointDetailsProps) => {
|
export const RestEndpointDetails = (props: RestEndpointDetailsProps) => {
|
||||||
const endpoint = useRestEndpoint(props.name);
|
const endpoint = useRestEndpoint(props.name);
|
||||||
|
|
||||||
const initialHeaders = getSessionVarsFromLS();
|
const initialHeaders = getInitialHeaders();
|
||||||
|
|
||||||
const [headers, setHeaders] = React.useState(
|
const [headers, setHeaders] = React.useState(
|
||||||
initialHeaders.map(header => ({
|
initialHeaders.map(header => ({
|
||||||
@ -80,11 +110,7 @@ export const RestEndpointDetails = (props: RestEndpointDetailsProps) => {
|
|||||||
|
|
||||||
const [variables, setVariables] = React.useState<Variable[]>([]);
|
const [variables, setVariables] = React.useState<Variable[]>([]);
|
||||||
|
|
||||||
const { data, refetch, isFetching, error } = useRestEndpointRequest(
|
const { data, mutate, isLoading } = useRestEndpointRequest();
|
||||||
endpoint?.endpoint,
|
|
||||||
headers,
|
|
||||||
variables
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
Form,
|
Form,
|
||||||
@ -129,10 +155,17 @@ export const RestEndpointDetails = (props: RestEndpointDetailsProps) => {
|
|||||||
label="GraphQL Request"
|
label="GraphQL Request"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="text-sm absolute top-6 right-0 mt-2 mr-2">
|
<div className="text-sm absolute top-3 right-0 mt-2">
|
||||||
<a href="/api/api-explorer">
|
<Button
|
||||||
Test it in GraphiQL <FaArrowRight />
|
icon={<FaArrowRight />}
|
||||||
</a>
|
iconPosition="end"
|
||||||
|
size="sm"
|
||||||
|
onClick={e => {
|
||||||
|
openInGraphiQL(endpoint.query.query);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Test it in GraphiQL
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Textarea
|
<Textarea
|
||||||
@ -163,26 +196,64 @@ export const RestEndpointDetails = (props: RestEndpointDetailsProps) => {
|
|||||||
)}
|
)}
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
/>
|
/>
|
||||||
<RequestHeaders headers={headers} setHeaders={setHeaders} />
|
|
||||||
<Variables variables={variables} setVariables={setVariables} />
|
<Variables variables={variables} setVariables={setVariables} />
|
||||||
|
|
||||||
|
<RequestHeaders headers={headers} setHeaders={setHeaders} />
|
||||||
|
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
{error && (
|
<Analytics name="api-tab-rest-endpoint-details-run-request">
|
||||||
<IndicatorCard status="negative" headline="An error has occured">
|
<Button
|
||||||
{JSON.stringify(error, null, 2)}
|
disabled={!endpoint?.endpoint}
|
||||||
</IndicatorCard>
|
isLoading={isLoading}
|
||||||
)}
|
icon={<FaPlay />}
|
||||||
<Button
|
onClick={() => {
|
||||||
disabled={!endpoint?.endpoint}
|
mutate(
|
||||||
isLoading={isFetching}
|
{
|
||||||
icon={<FaPlay />}
|
endpoint: endpoint?.endpoint,
|
||||||
onClick={() => {
|
headers,
|
||||||
refetch();
|
variables,
|
||||||
}}
|
},
|
||||||
mode="primary"
|
{
|
||||||
>
|
onSuccess: data => {
|
||||||
Run Request
|
hasuraToast({
|
||||||
</Button>
|
title: 'Success',
|
||||||
|
message: 'Request successful',
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: error => {
|
||||||
|
hasuraToast({
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Request failed',
|
||||||
|
type: 'error',
|
||||||
|
children: (
|
||||||
|
<div className="overflow-hidden">
|
||||||
|
<AceEditor
|
||||||
|
theme="github"
|
||||||
|
setOptions={{
|
||||||
|
minLines: 1,
|
||||||
|
maxLines: Infinity,
|
||||||
|
showGutter: false,
|
||||||
|
useWorker: false,
|
||||||
|
}}
|
||||||
|
value={JSON.stringify(error, null, 2)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
mode="primary"
|
||||||
|
>
|
||||||
|
Run Request
|
||||||
|
</Button>
|
||||||
|
</Analytics>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { RestEndpointDetails } from './RestEndpointDetails';
|
||||||
|
import { Button } from '../../../../new-components/Button';
|
||||||
|
import {
|
||||||
|
BreadcrumbItem,
|
||||||
|
Breadcrumbs,
|
||||||
|
} from '../../../../new-components/Breadcrumbs/Breadcrumbs';
|
||||||
|
import { RouteComponentProps, browserHistory } from 'react-router';
|
||||||
|
|
||||||
|
interface RestEndpointDetailsPageProps {
|
||||||
|
params: RouteComponentProps<{ name: string }, unknown>['params'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RestEndpointDetailsPage = (
|
||||||
|
props: RestEndpointDetailsPageProps
|
||||||
|
) => {
|
||||||
|
const name = props.params.name;
|
||||||
|
|
||||||
|
const breadcrumbs: BreadcrumbItem[] = [
|
||||||
|
{
|
||||||
|
title: 'Rest Endpoints',
|
||||||
|
onClick: () => browserHistory.push('/api/rest'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: name,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-9">
|
||||||
|
<div className="-ml-2 mb-2">
|
||||||
|
<Breadcrumbs items={breadcrumbs} />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="pb-2">
|
||||||
|
<h1 className="text-xl font-semibold mb-0">{name}</h1>
|
||||||
|
<p className="text-gray-500 mt-0">Test your REST endpoint</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className="ml-auto"
|
||||||
|
onClick={() => browserHistory.push(`/api/rest/edit/${name}`)}
|
||||||
|
>
|
||||||
|
Edit Endpoint
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<hr className="mb-4 mt-2 -mx-9" />
|
||||||
|
<RestEndpointDetails name={name} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,3 +1,4 @@
|
|||||||
|
import { supportedNumericTypes } from '../../../../components/Services/ApiExplorer/Rest/utils';
|
||||||
import { CardedTable } from '../../../../new-components/CardedTable';
|
import { CardedTable } from '../../../../new-components/CardedTable';
|
||||||
import { Collapsible } from '../../../../new-components/Collapsible';
|
import { Collapsible } from '../../../../new-components/Collapsible';
|
||||||
import { Variable } from './RestEndpointDetails';
|
import { Variable } from './RestEndpointDetails';
|
||||||
@ -9,6 +10,11 @@ type VariablesProps = {
|
|||||||
|
|
||||||
export const Variables = (props: VariablesProps) => {
|
export const Variables = (props: VariablesProps) => {
|
||||||
const { variables, setVariables } = props;
|
const { variables, setVariables } = props;
|
||||||
|
|
||||||
|
if (variables.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible
|
<Collapsible
|
||||||
defaultOpen
|
defaultOpen
|
||||||
@ -26,9 +32,14 @@ export const Variables = (props: VariablesProps) => {
|
|||||||
<span className="font-semibold text-muted">{variable.name}</span>,
|
<span className="font-semibold text-muted">{variable.name}</span>,
|
||||||
variable.type,
|
variable.type,
|
||||||
<input
|
<input
|
||||||
|
type={
|
||||||
|
supportedNumericTypes.includes(variable.type)
|
||||||
|
? 'number'
|
||||||
|
: 'text'
|
||||||
|
}
|
||||||
data-testid={`variable-${variable.name}`}
|
data-testid={`variable-${variable.name}`}
|
||||||
placeholder="Enter value..."
|
placeholder="Enter value..."
|
||||||
className="w-full font-normal text-muted"
|
className="w-full font-normal text-muted border-0"
|
||||||
value={variable.value}
|
value={variable.value}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
setVariables(
|
setVariables(
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
import { browserHistory } from 'react-router';
|
||||||
|
import { LS_KEYS, setLSItem } from '../../../../utils';
|
||||||
|
|
||||||
|
export const openInGraphiQL = (query: string) => {
|
||||||
|
if (query) {
|
||||||
|
setLSItem(LS_KEYS.graphiqlQuery, query);
|
||||||
|
}
|
||||||
|
browserHistory.push('/api/api-explorer?mode=rest');
|
||||||
|
};
|
@ -1,28 +1,43 @@
|
|||||||
import { RestEndpointEntry } from '../../../metadata/types';
|
import { RestEndpointEntry } from '../../../metadata/types';
|
||||||
import { Header, Variable } from '../components/RestEndpointDetails';
|
import { Header, Variable } from '../components/RestEndpointDetails';
|
||||||
import { Api } from '../../../hooks/apiUtils';
|
|
||||||
import {
|
import {
|
||||||
getCurrentPageHost,
|
getCurrentPageHost,
|
||||||
getValueWithType,
|
getValueWithType,
|
||||||
} from '../../../components/Services/ApiExplorer/Rest/utils';
|
} from '../../../components/Services/ApiExplorer/Rest/utils';
|
||||||
import { useQuery } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
|
|
||||||
type QueryKey = [
|
interface IApiArgs {
|
||||||
string,
|
headers: Record<string, string>;
|
||||||
{
|
url: string;
|
||||||
|
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
||||||
|
body?: Record<any, any> | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchApi<T = unknown, V = T>(args: IApiArgs): Promise<V> {
|
||||||
|
const { headers, url, method, body } = args;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers,
|
||||||
|
method,
|
||||||
|
body: typeof body !== 'string' ? JSON.stringify(body) : body,
|
||||||
|
});
|
||||||
|
const contentType = response.headers.get('Content-Type');
|
||||||
|
const isResponseJson = `${contentType}`.includes('application/json');
|
||||||
|
if (response.ok) {
|
||||||
|
if (!isResponseJson) {
|
||||||
|
return (await response.text()) as unknown as V;
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
throw response.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useRestEndpointRequest = () => {
|
||||||
|
const makeRequest = async (options: {
|
||||||
endpoint: RestEndpointEntry | undefined;
|
endpoint: RestEndpointEntry | undefined;
|
||||||
headers: Header[];
|
headers: Header[];
|
||||||
variables: Variable[];
|
variables: Variable[];
|
||||||
}
|
}) => {
|
||||||
];
|
const { endpoint, headers, variables } = options;
|
||||||
|
|
||||||
export const useRestEndpointRequest = (
|
|
||||||
endpoint: RestEndpointEntry | undefined,
|
|
||||||
headers: Header[],
|
|
||||||
variables: Variable[]
|
|
||||||
) => {
|
|
||||||
const makeRequest = ({ queryKey }: { queryKey: QueryKey }) => {
|
|
||||||
const [, { endpoint, headers, variables }] = queryKey;
|
|
||||||
|
|
||||||
const selectedHeaders = headers
|
const selectedHeaders = headers
|
||||||
.filter(h => !!h.name && h.selected)
|
.filter(h => !!h.name && h.selected)
|
||||||
@ -49,13 +64,16 @@ export const useRestEndpointRequest = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Api.base({
|
return fetchApi({
|
||||||
method: endpoint.methods?.[0],
|
method: endpoint.methods?.[0],
|
||||||
url: `${getCurrentPageHost()}/api/rest/${url}`,
|
url: `${getCurrentPageHost()}/api/rest/${url}`,
|
||||||
body: bodyVariables.reduce((acc, curr) => {
|
body:
|
||||||
acc[curr.name] = curr.value;
|
bodyVariables.length > 0
|
||||||
return acc;
|
? bodyVariables.reduce((acc, curr) => {
|
||||||
}, {} as Record<string, unknown>),
|
acc[curr.name] = curr.value;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, unknown>)
|
||||||
|
: undefined,
|
||||||
headers: selectedHeaders.reduce((acc, curr) => {
|
headers: selectedHeaders.reduce((acc, curr) => {
|
||||||
acc[curr.name] = curr.value;
|
acc[curr.name] = curr.value;
|
||||||
return acc;
|
return acc;
|
||||||
@ -63,13 +81,5 @@ export const useRestEndpointRequest = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return useQuery({
|
return useMutation(makeRequest);
|
||||||
queryKey: [
|
|
||||||
'rest-endpoint-request ',
|
|
||||||
{ endpoint, headers, variables },
|
|
||||||
] as QueryKey,
|
|
||||||
queryFn: makeRequest,
|
|
||||||
enabled: false,
|
|
||||||
retry: 1,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
@ -28,13 +28,13 @@ import { SupportContainer } from './components/Services/Support/SupportContainer
|
|||||||
import HelpPage from './components/Services/Support/HelpPage';
|
import HelpPage from './components/Services/Support/HelpPage';
|
||||||
import FormRestView from './components/Services/ApiExplorer/Rest/Form';
|
import FormRestView from './components/Services/ApiExplorer/Rest/Form';
|
||||||
import RestListView from './components/Services/ApiExplorer/Rest/List';
|
import RestListView from './components/Services/ApiExplorer/Rest/List';
|
||||||
import DetailsView from './components/Services/ApiExplorer/Rest/Details';
|
|
||||||
import { HerokuCallbackHandler } from './components/Services/Data/DataSources/CreateDataSource/Heroku/TempCallback';
|
import { HerokuCallbackHandler } from './components/Services/Data/DataSources/CreateDataSource/Heroku/TempCallback';
|
||||||
import { NeonCallbackHandler } from './components/Services/Data/DataSources/CreateDataSource/Neon/TempCallback';
|
import { NeonCallbackHandler } from './components/Services/Data/DataSources/CreateDataSource/Neon/TempCallback';
|
||||||
import InsecureDomains from './components/Services/Settings/InsercureDomains/AllowInsecureDomains';
|
import InsecureDomains from './components/Services/Settings/InsercureDomains/AllowInsecureDomains';
|
||||||
import AuthContainer from './components/Services/Auth/AuthContainer';
|
import AuthContainer from './components/Services/Auth/AuthContainer';
|
||||||
import { FeatureFlags } from './features/FeatureFlags';
|
import { FeatureFlags } from './features/FeatureFlags';
|
||||||
import { AllowListDetail } from './components/Services/AllowList';
|
import { AllowListDetail } from './components/Services/AllowList';
|
||||||
|
import { RestEndpointDetailsPage } from './features/RestEndpoints/components/RestEndpointDetails/RestEndpointDetailsPage';
|
||||||
|
|
||||||
const routes = store => {
|
const routes = store => {
|
||||||
// load hasuraCliServer migration status
|
// load hasuraCliServer migration status
|
||||||
@ -121,7 +121,7 @@ const routes = store => {
|
|||||||
<IndexRedirect to="list" />
|
<IndexRedirect to="list" />
|
||||||
<Route path="create" component={FormRestView} />
|
<Route path="create" component={FormRestView} />
|
||||||
<Route path="list" component={RestListView} />
|
<Route path="list" component={RestListView} />
|
||||||
<Route path="details/:name" component={DetailsView} />
|
<Route path="details/:name" component={RestEndpointDetailsPage} />
|
||||||
<Route path="edit/:name" component={FormRestView} />
|
<Route path="edit/:name" component={FormRestView} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="allow-list">
|
<Route path="allow-list">
|
||||||
|
@ -38,7 +38,6 @@ import {
|
|||||||
ApiContainer,
|
ApiContainer,
|
||||||
CreateRestView,
|
CreateRestView,
|
||||||
RestListView,
|
RestListView,
|
||||||
DetailsView,
|
|
||||||
InheritedRolesContainer,
|
InheritedRolesContainer,
|
||||||
ApiLimits,
|
ApiLimits,
|
||||||
IntrospectionOptions,
|
IntrospectionOptions,
|
||||||
@ -54,6 +53,7 @@ import {
|
|||||||
SingleSignOnPage,
|
SingleSignOnPage,
|
||||||
SchemaRegistryContainer,
|
SchemaRegistryContainer,
|
||||||
SchemaDetailsView,
|
SchemaDetailsView,
|
||||||
|
RestEndpointDetailsPage,
|
||||||
} from '@hasura/console-legacy-ce';
|
} from '@hasura/console-legacy-ce';
|
||||||
|
|
||||||
import AccessDeniedComponent from './components/AccessDenied/AccessDenied';
|
import AccessDeniedComponent from './components/AccessDenied/AccessDenied';
|
||||||
@ -344,7 +344,7 @@ const routes = store => {
|
|||||||
<IndexRedirect to="list" />
|
<IndexRedirect to="list" />
|
||||||
<Route path="create" component={CreateRestView} />
|
<Route path="create" component={CreateRestView} />
|
||||||
<Route path="list" component={RestListView} />
|
<Route path="list" component={RestListView} />
|
||||||
<Route path="details/:name" component={DetailsView} />
|
<Route path="details/:name" component={RestEndpointDetailsPage} />
|
||||||
<Route path="edit/:name" component={CreateRestView} />
|
<Route path="edit/:name" component={CreateRestView} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="allow-list">
|
<Route path="allow-list">
|
||||||
|
Loading…
Reference in New Issue
Block a user