console: allow users to remove event trigger auto clean-up configuration

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8036
Co-authored-by: Sooraj <8408875+soorajshankar@users.noreply.github.com>
GitOrigin-RevId: da8df15ac3591a0fd5dfce8e0be26690479ef066
This commit is contained in:
Varun Choudhary 2023-02-28 15:27:46 +05:30 committed by hasura-bot
parent 4e7fbbc2d6
commit 1971f4f6e4
9 changed files with 355 additions and 231 deletions

View File

@ -152,10 +152,14 @@ input[type='submit'] {
cursor: pointer; cursor: pointer;
} }
button[disabled], /*
This button style was overriding all disabled buttons in console,
which was not allowing the `cursor-not-allowed` class to take effect
*/
/* button[disabled],
html input[disabled] { html input[disabled] {
cursor: default; cursor: default;
} } */
button::-moz-focus-inner, button::-moz-focus-inner,
input::-moz-focus-inner { input::-moz-focus-inner {

View File

@ -152,10 +152,14 @@ input[type='submit'] {
cursor: pointer; cursor: pointer;
} }
button[disabled], /*
This button style was overriding all disabled buttons in console,
which was not allowing the `cursor-not-allowed` class to take effect
*/
/* button[disabled],
html input[disabled] { html input[disabled] {
cursor: default; cursor: default;
} } */
button::-moz-focus-inner, button::-moz-focus-inner,
input::-moz-focus-inner { input::-moz-focus-inner {

View File

@ -67,7 +67,7 @@ class Editor extends React.Component {
const saveWithToggle = () => saveFunc(this.toggleEditor); const saveWithToggle = () => saveFunc(this.toggleEditor);
return ( return (
<Button <Button
type="submit" type="button"
mode="primary" mode="primary"
isLoading={isProcessing} isLoading={isProcessing}
loadingText="Saving..." loadingText="Saving..."
@ -87,7 +87,7 @@ class Editor extends React.Component {
const removeWithToggle = () => removeFunc(this.toggleEditor); const removeWithToggle = () => removeFunc(this.toggleEditor);
return ( return (
<Button <Button
type="submit" type="button"
mode="destructive" mode="destructive"
isLoading={isProcessing} isLoading={isProcessing}
loadingText="Removing..." loadingText="Removing..."

View File

@ -307,9 +307,11 @@ const Add: React.FC<Props> = props => {
delete newState?.cleanupConfig; delete newState?.cleanupConfig;
} }
/* if auto cleanup is paused while creating event trigger */ /* don't pass cleanup config if it's empty or just have only paused*/
/* remove the cleanup_config from the state */ if (
if (state.cleanupConfig?.paused) { JSON.stringify(newState?.cleanupConfig) === '{}' ||
JSON.stringify(newState?.cleanupConfig) === '{"paused":true}'
) {
delete newState?.cleanupConfig; delete newState?.cleanupConfig;
} }

View File

@ -1,4 +1,6 @@
import defaultState from '../state'; import { FaCircle } from 'react-icons/fa';
import { Analytics } from '../../../../../features/Analytics';
import { Tooltip } from '../../../../../new-components/Tooltip';
import { Collapsible } from '../../../../../new-components/Collapsible'; import { Collapsible } from '../../../../../new-components/Collapsible';
import { DropdownButton } from '../../../../../new-components/DropdownButton'; import { DropdownButton } from '../../../../../new-components/DropdownButton';
import { InputSection } from '../../../../../new-components/InputSetionWithoutForm'; import { InputSection } from '../../../../../new-components/InputSetionWithoutForm';
@ -21,132 +23,239 @@ const crons = [
export const AutoCleanupForm = (props: AutoCleanupFormProps) => { export const AutoCleanupForm = (props: AutoCleanupFormProps) => {
const { cleanupConfig, onChange } = props; const { cleanupConfig, onChange } = props;
const isCleanupConfigSet =
cleanupConfig && Object.keys(cleanupConfig).length > 0;
// disable other fields when cleanup is paused
const isDisable = isCleanupConfigSet
? cleanupConfig?.paused
: !cleanupConfig?.paused;
return ( return (
<div className="w-1/2"> <div className="w-1/2">
<Collapsible <Analytics
triggerChildren={ name="open-event-log-auto-cleanup"
<h2 className="text-lg font-semibold mb-xs flex items-center mb-0"> passHtmlAttributesToChildren
Auto-cleanup Event Logs
</h2>
}
> >
<div className="flex items-center mb-sm">
<Switch
checked={!cleanupConfig?.paused}
onCheckedChange={() => {
onChange({
...cleanupConfig,
paused: !cleanupConfig?.paused,
});
}}
/>
<span className="ml-xs cursor-pointer">Enable event log cleanup</span>
</div>
<div className="flex items-center mb-sm">
<Switch
checked={cleanupConfig?.clean_invocation_logs}
disabled={cleanupConfig?.paused}
onCheckedChange={() => {
onChange({
...cleanupConfig,
clean_invocation_logs: !cleanupConfig?.clean_invocation_logs,
});
}}
/>
<span className="ml-xs cursor-pointer">
Clean invocation logs with event logs
</span>
</div>
<InputSection
label="Clear logs older than (hours)"
tooltip="Clear event logs older than (in hours, default:168 hours or 7
days)"
placeholder={
defaultState?.cleanupConfig?.clear_older_than?.toString() ?? ''
}
value={cleanupConfig?.clear_older_than?.toString() ?? ''}
onChange={value => {
onChange({
...cleanupConfig,
clear_older_than: value ? parseInt(value, 10) : undefined,
});
}}
/>
<InputSection
label="Cleanup Frequency"
tooltip="Cron expression at which the cleanup should be invoked."
placeholder={defaultState?.cleanupConfig?.timeout?.toString() ?? ''}
value={cleanupConfig?.schedule?.toString() ?? ''}
onChange={value => {
onChange({
...cleanupConfig,
schedule: value,
});
}}
/>
<div className="my-sm">
<DropdownButton
items={[
crons.map(cron => (
<div
key={cron.value}
onClick={() => {
onChange({
...cleanupConfig,
schedule: cron.value,
});
}}
className="py-xs cursor-pointer mx-1 px-xs py-1 rounded hover:bg-gray-100"
>
<p className="mb-0 font-semibold whitespace-nowrap">
{cron.label}
</p>
<p className="mb-0">{cron.value}</p>
</div>
)),
]}
>
<span className="font-bold">Frequent Frequencies</span>
</DropdownButton>
</div>
<Collapsible <Collapsible
triggerChildren={ triggerChildren={
<h2 className="text-lg font-semibold mb-xs flex items-center mb-0"> <h2 className="text-lg font-semibold mb-xs flex items-center mb-0">
Advanced Settings Auto-cleanup Event Logs
<div className="flex items-center">
<Tooltip
side="top"
tooltipContentChildren={
isCleanupConfigSet &&
!(
cleanupConfig?.paused &&
Object.keys(cleanupConfig).length === 1
)
? 'Auto-cleanup has been configured. After clearing/resetting, save changes to remove the configuration.'
: 'Auto-cleanup is currently not configured'
}
className="h-full flex items-center"
>
{' '}
{isCleanupConfigSet &&
!(
cleanupConfig?.paused &&
Object.keys(cleanupConfig).length === 1
) ? (
<FaCircle className="ml-xs fill-sky-600 text-xs" />
) : (
<FaCircle className="ml-xs fill-slate-400 text-xs" />
)}
<Analytics
name="event-auto-cleanup-clear-reset-btn"
passHtmlAttributesToChildren
>
<span
className="text-sky-500 ml-xs font-thin text-sm"
onClick={() => onChange({})}
>
{isCleanupConfigSet &&
!(
cleanupConfig?.paused &&
Object.keys(cleanupConfig).length === 1
)
? 'Clear / Reset'
: ''}
</span>
</Analytics>
</Tooltip>
</div>
</h2> </h2>
} }
defaultOpen={!!cleanupConfig}
> >
<InputSection <div className="flex items-center mb-sm">
label="Timeout (seconds)" <Tooltip
tooltip="Timeout for the query (in seconds, default: 60)" side="right"
placeholder={defaultState.cleanupConfig?.timeout?.toString() ?? ''} tooltipContentChildren={
value={cleanupConfig?.timeout?.toString() ?? ''} isCleanupConfigSet
onChange={value => { ? 'When not enabled, event log cleanup is paused. To completely remove event log cleanup configuration use Clear/Reset button'
onChange({ : 'When not enabled, event log cleanup is paused'
...cleanupConfig, }
timeout: value ? parseInt(value, 10) : undefined, className="flex items-center ml-0"
}); >
}} <Switch
/> checked={cleanupConfig?.paused === false}
onCheckedChange={() => {
onChange({
...cleanupConfig,
paused: cleanupConfig?.paused === false ? true : false,
});
}}
/>
<span className="ml-xs cursor-pointer">
Enable event log cleanup
</span>
</Tooltip>
</div>
{
<div>
<div className="flex items-center mb-sm">
<Tooltip
side="right"
tooltipContentChildren={
isDisable
? 'Enable event log cleanup to configure'
: 'Enabling this will clear event invocation logs along with event logs'
}
className="flex items-center ml-0"
>
<Switch
checked={cleanupConfig?.clean_invocation_logs}
disabled={isDisable}
onCheckedChange={() => {
onChange({
...cleanupConfig,
clean_invocation_logs:
!cleanupConfig?.clean_invocation_logs,
});
}}
/>
<span className="ml-xs cursor-pointer">
Clean invocation logs with event logs
</span>
</Tooltip>
</div>
<InputSection <InputSection
label="Batch Size" label="Clear logs older than (hours)"
tooltip="Number of event trigger logs to delete in a batch (default: 10,000)" disabled={isDisable}
placeholder={ tooltip={
defaultState.cleanupConfig?.batch_size?.toString() ?? '' isDisable
} ? `Enable event log cleanup to configure. Clear event logs older than (in hours)`
value={cleanupConfig?.batch_size?.toString() ?? ''} : `Clear event logs older than (in hours)`
onChange={value => { }
onChange({ placeholder="168"
...cleanupConfig, required
batch_size: value ? parseInt(value, 10) : undefined, value={cleanupConfig?.clear_older_than?.toString() ?? ''}
}); onChange={value => {
}} onChange({
/> ...cleanupConfig,
clear_older_than: value ? parseInt(value, 10) : undefined,
});
}}
/>
<InputSection
label="Cleanup Frequency"
disabled={isDisable}
tooltip={
isDisable
? `Enable event log cleanup to configure. Cron expression at which the cleanup should be invoked.`
: `Cron expression at which the cleanup should be invoked.`
}
placeholder="0 0 * * *"
required
value={cleanupConfig?.schedule?.toString() ?? ''}
onChange={value => {
onChange({
...cleanupConfig,
schedule: value,
});
}}
/>
<div className="my-sm">
<DropdownButton
disabled={isDisable}
items={[
crons.map(cron => (
<div
key={cron.value}
onClick={() => {
onChange({
...cleanupConfig,
schedule: cron.value,
});
}}
className="py-xs cursor-pointer mx-1 px-xs py-1 rounded hover:bg-gray-100"
>
<p className="mb-0 font-semibold whitespace-nowrap">
{cron.label}
</p>
<p className="mb-0">{cron.value}</p>
</div>
)),
]}
>
<span className="font-bold">Frequent Frequencies</span>
</DropdownButton>
</div>
<Analytics
name="open-adv-setting-event-log-cleanup"
passHtmlAttributesToChildren
>
<Collapsible
triggerChildren={
<h2 className="text-lg font-semibold mb-xs flex items-center mb-0">
Advanced Settings
</h2>
}
>
<InputSection
label="Timeout (seconds)"
disabled={isDisable}
tooltip={
isDisable
? `Enable event log cleanup to configure. Timeout for the query (in seconds, default: 60)`
: `Timeout for the query (in seconds, default: 60)`
}
placeholder="60"
value={cleanupConfig?.timeout?.toString() ?? ''}
onChange={value => {
onChange({
...cleanupConfig,
timeout: value ? parseInt(value, 10) : undefined,
});
}}
/>
<InputSection
label="Batch Size"
disabled={isDisable}
tooltip={
isDisable
? `Enable event log cleanup to configure. Number of event trigger logs to delete in a batch (default: 10,000)`
: `Number of event trigger logs to delete in a batch (default: 10,000)`
}
placeholder="10000"
value={cleanupConfig?.batch_size?.toString() ?? ''}
onChange={value => {
onChange({
...cleanupConfig,
batch_size: value ? parseInt(value, 10) : undefined,
});
}}
/>
</Collapsible>
</Analytics>
</div>
}
</Collapsible> </Collapsible>
</Collapsible> </Analytics>
</div> </div>
); );
}; };

View File

@ -339,9 +339,10 @@ const Modify: React.FC<Props> = props => {
const modifyTriggerState = { ...state }; const modifyTriggerState = { ...state };
/* don't pass cleanup config if it's empty or just have only paused */
if ( if (
!currentTrigger?.configuration?.cleanup_config && JSON.stringify(modifyTriggerState?.cleanupConfig) === '{}' ||
state.cleanupConfig?.paused JSON.stringify(modifyTriggerState?.cleanupConfig) === '{"paused":true}'
) { ) {
delete modifyTriggerState?.cleanupConfig; delete modifyTriggerState?.cleanupConfig;
} }
@ -359,110 +360,111 @@ const Modify: React.FC<Props> = props => {
); );
}; };
const submit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
saveWrapper()();
};
const deleteWrapper = () => { const deleteWrapper = () => {
dispatch(deleteEventTrigger(currentTrigger)); dispatch(deleteEventTrigger(currentTrigger));
}; };
return ( return (
<Analytics name="ModifyEventTriggers" {...REDACT_EVERYTHING}> <Analytics name="ModifyEventTriggers" {...REDACT_EVERYTHING}>
<div <div className="w-full overflow-y-auto bg-gray-50">
key={currentTrigger.name}
className="w-full overflow-y-auto bg-gray-50"
>
<div className="max-w-6xl"> <div className="max-w-6xl">
<TableHeader <form onSubmit={submit}>
count={null} <TableHeader
triggerName={currentTrigger.name} count={null}
tabName="modify" triggerName={currentTrigger.name}
readOnlyMode={readOnlyMode} tabName="modify"
/> readOnlyMode={readOnlyMode}
<br />
<h2 className="text-lg font-semibold mb-xs flex items-center">
Event Info
</h2>
<Info currentTrigger={currentTrigger} />
<div className="relative">
<WebhookEditor
currentTrigger={currentTrigger}
webhook={state.webhook}
setWebhook={setState.webhook}
save={saveWrapper('webhook')}
/> />
<OperationEditor <br />
currentTrigger={currentTrigger} <h2 className="text-lg font-semibold mb-xs flex items-center">
databaseInfo={databaseInfo} Event Info
operations={state.operations} </h2>
setOperations={setState.operations} <Info currentTrigger={currentTrigger} />
operationColumns={state.operationColumns} <div className="relative">
setOperationColumns={setState.operationColumns} <WebhookEditor
save={saveWrapper('ops')} currentTrigger={currentTrigger}
isAllColumnChecked={state.isAllColumnChecked} webhook={state.webhook}
handleColumnRadioButton={setState.toggleAllColumnChecked} setWebhook={setState.webhook}
/> save={saveWrapper('webhook')}
<hr className="my-md" /> />
<RetryConfEditor <OperationEditor
conf={state.retryConf} currentTrigger={currentTrigger}
setRetryConf={setState.retryConf} databaseInfo={databaseInfo}
currentTrigger={currentTrigger} operations={state.operations}
save={saveWrapper('retry_conf')} setOperations={setState.operations}
/> operationColumns={state.operationColumns}
<hr className="my-md" /> setOperationColumns={setState.operationColumns}
{isProConsole(window.__env) && ( save={saveWrapper('ops')}
<div className="mb-md"> isAllColumnChecked={state.isAllColumnChecked}
<AutoCleanupForm handleColumnRadioButton={setState.toggleAllColumnChecked}
onChange={setState.cleanupConfig} />
cleanupConfig={ <hr className="my-md" />
state?.cleanupConfig || defaultState.cleanupConfig <RetryConfEditor
} conf={state.retryConf}
/> setRetryConf={setState.retryConf}
</div> currentTrigger={currentTrigger}
)} save={saveWrapper('retry_conf')}
<HeadersEditor />
headers={state.headers} <hr className="my-md" />
setHeaders={setState.headers} {isProConsole(window.__env) && (
currentTrigger={currentTrigger} <div className="mb-md">
save={saveWrapper('headers')} <AutoCleanupForm
/> onChange={setState.cleanupConfig}
<ConfigureTransformation cleanupConfig={state?.cleanupConfig}
transformationType="event" />
requestTransfromState={transformState} </div>
resetSampleInput={resetSampleInput} )}
envVarsOnChange={envVarsOnChange} <HeadersEditor
sessionVarsOnChange={sessionVarsOnChange} headers={state.headers}
requestMethodOnChange={requestMethodOnChange} setHeaders={setState.headers}
requestUrlOnChange={requestUrlOnChange} currentTrigger={currentTrigger}
requestQueryParamsOnChange={requestQueryParamsOnChange} save={saveWrapper('headers')}
requestAddHeadersOnChange={requestAddHeadersOnChange} />
requestBodyOnChange={requestBodyOnChange} <ConfigureTransformation
requestSampleInputOnChange={requestSampleInputOnChange} transformationType="event"
requestContentTypeOnChange={requestContentTypeOnChange} requestTransfromState={transformState}
requestUrlTransformOnChange={requestUrlTransformOnChange} resetSampleInput={resetSampleInput}
requestPayloadTransformOnChange={requestPayloadTransformOnChange} envVarsOnChange={envVarsOnChange}
/> sessionVarsOnChange={sessionVarsOnChange}
{!readOnlyMode && ( requestMethodOnChange={requestMethodOnChange}
<div className="mb-md"> requestUrlOnChange={requestUrlOnChange}
<span className="mr-md"> requestQueryParamsOnChange={requestQueryParamsOnChange}
requestAddHeadersOnChange={requestAddHeadersOnChange}
requestBodyOnChange={requestBodyOnChange}
requestSampleInputOnChange={requestSampleInputOnChange}
requestContentTypeOnChange={requestContentTypeOnChange}
requestUrlTransformOnChange={requestUrlTransformOnChange}
requestPayloadTransformOnChange={
requestPayloadTransformOnChange
}
/>
{!readOnlyMode && (
<div className="mb-md">
<span className="mr-md">
<Button
mode="primary"
type="submit"
data-test="save-modify-trigger-changes"
>
Save Event Trigger
</Button>
</span>
<Button <Button
mode="primary" mode="destructive"
type="submit" data-test="delete-trigger"
onClick={() => { onClick={deleteWrapper}
saveWrapper()();
}}
data-test="save-modify-trigger-changes"
> >
Save Event Trigger Delete Event Trigger
</Button> </Button>
</span> </div>
<Button )}
mode="destructive" </div>
data-test="delete-trigger" </form>
onClick={deleteWrapper}
>
Delete Event Trigger
</Button>
</div>
)}
</div>
</div> </div>
</div> </div>
</Analytics> </Analytics>

View File

@ -56,14 +56,6 @@ export const defaultState: LocalEventTriggerState = {
headers: [defaultHeader], headers: [defaultHeader],
source: '', source: '',
isAllColumnChecked: true, isAllColumnChecked: true,
cleanupConfig: {
schedule: '0 0 * * *',
batch_size: 10000,
clear_older_than: 168,
timeout: 60,
clean_invocation_logs: false,
paused: true,
},
}; };
export const parseServerETDefinition = ( export const parseServerETDefinition = (

View File

@ -64,7 +64,9 @@ const triggerRoutes = (
<Route path=":triggerName" component={TriggerContainerConnector}> <Route path=":triggerName" component={TriggerContainerConnector}>
<Route <Route
path={getETModifyRoute({ type: 'relative' })} path={getETModifyRoute({ type: 'relative' })}
component={ModifyEventTrigger} component={props => (
<ModifyEventTrigger {...props} key={props?.params?.triggerName} />
)}
/> />
<Route <Route
path={getETPendingEventsRoute('relative')} path={getETPendingEventsRoute('relative')}

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import clsx from 'clsx';
import { IconTooltip } from '../Tooltip'; import { IconTooltip } from '../Tooltip';
export const InputSection = (props: { export const InputSection = (props: {
@ -7,8 +8,11 @@ export const InputSection = (props: {
onChange: (value: string) => void; onChange: (value: string) => void;
placeholder: string; placeholder: string;
tooltip: string; tooltip: string;
disabled?: boolean;
required?: boolean;
}) => { }) => {
const { label, value, onChange, placeholder, tooltip } = props; const { label, value, onChange, placeholder, tooltip, disabled, required } =
props;
return ( return (
<div className="mb-sm"> <div className="mb-sm">
<label className="flex items-center mb-xs font-semibold text-muted"> <label className="flex items-center mb-xs font-semibold text-muted">
@ -20,7 +24,12 @@ export const InputSection = (props: {
value={value} value={value}
onChange={event => onChange(event.target.value)} onChange={event => onChange(event.target.value)}
placeholder={placeholder} placeholder={placeholder}
className="block w-full h-input shadow-sm rounded border border-gray-300 hover:border-gray-400 focus:outline-0 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400" className={clsx(
'block w-full h-input shadow-sm rounded border border-gray-300 hover:border-gray-400 focus:outline-0 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400',
disabled ? 'cursor-not-allowed bg-gray-200' : 'cursor-pointer'
)}
disabled={disabled}
required={required}
/> />
</div> </div>
); );