console: unify webhook handler UX for action, remote schemas and events
[GS-397]: https://hasurahq.atlassian.net/browse/GS-397?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7796 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Sean Park-Ross <94021366+seanparkross@users.noreply.github.com> GitOrigin-RevId: 620aea50e7d1b45835a5996246f46017b2ba5904
@ -78,24 +78,24 @@ X-Hasura-Role: admin
|
||||
|
||||
### Args syntax {#metadata-pg-create-event-trigger-syntax}
|
||||
|
||||
| Key | Required | Schema | Description |
|
||||
| ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
|
||||
| name | true | [TriggerName](/api-reference/syntax-defs.mdx#triggername) | Name of the Event Trigger |
|
||||
| table | true | [QualifiedTable](/api-reference/syntax-defs.mdx#qualifiedtable) | Object with table name and schema |
|
||||
| source | false | [SourceName](/api-reference/syntax-defs.mdx#sourcename) | Name of the source database of the table (default: `default`) |
|
||||
| webhook | false | String | Full url of webhook (\*) |
|
||||
| webhook_from_env | false | String | Environment variable name of webhook (must exist at boot time) (\*) |
|
||||
| insert | false | [OperationSpec](/api-reference/syntax-defs.mdx#operationspec) | Specification for insert operation |
|
||||
| update | false | [OperationSpec](/api-reference/syntax-defs.mdx#operationspec) | Specification for update operation |
|
||||
| delete | false | [OperationSpec](/api-reference/syntax-defs.mdx#operationspec) | Specification for delete operation |
|
||||
| headers | false | [ [HeaderFromValue](/api-reference/syntax-defs.mdx#headerfromvalue) \| [HeaderFromEnv](/api-reference/syntax-defs.mdx#headerfromenv) ] | List of headers to be sent with the webhook |
|
||||
| retry_conf | false | [RetryConf](/api-reference/syntax-defs.mdx#retryconf) | Retry configuration if event delivery fails |
|
||||
| replace | false | Boolean | If set to true, the Event Trigger is replaced with the new definition |
|
||||
| enable_manual | false | Boolean | If set to true, the Event Trigger can be invoked manually |
|
||||
| request_transform | false | [RequestTransformation](/api-reference/syntax-defs.mdx#requesttransformation) | Attaches a Request Transformation to the Event Trigger. |
|
||||
| response_transform | false | [ResponseTransformation](/api-reference/syntax-defs.mdx#responsetransformation) | Attaches a Request Transformation to the Event Trigger. |
|
||||
| cleanup_config | false | [AutoEventTriggerCleanupConfig](/api-reference/syntax-defs.mdx#autoeventtriggercleanupconfig) | Cleanup config for the auto cleanup process (EE/Cloud only). |
|
||||
| trigger_on_replication | false | Boolean | Specification for enabling/disabling the Event Trigger during logical replication |
|
||||
| Key | Required | Schema | Description |
|
||||
|------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------|
|
||||
| name | true | [TriggerName](/api-reference/syntax-defs.mdx#triggername) | Name of the Event Trigger |
|
||||
| table | true | [QualifiedTable](/api-reference/syntax-defs.mdx#qualifiedtable) | Object with table name and schema |
|
||||
| source | false | [SourceName](/api-reference/syntax-defs.mdx#sourcename) | Name of the source database of the table (default: `default`) |
|
||||
| webhook | false | [WebhookURL](/api-reference/syntax-defs.mdx#webhookurl) | Event Trigger webhook URL |
|
||||
| webhook_from_env | false | String | Environment variable name of webhook (Deprecated in favour of [WebhookURL](/api-reference/syntax-defs.mdx#webhookurl)) |
|
||||
| insert | false | [OperationSpec](/api-reference/syntax-defs.mdx#operationspec) | Specification for insert operation |
|
||||
| update | false | [OperationSpec](/api-reference/syntax-defs.mdx#operationspec) | Specification for update operation |
|
||||
| delete | false | [OperationSpec](/api-reference/syntax-defs.mdx#operationspec) | Specification for delete operation |
|
||||
| headers | false | [ [HeaderFromValue](/api-reference/syntax-defs.mdx#headerfromvalue) \| [HeaderFromEnv](/api-reference/syntax-defs.mdx#headerfromenv) ] | List of headers to be sent with the webhook |
|
||||
| retry_conf | false | [RetryConf](/api-reference/syntax-defs.mdx#retryconf) | Retry configuration if event delivery fails |
|
||||
| replace | false | Boolean | If set to true, the Event Trigger is replaced with the new definition |
|
||||
| enable_manual | false | Boolean | If set to true, the Event Trigger can be invoked manually |
|
||||
| request_transform | false | [RequestTransformation](/api-reference/syntax-defs.mdx#requesttransformation) | Attaches a Request Transformation to the Event Trigger. |
|
||||
| response_transform | false | [ResponseTransformation](/api-reference/syntax-defs.mdx#responsetransformation) | Attaches a Request Transformation to the Event Trigger. |
|
||||
| cleanup_config | false | [AutoEventTriggerCleanupConfig](/api-reference/syntax-defs.mdx#autoeventtriggercleanupconfig) | Cleanup config for the auto cleanup process (EE/Cloud only). |
|
||||
| trigger_on_replication | false | Boolean | Specification for enabling/disabling the Event Trigger during logical replication |
|
||||
|
||||
(\*) Either `webhook` or `webhook_from_env` are required.
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 454 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 290 KiB |
BIN
docs/static/img/event-triggers/one-off.png
vendored
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 300 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 139 KiB |
@ -5,21 +5,8 @@ import {
|
||||
} from '../Common/stateDefaults';
|
||||
import { DefaultState } from './types';
|
||||
|
||||
let defaultHandler = '';
|
||||
if (typeof navigator !== 'undefined') {
|
||||
const { appVersion } = navigator;
|
||||
const isLinux =
|
||||
appVersion.toLowerCase().includes('linux') ||
|
||||
appVersion.toLowerCase().includes('x11');
|
||||
if (isLinux) {
|
||||
defaultHandler = 'http://localhost:3000';
|
||||
} else {
|
||||
defaultHandler = 'http://host.docker.internal:3000';
|
||||
}
|
||||
}
|
||||
|
||||
const state: DefaultState = {
|
||||
handler: defaultHandler,
|
||||
handler: '',
|
||||
actionDefinition: {
|
||||
sdl: defaultActionDefSdl,
|
||||
error: null,
|
||||
|
@ -2,6 +2,9 @@ import React, { useEffect, useState } from 'react';
|
||||
import { useDebouncedEffect } from '@/hooks/useDebounceEffect';
|
||||
import { Analytics, REDACT_EVERYTHING } from '@/features/Analytics';
|
||||
import { inputStyles } from '../../constants';
|
||||
import { FaShieldAlt } from 'react-icons/fa';
|
||||
import { IconTooltip } from '@/new-components/Tooltip';
|
||||
import { LearnMoreLink } from '@/new-components/LearnMoreLink';
|
||||
|
||||
const editorLabel = 'Webhook (HTTP/S) Handler';
|
||||
|
||||
@ -32,26 +35,30 @@ const HandlerEditor: React.FC<HandlerEditorProps> = ({
|
||||
|
||||
return (
|
||||
<Analytics name="ActionEditor" {...REDACT_EVERYTHING}>
|
||||
<div className="mb-lg w-4/12">
|
||||
<div className="mb-lg w-6/12">
|
||||
<h2 className="text-lg font-semibold mb-xs flex items-center">
|
||||
{editorLabel}
|
||||
<span className="text-red-700 ml-xs mr-sm">*</span>
|
||||
<span className="text-red-700 ml-xs">*</span>
|
||||
<IconTooltip
|
||||
message="Environment variables and secrets are available using the {{VARIABLE}} tag. Environment variable templating is available for this field. Example: https://{{ENV_VAR}}/endpoint_url"
|
||||
icon={<FaShieldAlt className="h-4 text-muted cursor-pointer" />}
|
||||
/>
|
||||
<LearnMoreLink href="https://hasura.io/docs/latest/api-reference/syntax-defs/#webhookurl" />
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 mb-sm">
|
||||
Note: Provide an URL or use an env var to template the handler URL if
|
||||
you have different URLs for multiple environments.
|
||||
</p>
|
||||
<input
|
||||
disabled={disabled}
|
||||
type="text"
|
||||
name="handler"
|
||||
value={localValue}
|
||||
onChange={e => setLocalValue(e.target.value)}
|
||||
placeholder="http://custom-logic.com/api"
|
||||
placeholder="http://custom-logic.com/api or {{ACTION_BASE_URL}}/handler"
|
||||
className={inputStyles}
|
||||
data-test="action-create-handler-input"
|
||||
/>
|
||||
<p className="text-sm text-gray-600">
|
||||
Note: You can use an env var to template the handler URL if you have
|
||||
different URLs for multiple environments.
|
||||
<br /> e.g. {'{{ACTION_BASE_URL}}/handler'}
|
||||
</p>
|
||||
</div>
|
||||
</Analytics>
|
||||
);
|
||||
|
@ -61,6 +61,7 @@ import {
|
||||
RetryConf,
|
||||
EventTriggerAutoCleanup,
|
||||
} from '../../types';
|
||||
import { useDebouncedEffect } from '@/hooks/useDebounceEffect';
|
||||
|
||||
interface Props extends InjectedProps {}
|
||||
|
||||
@ -229,49 +230,53 @@ const Add: React.FC<Props> = props => {
|
||||
transformState.sessionVars,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
requestBodyErrorOnChange('');
|
||||
requestTransformedBodyOnChange('');
|
||||
const onResponse = (data: Record<string, any>) => {
|
||||
parseValidateApiData(
|
||||
data,
|
||||
requestBodyErrorOnChange,
|
||||
undefined,
|
||||
requestTransformedBodyOnChange
|
||||
);
|
||||
};
|
||||
const options = getValidateTransformOptions({
|
||||
version: transformState.version,
|
||||
inputPayloadString: transformState.requestSampleInput,
|
||||
webhookUrl: webhook.value,
|
||||
envVarsFromContext: transformState.envVars,
|
||||
sessionVarsFromContext: transformState.sessionVars,
|
||||
transformerBody: transformState.requestBody,
|
||||
isEnvVar: webhook.type === 'env',
|
||||
});
|
||||
if (!webhook.value) {
|
||||
requestBodyErrorOnChange(
|
||||
'Please configure your webhook handler to generate request body transform'
|
||||
);
|
||||
} else if (transformState.requestBody && webhook.value) {
|
||||
dispatch(
|
||||
requestAction(
|
||||
Endpoints.metadata,
|
||||
options,
|
||||
useDebouncedEffect(
|
||||
() => {
|
||||
requestBodyErrorOnChange('');
|
||||
requestTransformedBodyOnChange('');
|
||||
const onResponse = (data: Record<string, any>) => {
|
||||
parseValidateApiData(
|
||||
data,
|
||||
requestBodyErrorOnChange,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
true
|
||||
)
|
||||
).then(onResponse, onResponse); // parseValidateApiData will parse both success and error
|
||||
}
|
||||
}, [
|
||||
transformState.requestSampleInput,
|
||||
transformState.requestBody,
|
||||
webhook,
|
||||
transformState.envVars,
|
||||
transformState.sessionVars,
|
||||
]);
|
||||
requestTransformedBodyOnChange
|
||||
);
|
||||
};
|
||||
const options = getValidateTransformOptions({
|
||||
version: transformState.version,
|
||||
inputPayloadString: transformState.requestSampleInput,
|
||||
webhookUrl: webhook.value,
|
||||
envVarsFromContext: transformState.envVars,
|
||||
sessionVarsFromContext: transformState.sessionVars,
|
||||
transformerBody: transformState.requestBody,
|
||||
isEnvVar: webhook.type === 'env',
|
||||
});
|
||||
if (!webhook.value) {
|
||||
requestBodyErrorOnChange(
|
||||
'Please configure your webhook handler to generate request body transform'
|
||||
);
|
||||
} else if (transformState.requestBody && webhook.value) {
|
||||
dispatch(
|
||||
requestAction(
|
||||
Endpoints.metadata,
|
||||
options,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
true
|
||||
)
|
||||
).then(onResponse, onResponse); // parseValidateApiData will parse both success and error
|
||||
}
|
||||
},
|
||||
1000,
|
||||
[
|
||||
transformState.requestSampleInput,
|
||||
transformState.requestBody,
|
||||
webhook,
|
||||
transformState.envVars,
|
||||
transformState.sessionVars,
|
||||
]
|
||||
);
|
||||
|
||||
const createBtnText = 'Create Event Trigger';
|
||||
|
||||
|
@ -18,9 +18,9 @@ import {
|
||||
} from '../../types';
|
||||
import ColumnList from '../Common/ColumnList';
|
||||
import FormLabel from './FormLabel';
|
||||
import DebouncedDropdownInput from '../Common/DropdownWrapper';
|
||||
import { inputStyles, heading } from '../../constants';
|
||||
import { AutoCleanupForm } from '../Common/AutoCleanupForm';
|
||||
import { FaShieldAlt } from 'react-icons/fa';
|
||||
|
||||
type CreateETFormProps = {
|
||||
state: LocalEventTriggerState;
|
||||
@ -62,7 +62,6 @@ const CreateETForm: React.FC<CreateETFormProps> = props => {
|
||||
handleDatabaseChange,
|
||||
handleSchemaChange,
|
||||
handleTableChange,
|
||||
handleWebhookTypeChange,
|
||||
handleWebhookValueChange,
|
||||
handleOperationsChange,
|
||||
handleOperationsColumnsChange,
|
||||
@ -166,36 +165,31 @@ const CreateETForm: React.FC<CreateETFormProps> = props => {
|
||||
<FormLabel
|
||||
title="Webhook (HTTP/S) Handler"
|
||||
tooltip={tooltip.webhookUrlDescription}
|
||||
tooltipIcon={
|
||||
<FaShieldAlt className="h-4 text-muted cursor-pointer" />
|
||||
}
|
||||
knowMoreLink="https://hasura.io/docs/latest/api-reference/syntax-defs/#webhookurl"
|
||||
/>
|
||||
<div>
|
||||
<div className="w-72">
|
||||
<DebouncedDropdownInput
|
||||
dropdownOptions={[
|
||||
{ display_text: 'URL', value: 'static' },
|
||||
{ display_text: 'From env var', value: 'env' },
|
||||
]}
|
||||
title={webhook.type === 'static' ? 'URL' : 'From env var'}
|
||||
dataKey={webhook.type === 'static' ? 'static' : 'env'}
|
||||
onButtonChange={handleWebhookTypeChange}
|
||||
onHandlerValChange={handleWebhookValueChange}
|
||||
<div className="w-1/2">
|
||||
<p className="text-sm text-gray-600 mb-sm">
|
||||
Note: Provide an URL or use an env var to template the handler URL
|
||||
if you have different URLs for multiple environments.
|
||||
</p>
|
||||
<input
|
||||
type="text"
|
||||
name="handler"
|
||||
onChange={e => handleWebhookValueChange(e.target.value)}
|
||||
required
|
||||
bsClass="w-72"
|
||||
handlerVal={webhook.value}
|
||||
value={webhook.value}
|
||||
id="webhook-url"
|
||||
inputPlaceHolder={
|
||||
webhook.type === 'static'
|
||||
? 'http://httpbin.org/post'
|
||||
: 'MY_WEBHOOK_URL'
|
||||
}
|
||||
testId="webhook"
|
||||
placeholder="http://httpbin.org/post or {{MY_WEBHOOK_URL}}/handler"
|
||||
data-test="webhook"
|
||||
className={`w-82 ${inputStyles}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<small>
|
||||
Note: Specifying the webhook URL via an environmental variable is
|
||||
recommended if you have different URLs for multiple environments.
|
||||
</small>
|
||||
</div>
|
||||
<hr className="my-md" />
|
||||
{isProConsole(window.__env) && (
|
||||
|
@ -1,17 +1,26 @@
|
||||
import React from 'react';
|
||||
import { IconTooltip } from '@/new-components/Tooltip';
|
||||
import { LearnMoreLink } from '@/new-components/LearnMoreLink';
|
||||
|
||||
type FormLabelProps = {
|
||||
title: string;
|
||||
tooltip: string;
|
||||
tooltipIcon?: React.ReactElement;
|
||||
knowMoreLink?: string;
|
||||
};
|
||||
|
||||
const FormLabel: React.FC<FormLabelProps> = ({ title, tooltip }) => {
|
||||
const FormLabel: React.FC<FormLabelProps> = ({
|
||||
title,
|
||||
tooltip,
|
||||
tooltipIcon,
|
||||
knowMoreLink,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<h2 className="text-lg font-semibold mb-xs flex items-center">
|
||||
{title}
|
||||
<IconTooltip message={tooltip} />
|
||||
<IconTooltip message={tooltip} icon={tooltipIcon} />
|
||||
{knowMoreLink ? <LearnMoreLink href={knowMoreLink} /> : null}
|
||||
</h2>
|
||||
</>
|
||||
);
|
||||
|
@ -12,7 +12,7 @@ export const manualOperationsDescription =
|
||||
'Trigger manually from table data browser in console';
|
||||
|
||||
export const webhookUrlDescription =
|
||||
'POST endpoint which will be triggered with payload on configured events';
|
||||
'Environment variables and secrets are available using the {{VARIABLE}} tag. Environment variable templating is available for this field. Example: https://{{ENV_VAR}}/endpoint_url';
|
||||
|
||||
export const advancedOperationDescription =
|
||||
'For update triggers, webhook will be triggered only when selected columns are modified';
|
||||
|
@ -65,6 +65,7 @@ import {
|
||||
getEventTriggerByName,
|
||||
} from '../../../../../metadata/selector';
|
||||
import { AutoCleanupForm } from '../Common/AutoCleanupForm';
|
||||
import { useDebouncedEffect } from '@/hooks/useDebounceEffect';
|
||||
|
||||
interface Props extends InjectedProps {}
|
||||
|
||||
@ -291,25 +292,29 @@ const Modify: React.FC<Props> = props => {
|
||||
transformState.sessionVars,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
transformState.requestBody &&
|
||||
state.webhook?.value &&
|
||||
!transformState.requestTransformedBody
|
||||
) {
|
||||
requestBodyErrorOnChange('');
|
||||
dispatch(
|
||||
requestAction(
|
||||
Endpoints.metadata,
|
||||
reqBodyoptions,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
true
|
||||
)
|
||||
).then(onRequestBodyResponse, onRequestBodyResponse);
|
||||
}
|
||||
}, [transformState.requestTransformedBody]);
|
||||
useDebouncedEffect(
|
||||
() => {
|
||||
if (
|
||||
transformState.requestBody &&
|
||||
state.webhook?.value &&
|
||||
!transformState.requestTransformedBody
|
||||
) {
|
||||
requestBodyErrorOnChange('');
|
||||
dispatch(
|
||||
requestAction(
|
||||
Endpoints.metadata,
|
||||
reqBodyoptions,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
true
|
||||
)
|
||||
).then(onRequestBodyResponse, onRequestBodyResponse);
|
||||
}
|
||||
},
|
||||
1000,
|
||||
[transformState.requestTransformedBody]
|
||||
);
|
||||
|
||||
const saveWrapper =
|
||||
(property?: EventTriggerProperty) =>
|
||||
|
@ -1,8 +1,10 @@
|
||||
import React from 'react';
|
||||
import { FaShieldAlt } from 'react-icons/fa';
|
||||
import Editor from '../../../../Common/Layout/ExpandableEditor/Editor';
|
||||
import { inputStyles } from '../../constants';
|
||||
import { EventTrigger, URLConf, VoidCallback } from '../../types';
|
||||
import { parseServerWebhook } from '../../utils';
|
||||
import FormLabel from '../Add/FormLabel';
|
||||
import DebouncedDropdownInput from '../Common/DropdownWrapper';
|
||||
|
||||
type WebhookEditorProps = {
|
||||
@ -14,7 +16,6 @@ type WebhookEditorProps = {
|
||||
|
||||
const WebhookEditor = (props: WebhookEditorProps) => {
|
||||
const { currentTrigger, webhook, setWebhook, save } = props;
|
||||
|
||||
const existingWebhook = parseServerWebhook(
|
||||
currentTrigger.configuration.webhook,
|
||||
currentTrigger.configuration.webhook_from_env
|
||||
@ -50,37 +51,62 @@ const WebhookEditor = (props: WebhookEditorProps) => {
|
||||
);
|
||||
|
||||
const expanded = () => (
|
||||
<div className="pb-sm pt-sm max-w-80">
|
||||
<DebouncedDropdownInput
|
||||
dropdownOptions={[
|
||||
{ display_text: 'URL', value: 'static' },
|
||||
{ display_text: 'From env var', value: 'env' },
|
||||
]}
|
||||
title={webhook.type === 'env' ? 'From env var' : 'URL'}
|
||||
dataKey={webhook.type === 'env' ? 'env' : 'static'}
|
||||
onButtonChange={handleWebhookTypeChange}
|
||||
onHandlerValChange={handleWebhookValueChange}
|
||||
required
|
||||
bsClass={`${inputStyles} w-72`}
|
||||
handlerVal={webhook.value}
|
||||
id="webhook-url"
|
||||
inputPlaceHolder={
|
||||
webhook.type === 'env' ? 'MY_WEBHOOK_URL' : 'http://httpbin.org/post'
|
||||
}
|
||||
testId="webhook"
|
||||
/>
|
||||
<div className="w-1/2">
|
||||
<p className="text-sm text-gray-600 mb-sm">
|
||||
Note: Provide an URL or use an env var to template the handler URL if
|
||||
you have different URLs for multiple environments.
|
||||
</p>
|
||||
{existingWebhook?.type === 'env' ? (
|
||||
<DebouncedDropdownInput
|
||||
dropdownOptions={[
|
||||
{ display_text: 'URL', value: 'static' },
|
||||
{ display_text: 'From env var', value: 'env' },
|
||||
]}
|
||||
title={webhook.type === 'env' ? 'From env var' : 'URL'}
|
||||
dataKey={webhook.type === 'env' ? 'env' : 'static'}
|
||||
onButtonChange={handleWebhookTypeChange}
|
||||
onHandlerValChange={handleWebhookValueChange}
|
||||
required
|
||||
bsClass={`w-82`}
|
||||
handlerVal={webhook.value}
|
||||
id="webhook-url"
|
||||
inputPlaceHolder={
|
||||
webhook.type === 'env'
|
||||
? 'MY_WEBHOOK_URL'
|
||||
: 'http://httpbin.org/post'
|
||||
}
|
||||
testId="webhook"
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
type="text"
|
||||
name="handler"
|
||||
onChange={e => handleWebhookValueChange(e.target.value)}
|
||||
required
|
||||
className={`${inputStyles} w-82`}
|
||||
value={
|
||||
webhook.type === 'static' ? webhook.value : `{{${webhook.value}}}`
|
||||
}
|
||||
id="webhook-url"
|
||||
placeholder="http://httpbin.org/post or {{MY_WEBHOOK_URL}}/handler"
|
||||
data-test="webhook"
|
||||
/>
|
||||
)}
|
||||
<br />
|
||||
<small>
|
||||
Note: Specifying the webhook URL via an environmental variable is
|
||||
recommended if you have different URLs for multiple environments.
|
||||
</small>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-full border-b border-solid border-gray-300 mb-md">
|
||||
<div className="mb-md">
|
||||
<h4 className="text-lg font-bold mb-md">Webhook (HTTP/S) Handler</h4>
|
||||
<FormLabel
|
||||
title="Webhook (HTTP/S) Handler"
|
||||
tooltip="Environment variables and secrets are available using the {{VARIABLE}} tag. Environment variable templating is available for this field. Example: https://{{ENV_VAR}}/endpoint_url"
|
||||
tooltipIcon={
|
||||
<FaShieldAlt className="h-4 text-muted cursor-pointer" />
|
||||
}
|
||||
knowMoreLink="https://hasura.io/docs/latest/api-reference/syntax-defs/#webhookurl"
|
||||
/>
|
||||
<Editor
|
||||
editorCollapsed={collapsed}
|
||||
editorExpanded={expanded}
|
||||
|
@ -12,6 +12,8 @@ import CommonHeader from '../../../Common/Layout/ReusableHeader/Header';
|
||||
import GraphQLCustomizationEdit from './GraphQLCustomization/GraphQLCustomizationEdit';
|
||||
|
||||
import { focusYellowRing, inputStyles, subHeading } from '../constants';
|
||||
import { FaShieldAlt } from 'react-icons/fa';
|
||||
import { LearnMoreLink } from '@/new-components/LearnMoreLink';
|
||||
|
||||
class Common extends React.Component {
|
||||
getPlaceHolderText(valType) {
|
||||
@ -48,6 +50,7 @@ class Common extends React.Component {
|
||||
forwardClientHeaders,
|
||||
comment,
|
||||
customization,
|
||||
isEnvVarEnabled,
|
||||
} = this.props;
|
||||
|
||||
const { isModify } = this.props.editState;
|
||||
@ -58,8 +61,9 @@ class Common extends React.Component {
|
||||
const tooltips = {
|
||||
graphqlurl: (
|
||||
<IconTooltip
|
||||
message="Remote GraphQL server’s URL. E.g. https://my-domain/v1/graphql"
|
||||
message="Environment variables and secrets are available using the {{VARIABLE}} tag. Environment variable templating is available for this field. Example: https://{{ENV_VAR}}/endpoint_url"
|
||||
side="right"
|
||||
icon={<FaShieldAlt className="h-4 text-muted cursor-pointer" />}
|
||||
/>
|
||||
),
|
||||
clientHeaderForward: (
|
||||
@ -142,42 +146,59 @@ class Common extends React.Component {
|
||||
<hr className="my-md" />
|
||||
<div className={`${subHeading} flex items-center`}>
|
||||
GraphQL server URL *{tooltips.graphqlurl}
|
||||
<LearnMoreLink href="https://hasura.io/docs/latest/api-reference/syntax-defs/#webhookurl" />
|
||||
</div>
|
||||
<div className={'w-80'}>
|
||||
<DropdownButton
|
||||
dropdownOptions={[
|
||||
{ display_text: 'URL', value: 'manualUrl' },
|
||||
{ display_text: 'From env var', value: 'envName' },
|
||||
]}
|
||||
title={
|
||||
(manualUrl !== null && 'URL') ||
|
||||
(envName !== null && 'From env var') ||
|
||||
'Value'
|
||||
}
|
||||
dataKey={
|
||||
(manualUrl !== null && 'manualUrl') ||
|
||||
(envName !== null && 'envName')
|
||||
}
|
||||
onButtonChange={this.toggleUrlParam.bind(this)}
|
||||
onInputChange={this.handleInputChange.bind(this)}
|
||||
required={urlRequired}
|
||||
bsClass="w-80"
|
||||
inputVal={manualUrl || envName}
|
||||
disabled={isDisabled}
|
||||
id="graphql-server-url"
|
||||
inputPlaceHolder={
|
||||
(manualUrl !== null &&
|
||||
'https://my-graphql-service.com/graphql') ||
|
||||
(envName !== null && 'MY_GRAPHQL_ENDPOINT')
|
||||
}
|
||||
testId="remote-schema-graphql-url"
|
||||
/>
|
||||
<p className="text-sm text-gray-600 mb-sm w-1/2">
|
||||
Note: Provide an URL or use an env var to template the handler URL if
|
||||
you have different URLs for multiple environments.
|
||||
</p>
|
||||
<div className={'w-1/2'}>
|
||||
{isEnvVarEnabled ? (
|
||||
<DropdownButton
|
||||
dropdownOptions={[
|
||||
{ display_text: 'URL', value: 'manualUrl' },
|
||||
{ display_text: 'From env var', value: 'envName' },
|
||||
]}
|
||||
title={
|
||||
(manualUrl !== null && 'URL') ||
|
||||
(envName !== null && 'From env var') ||
|
||||
'Value'
|
||||
}
|
||||
dataKey={
|
||||
(manualUrl !== null && 'manualUrl') ||
|
||||
(envName !== null && 'envName')
|
||||
}
|
||||
onButtonChange={this.toggleUrlParam.bind(this)}
|
||||
onInputChange={this.handleInputChange.bind(this)}
|
||||
required={urlRequired}
|
||||
bsClass="w-80"
|
||||
inputVal={envName || manualUrl}
|
||||
disabled={isDisabled}
|
||||
id="graphql-server-url"
|
||||
inputPlaceHolder={
|
||||
(manualUrl !== null &&
|
||||
'https://my-graphql-service.com/graphql') ||
|
||||
(envName !== null && 'MY_GRAPHQL_ENDPOINT')
|
||||
}
|
||||
testId="remote-schema-graphql-url"
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
type="text"
|
||||
name="handler"
|
||||
onChange={this.handleInputChange.bind(this)}
|
||||
required={urlRequired}
|
||||
className={`w-3/4 ${inputStyles}`}
|
||||
data-key={'manualUrl'}
|
||||
value={manualUrl}
|
||||
disabled={isDisabled}
|
||||
id="graphql-server-url"
|
||||
placeholder="https://my-graphql-service.com/graphql or {{MY_WEBHOOK_URL}}/graphql"
|
||||
data-test="remote-schema-graphql-url"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<br />
|
||||
<small>
|
||||
Note: Specifying the server URL via an environmental variable is
|
||||
recommended if you have different URLs for multiple environments.
|
||||
</small>
|
||||
<div className={`${subHeading} pt-md`}>
|
||||
Headers for the remote GraphQL server
|
||||
</div>
|
||||
|
@ -199,6 +199,12 @@ class Edit extends React.Component {
|
||||
{ redactText: true }
|
||||
);
|
||||
|
||||
const isEnvVarEnabled = () => {
|
||||
return this.props.allRemoteSchemas.some(
|
||||
rs => rs.name === remoteSchemaName && rs.definition.url_from_env
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
@ -227,7 +233,7 @@ class Edit extends React.Component {
|
||||
this.editClicked();
|
||||
}}
|
||||
>
|
||||
<Common {...this.props} />
|
||||
<Common {...this.props} isEnvVarEnabled={isEnvVarEnabled()} />
|
||||
{generateMigrateBtns()}
|
||||
</form>
|
||||
</Analytics>
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
ScheduleEventPayloadInput,
|
||||
} from './components';
|
||||
import { ScheduledTime } from './components/ScheduledTime';
|
||||
import { FaShieldAlt } from 'react-icons/fa';
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
@ -51,10 +52,15 @@ const OneOffScheduledEventForm = (props: Props) => {
|
||||
</div>
|
||||
<div className="mb-md">
|
||||
<InputField
|
||||
learnMoreLink="https://hasura.io/docs/latest/api-reference/syntax-defs/#webhookurl"
|
||||
tooltipIcon={
|
||||
<FaShieldAlt className="h-4 text-muted cursor-pointer" />
|
||||
}
|
||||
name="webhook"
|
||||
label="Webhook URL"
|
||||
placeholder="https://httpbin.com/post"
|
||||
tooltip="The HTTP URL that should be triggered."
|
||||
placeholder="https://httpbin.com/post or {{MY_WEBHOOK_URL}}/handler"
|
||||
tooltip="Environment variables and secrets are available using the {{VARIABLE}} tag. Environment variable templating is available for this field. Example: https://{{ENV_VAR}}/endpoint_url"
|
||||
description="Note: Provide an URL or use an env var to template the handler URL if you have different URLs for multiple environments."
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-md">
|
||||
|
@ -19,6 +19,7 @@ import {
|
||||
} from './utils';
|
||||
import { useCronMetadataMigration, useDefaultValues } from './hooks';
|
||||
import { CronRequestTransformation } from './components/CronRequestTransformation';
|
||||
import { FaQuestionCircle, FaShieldAlt } from 'react-icons/fa';
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
@ -63,10 +64,15 @@ const FormContent = (props: FormContentProps) => {
|
||||
<hr className="my-md" />
|
||||
<div className="mb-xs w-1/2">
|
||||
<InputField
|
||||
learnMoreLink="https://hasura.io/docs/latest/api-reference/syntax-defs/#webhookurl"
|
||||
tooltipIcon={
|
||||
<FaShieldAlt className="h-4 text-muted cursor-pointer" />
|
||||
}
|
||||
name="webhook"
|
||||
label="Webhook URL"
|
||||
placeholder="https://httpbin.com/post"
|
||||
tooltip="The HTTP URL that should be triggered. You can also provide the URL from environment variables, e.g. {{MY_WEBHOOK_URL}}"
|
||||
placeholder="https://httpbin.com/post or {{MY_WEBHOOK_URL}}/handler"
|
||||
tooltip="Environment variables and secrets are available using the {{VARIABLE}} tag. Environment variable templating is available for this field. Example: https://{{ENV_VAR}}/endpoint_url"
|
||||
description="Note: Provide an URL or use an env var to template the handler URL if you have different URLs for multiple environments."
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-xs w-1/2">
|
||||
|
@ -16,7 +16,9 @@ export const CronScheduleSelector = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="block flex items-center text-gray-600 font-semibold">
|
||||
<label htmlFor="schedule">Cron Schedule</label>
|
||||
<label htmlFor="schedule" className="font-semibold">
|
||||
Cron Schedule
|
||||
</label>
|
||||
<IconTooltip message="Schedule for your cron (events are created based on the UTC timezone)" />
|
||||
<LearnMoreLink
|
||||
href="https://crontab.guru/#*_*_*_*_*"
|
||||
|
@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import { IconTooltip } from '@/new-components/Tooltip';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { FaShieldAlt } from 'react-icons/fa';
|
||||
import { LearnMoreLink } from '@/new-components/LearnMoreLink';
|
||||
|
||||
export const GraphQLServiceUrl = () => {
|
||||
const { watch, register } = useFormContext();
|
||||
@ -9,28 +11,21 @@ export const GraphQLServiceUrl = () => {
|
||||
<div className="mb-md w-6/12">
|
||||
<label className="block flex items-center text-gray-600 font-semibold mb-xs">
|
||||
GraphQL Service URL
|
||||
<IconTooltip message="Remote GraphQL server’s URL. E.g. https://my-domain/v1/graphql" />
|
||||
<IconTooltip
|
||||
message="Environment variables and secrets are available using the {{VARIABLE}} tag. Environment variable templating is available for this field. Example: https://{{ENV_VAR}}/endpoint_url"
|
||||
icon={<FaShieldAlt className="h-4 text-muted cursor-pointer" />}
|
||||
/>
|
||||
<LearnMoreLink href="https://hasura.io/docs/latest/api-reference/syntax-defs/#webhookurl" />
|
||||
</label>
|
||||
<p className="text-sm text-gray-600 mb-sm">
|
||||
Note: Specifying the server URL via an environmental variable is
|
||||
recommended if you have different URLs for multiple environments.
|
||||
Note: Provide an URL or use an env var to template the handler URL if
|
||||
you have different URLs for multiple environments.
|
||||
</p>
|
||||
<div className="flex shadow-sm rounded">
|
||||
<select
|
||||
className="inline-flex rounded-l border border-r-0 border-gray-300 bg-white hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
|
||||
{...register('url.type')}
|
||||
>
|
||||
<option value="from_url">URL</option>
|
||||
<option value="from_env">Env Var</option>
|
||||
</select>
|
||||
<input
|
||||
type="text"
|
||||
className="flex-1 min-w-0 block w-full px-3 py-2 rounded-r border-gray-300 hover:border-gray-400 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
|
||||
placeholder={
|
||||
url?.type === 'from_url'
|
||||
? 'https://myservice.com/graphql'
|
||||
: 'MY_GRAPHQL_ENDPOINT'
|
||||
}
|
||||
placeholder="https://myservice.com/graphql or {{MY_WEBHOOK_URL}}/graphql"
|
||||
{...register('url.value')}
|
||||
data-testid="url"
|
||||
/>
|
||||
|
@ -39,6 +39,10 @@ export type FieldWrapperPassThroughProps = {
|
||||
* Render line breaks in the description
|
||||
*/
|
||||
renderDescriptionLineBreaks?: boolean;
|
||||
/**
|
||||
* tooltip icon other then ?
|
||||
*/
|
||||
tooltipIcon?: React.ReactElement;
|
||||
} & DiscriminatedTypes<
|
||||
{
|
||||
/**
|
||||
@ -110,6 +114,7 @@ export const FieldWrapper = (props: FieldWrapperProps) => {
|
||||
className,
|
||||
size = 'full',
|
||||
error,
|
||||
tooltipIcon,
|
||||
children,
|
||||
description,
|
||||
tooltip,
|
||||
@ -158,7 +163,9 @@ export const FieldWrapper = (props: FieldWrapperProps) => {
|
||||
{label}
|
||||
{loading ? <Skeleton className="absolute inset-0" /> : null}
|
||||
</span>
|
||||
{!loading && tooltip ? <IconTooltip message={tooltip} /> : null}
|
||||
{!loading && tooltip ? (
|
||||
<IconTooltip message={tooltip} icon={tooltipIcon} />
|
||||
) : null}
|
||||
{!loading && !!learnMoreLink && (
|
||||
<LearnMoreLink href={learnMoreLink} />
|
||||
)}
|
||||
|
@ -18,7 +18,7 @@ export const LearnMoreLink: React.VFC<LearnMoreLinkProps> = props => {
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`ml-xs italic text-sm text-secondary ${className}`}
|
||||
className={`ml-xs italic text-sm font-thin text-secondary ${className}`}
|
||||
>
|
||||
{text}
|
||||
</a>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { ReactElement, ReactNode } from 'react';
|
||||
import { Tooltip, TooltipProps } from '@/new-components/Tooltip';
|
||||
import { FaQuestionCircle } from 'react-icons/fa';
|
||||
|
||||
@ -11,6 +11,10 @@ export type IconTooltipProps = {
|
||||
* The tooltip icon classes
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* tooltip icon other then ?
|
||||
*/
|
||||
icon?: ReactNode;
|
||||
} & Pick<TooltipProps, 'side'> &
|
||||
Pick<TooltipProps, 'defaultOpen'>;
|
||||
|
||||
@ -19,6 +23,7 @@ export const IconTooltip: React.VFC<IconTooltipProps> = ({
|
||||
className,
|
||||
side = 'right',
|
||||
defaultOpen = false,
|
||||
icon,
|
||||
}) => (
|
||||
<Tooltip
|
||||
tooltipContentChildren={message}
|
||||
@ -26,8 +31,12 @@ export const IconTooltip: React.VFC<IconTooltipProps> = ({
|
||||
defaultOpen={defaultOpen}
|
||||
className="flex items-center"
|
||||
>
|
||||
<FaQuestionCircle
|
||||
className={`h-4 text-muted cursor-pointer ${className}`}
|
||||
/>
|
||||
{!icon ? (
|
||||
<FaQuestionCircle
|
||||
className={`h-4 text-muted cursor-pointer ${className}`}
|
||||
/>
|
||||
) : (
|
||||
icon
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
|