mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
console: add auto clean form on events tab
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5956 Co-authored-by: Daniele Cammareri <5709409+dancamma@users.noreply.github.com> GitOrigin-RevId: febb6ae707c9ac9c1e77a25aa01c2d2283c0d7d9
This commit is contained in:
parent
e369f567c3
commit
a7c0b02296
@ -1,8 +1,8 @@
|
|||||||
|
/* eslint-disable no-underscore-dangle */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { browserHistory } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
|
|
||||||
import { Tabs } from '@/new-components/Tabs';
|
import { Tabs } from '@/new-components/Tabs';
|
||||||
import { useConsoleConfig } from '@/hooks/useEnvVars';
|
|
||||||
import {
|
import {
|
||||||
useQueryCollections,
|
useQueryCollections,
|
||||||
QueryCollectionsOperations,
|
QueryCollectionsOperations,
|
||||||
@ -11,6 +11,7 @@ import {
|
|||||||
import { AllowListSidebar, AllowListPermissions } from '@/features/AllowLists';
|
import { AllowListSidebar, AllowListPermissions } from '@/features/AllowLists';
|
||||||
|
|
||||||
import PageContainer from '@/components/Common/Layout/PageContainer/PageContainer';
|
import PageContainer from '@/components/Common/Layout/PageContainer/PageContainer';
|
||||||
|
import { isProConsole } from '@/utils/proConsole';
|
||||||
|
|
||||||
interface AllowListDetailProps {
|
interface AllowListDetailProps {
|
||||||
params: {
|
params: {
|
||||||
@ -35,8 +36,6 @@ export const AllowListDetail: React.FC<AllowListDetailProps> = props => {
|
|||||||
isRefetching,
|
isRefetching,
|
||||||
} = useQueryCollections();
|
} = useQueryCollections();
|
||||||
|
|
||||||
const { type } = useConsoleConfig();
|
|
||||||
|
|
||||||
const queryCollection = queryCollections?.find(
|
const queryCollection = queryCollections?.find(
|
||||||
({ name: collectionName }) => collectionName === name
|
({ name: collectionName }) => collectionName === name
|
||||||
);
|
);
|
||||||
@ -86,7 +85,7 @@ export const AllowListDetail: React.FC<AllowListDetailProps> = props => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{type !== 'oss' ? (
|
{isProConsole(window.__env) ? (
|
||||||
<Tabs
|
<Tabs
|
||||||
value={section}
|
value={section}
|
||||||
onValueChange={value => {
|
onValueChange={value => {
|
||||||
|
@ -54,6 +54,7 @@ import {
|
|||||||
ETOperationColumn,
|
ETOperationColumn,
|
||||||
EventTriggerOperation,
|
EventTriggerOperation,
|
||||||
RetryConf,
|
RetryConf,
|
||||||
|
EventTriggerAutoCleanup,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
interface Props extends InjectedProps {}
|
interface Props extends InjectedProps {}
|
||||||
@ -317,6 +318,10 @@ const Add: React.FC<Props> = props => {
|
|||||||
setState.operations(o);
|
setState.operations(o);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAutoCleanupChange = (config: EventTriggerAutoCleanup) => {
|
||||||
|
setState.cleanupConfig(config);
|
||||||
|
};
|
||||||
|
|
||||||
const handleOperationsColumnsChange = (oc: ETOperationColumn[]) => {
|
const handleOperationsColumnsChange = (oc: ETOperationColumn[]) => {
|
||||||
setState.operationColumns(oc);
|
setState.operationColumns(oc);
|
||||||
};
|
};
|
||||||
@ -361,6 +366,7 @@ const Add: React.FC<Props> = props => {
|
|||||||
handleRetryConfChange={handleRetryConfChange}
|
handleRetryConfChange={handleRetryConfChange}
|
||||||
handleHeadersChange={handleHeadersChange}
|
handleHeadersChange={handleHeadersChange}
|
||||||
handleToggleAllColumn={setState.toggleAllColumnChecked}
|
handleToggleAllColumn={setState.toggleAllColumnChecked}
|
||||||
|
handleAutoCleanupChange={handleAutoCleanupChange}
|
||||||
/>
|
/>
|
||||||
<ConfigureTransformation
|
<ConfigureTransformation
|
||||||
transformationType="event"
|
transformationType="event"
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
/* eslint-disable no-underscore-dangle */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Collapsible } from '@/new-components/Collapsible';
|
||||||
|
import { isProConsole } from '@/utils/proConsole';
|
||||||
import { LocalEventTriggerState } from '../state';
|
import { LocalEventTriggerState } from '../state';
|
||||||
import Headers, { Header } from '../../../../Common/Headers/Headers';
|
import Headers, { Header } from '../../../../Common/Headers/Headers';
|
||||||
import CollapsibleToggle from '../../../../Common/CollapsibleToggle/CollapsibleToggle';
|
|
||||||
import RetryConfEditor from '../../Common/Components/RetryConfEditor';
|
import RetryConfEditor from '../../Common/Components/RetryConfEditor';
|
||||||
import * as tooltip from '../Common/Tooltips';
|
import * as tooltip from '../Common/Tooltips';
|
||||||
import { Operations } from '../Common/Operations';
|
import { Operations } from '../Common/Operations';
|
||||||
@ -12,11 +14,13 @@ import {
|
|||||||
ETOperationColumn,
|
ETOperationColumn,
|
||||||
EventTriggerOperation,
|
EventTriggerOperation,
|
||||||
RetryConf,
|
RetryConf,
|
||||||
|
EventTriggerAutoCleanup,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import ColumnList from '../Common/ColumnList';
|
import ColumnList from '../Common/ColumnList';
|
||||||
import FormLabel from './FormLabel';
|
import FormLabel from './FormLabel';
|
||||||
import DebouncedDropdownInput from '../Common/DropdownWrapper';
|
import DebouncedDropdownInput from '../Common/DropdownWrapper';
|
||||||
import { inputStyles, heading } from '../../constants';
|
import { inputStyles, heading } from '../../constants';
|
||||||
|
import { AutoCleanupForm } from '../Common/AutoCleanupForm';
|
||||||
|
|
||||||
type CreateETFormProps = {
|
type CreateETFormProps = {
|
||||||
state: LocalEventTriggerState;
|
state: LocalEventTriggerState;
|
||||||
@ -34,6 +38,7 @@ type CreateETFormProps = {
|
|||||||
handleRetryConfChange: (r: RetryConf) => void;
|
handleRetryConfChange: (r: RetryConf) => void;
|
||||||
handleHeadersChange: (h: Header[]) => void;
|
handleHeadersChange: (h: Header[]) => void;
|
||||||
handleToggleAllColumn: () => void;
|
handleToggleAllColumn: () => void;
|
||||||
|
handleAutoCleanupChange: (config: EventTriggerAutoCleanup) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CreateETForm: React.FC<CreateETFormProps> = props => {
|
const CreateETForm: React.FC<CreateETFormProps> = props => {
|
||||||
@ -48,6 +53,7 @@ const CreateETForm: React.FC<CreateETFormProps> = props => {
|
|||||||
operations,
|
operations,
|
||||||
operationColumns,
|
operationColumns,
|
||||||
isAllColumnChecked,
|
isAllColumnChecked,
|
||||||
|
cleanupConfig,
|
||||||
},
|
},
|
||||||
databaseInfo,
|
databaseInfo,
|
||||||
dataSourcesList,
|
dataSourcesList,
|
||||||
@ -63,9 +69,11 @@ const CreateETForm: React.FC<CreateETFormProps> = props => {
|
|||||||
handleRetryConfChange,
|
handleRetryConfChange,
|
||||||
handleHeadersChange,
|
handleHeadersChange,
|
||||||
handleToggleAllColumn,
|
handleToggleAllColumn,
|
||||||
|
handleAutoCleanupChange,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const supportedDrivers = getSupportedDrivers('events.triggers.add');
|
const supportedDrivers = getSupportedDrivers('events.triggers.add');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormLabel
|
<FormLabel
|
||||||
@ -190,9 +198,25 @@ const CreateETForm: React.FC<CreateETFormProps> = props => {
|
|||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<hr className="my-md" />
|
<hr className="my-md" />
|
||||||
<CollapsibleToggle
|
{isProConsole(window.__env) && (
|
||||||
title={<h4 className={heading}>Advanced Settings</h4>}
|
<>
|
||||||
testId="advanced-settings"
|
<div className="mb-md">
|
||||||
|
<div className="mb-md cursor-pointer">
|
||||||
|
<AutoCleanupForm
|
||||||
|
cleanupConfig={cleanupConfig}
|
||||||
|
onChange={handleAutoCleanupChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr className="my-md" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Collapsible
|
||||||
|
triggerChildren={
|
||||||
|
<h2 className="text-lg font-semibold mb-xs flex items-center mb-0">
|
||||||
|
Advanced Settings
|
||||||
|
</h2>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
@ -232,7 +256,7 @@ const CreateETForm: React.FC<CreateETFormProps> = props => {
|
|||||||
<Headers headers={headers} setHeaders={handleHeadersChange} />
|
<Headers headers={headers} setHeaders={handleHeadersChange} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleToggle>
|
</Collapsible>
|
||||||
<hr className="my-md" />
|
<hr className="my-md" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,151 @@
|
|||||||
|
import defaultState from '@/components/Services/Events/EventTriggers/state';
|
||||||
|
import { Collapsible } from '@/new-components/Collapsible';
|
||||||
|
import { DropdownButton } from '@/new-components/DropdownButton';
|
||||||
|
import { InputSection } from '@/new-components/InputSetionWithoutForm';
|
||||||
|
import { Switch } from '@/new-components/Switch';
|
||||||
|
import React from 'react';
|
||||||
|
import { EventTriggerAutoCleanup } from '../../types';
|
||||||
|
|
||||||
|
interface AutoCleanupFormProps {
|
||||||
|
cleanupConfig: EventTriggerAutoCleanup;
|
||||||
|
onChange: (cleanupConfig: EventTriggerAutoCleanup) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const crons = [
|
||||||
|
{ value: '* * * * *', label: 'Every Minute' },
|
||||||
|
{ value: '*/10 * * * *', label: 'Every 10 Minutes' },
|
||||||
|
{ value: '0 0 * * *', label: 'Every Midnight' },
|
||||||
|
{ value: '0 0 1 * *', label: 'Every Month Start' },
|
||||||
|
{ value: '0 12 * * 5', label: 'Every Friday Noon' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const AutoCleanupForm = (props: AutoCleanupFormProps) => {
|
||||||
|
const { cleanupConfig, onChange } = props;
|
||||||
|
return (
|
||||||
|
<div className="w-1/2">
|
||||||
|
<Collapsible
|
||||||
|
triggerChildren={
|
||||||
|
<h2 className="text-lg font-semibold mb-xs flex items-center mb-0">
|
||||||
|
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="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
|
||||||
|
triggerChildren={
|
||||||
|
<h2 className="text-lg font-semibold mb-xs flex items-center mb-0">
|
||||||
|
Advanced Settings
|
||||||
|
</h2>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<InputSection
|
||||||
|
label="Timeout (seconds)"
|
||||||
|
tooltip="Timeout for the query (in seconds, default: 60)"
|
||||||
|
placeholder={defaultState.cleanupConfig.timeout?.toString() || ''}
|
||||||
|
value={cleanupConfig.timeout?.toString() || ''}
|
||||||
|
onChange={value => {
|
||||||
|
onChange({
|
||||||
|
...cleanupConfig,
|
||||||
|
timeout: value ? parseInt(value, 10) : undefined,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InputSection
|
||||||
|
label="Batch Size"
|
||||||
|
tooltip="Number of event trigger logs to delete in a batch (default: 10,000)"
|
||||||
|
placeholder={
|
||||||
|
defaultState.cleanupConfig.batch_size?.toString() || ''
|
||||||
|
}
|
||||||
|
value={cleanupConfig.batch_size?.toString() || ''}
|
||||||
|
onChange={value => {
|
||||||
|
onChange({
|
||||||
|
...cleanupConfig,
|
||||||
|
batch_size: value ? parseInt(value, 10) : undefined,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Collapsible>
|
||||||
|
</Collapsible>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-underscore-dangle */
|
||||||
import React, { useEffect, useReducer } from 'react';
|
import React, { useEffect, useReducer } from 'react';
|
||||||
import { connect, ConnectedProps } from 'react-redux';
|
import { connect, ConnectedProps } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
@ -31,12 +32,14 @@ import {
|
|||||||
import ConfigureTransformation from '@/components/Common/ConfigureTransformation/ConfigureTransformation';
|
import ConfigureTransformation from '@/components/Common/ConfigureTransformation/ConfigureTransformation';
|
||||||
import requestAction from '@/utils/requestAction';
|
import requestAction from '@/utils/requestAction';
|
||||||
import Endpoints from '@/Endpoints';
|
import Endpoints from '@/Endpoints';
|
||||||
|
import defaultState from '@/components/Services/Events/EventTriggers/state';
|
||||||
import {
|
import {
|
||||||
getValidateTransformOptions,
|
getValidateTransformOptions,
|
||||||
parseValidateApiData,
|
parseValidateApiData,
|
||||||
getTransformState,
|
getTransformState,
|
||||||
} from '@/components/Common/ConfigureTransformation/utils';
|
} from '@/components/Common/ConfigureTransformation/utils';
|
||||||
import { Button } from '@/new-components/Button';
|
import { Button } from '@/new-components/Button';
|
||||||
|
import { isProConsole } from '@/utils/proConsole';
|
||||||
import { getSourceDriver } from '../../../Data/utils';
|
import { getSourceDriver } from '../../../Data/utils';
|
||||||
import { mapDispatchToPropsEmpty } from '../../../../Common/utils/reactUtils';
|
import { mapDispatchToPropsEmpty } from '../../../../Common/utils/reactUtils';
|
||||||
import { getEventRequestSampleInput } from '../utils';
|
import { getEventRequestSampleInput } from '../utils';
|
||||||
@ -57,6 +60,7 @@ import {
|
|||||||
getDataSources,
|
getDataSources,
|
||||||
getEventTriggerByName,
|
getEventTriggerByName,
|
||||||
} from '../../../../../metadata/selector';
|
} from '../../../../../metadata/selector';
|
||||||
|
import { AutoCleanupForm } from '../Common/AutoCleanupForm';
|
||||||
|
|
||||||
interface Props extends InjectedProps {}
|
interface Props extends InjectedProps {}
|
||||||
|
|
||||||
@ -384,6 +388,16 @@ const Modify: React.FC<Props> = props => {
|
|||||||
requestUrlTransformOnChange={requestUrlTransformOnChange}
|
requestUrlTransformOnChange={requestUrlTransformOnChange}
|
||||||
requestPayloadTransformOnChange={requestPayloadTransformOnChange}
|
requestPayloadTransformOnChange={requestPayloadTransformOnChange}
|
||||||
/>
|
/>
|
||||||
|
{isProConsole(window.__env) && (
|
||||||
|
<div className="mb-md">
|
||||||
|
<AutoCleanupForm
|
||||||
|
onChange={setState.cleanupConfig}
|
||||||
|
cleanupConfig={
|
||||||
|
state?.cleanupConfig || defaultState.cleanupConfig
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{!readOnlyMode && (
|
{!readOnlyMode && (
|
||||||
<div className="mb-md">
|
<div className="mb-md">
|
||||||
<span className="mr-md">
|
<span className="mr-md">
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
EventTrigger,
|
EventTrigger,
|
||||||
RetryConf,
|
RetryConf,
|
||||||
DatabaseInfo,
|
DatabaseInfo,
|
||||||
|
EventTriggerAutoCleanup,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { Header, defaultHeader } from '../../../Common/Headers/Headers';
|
import { Header, defaultHeader } from '../../../Common/Headers/Headers';
|
||||||
import {
|
import {
|
||||||
@ -27,9 +28,10 @@ export type LocalEventTriggerState = {
|
|||||||
headers: Header[];
|
headers: Header[];
|
||||||
source: string;
|
source: string;
|
||||||
isAllColumnChecked: boolean;
|
isAllColumnChecked: boolean;
|
||||||
|
cleanupConfig: EventTriggerAutoCleanup;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultState: LocalEventTriggerState = {
|
export const defaultState: LocalEventTriggerState = {
|
||||||
name: '',
|
name: '',
|
||||||
table: {
|
table: {
|
||||||
name: '',
|
name: '',
|
||||||
@ -54,6 +56,14 @@ 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 = (
|
||||||
@ -89,6 +99,8 @@ export const parseServerETDefinition = (
|
|||||||
retryConf: etConf.retry_conf,
|
retryConf: etConf.retry_conf,
|
||||||
headers: parseServerHeaders(eventTrigger.configuration.headers),
|
headers: parseServerHeaders(eventTrigger.configuration.headers),
|
||||||
isAllColumnChecked: etDef?.update?.columns === '*',
|
isAllColumnChecked: etDef?.update?.columns === '*',
|
||||||
|
cleanupConfig:
|
||||||
|
eventTrigger.configuration?.cleanup_config ?? defaultState.cleanupConfig,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -175,6 +187,12 @@ export const useEventTrigger = (initState?: LocalEventTriggerState) => {
|
|||||||
isAllColumnChecked: !s.isAllColumnChecked,
|
isAllColumnChecked: !s.isAllColumnChecked,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
cleanupConfig: (cleanupConfig: EventTriggerAutoCleanup) => {
|
||||||
|
setState(s => ({
|
||||||
|
...s,
|
||||||
|
cleanupConfig,
|
||||||
|
}));
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -64,6 +64,7 @@ import _push from '../Data/push';
|
|||||||
import { RequestTransformState } from '../../Common/ConfigureTransformation/stateDefaults';
|
import { RequestTransformState } from '../../Common/ConfigureTransformation/stateDefaults';
|
||||||
import { getRequestTransformObject } from '../../Common/ConfigureTransformation/utils';
|
import { getRequestTransformObject } from '../../Common/ConfigureTransformation/utils';
|
||||||
import { getSourceDriver } from '../Data/utils';
|
import { getSourceDriver } from '../Data/utils';
|
||||||
|
import defaultState from '../Events/EventTriggers/state';
|
||||||
|
|
||||||
export const addScheduledTrigger =
|
export const addScheduledTrigger =
|
||||||
(
|
(
|
||||||
@ -460,6 +461,10 @@ export const modifyEventTrigger =
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
upQuery.args.cleanup_config = {
|
||||||
|
...defaultState.cleanupConfig,
|
||||||
|
...state.cleanupConfig,
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const migration = new Migration();
|
const migration = new Migration();
|
||||||
|
@ -82,6 +82,15 @@ export type ETOperationColumn = {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type EventTriggerAutoCleanup = {
|
||||||
|
schedule?: string;
|
||||||
|
batch_size?: number;
|
||||||
|
clear_older_than?: number;
|
||||||
|
timeout?: number;
|
||||||
|
clean_invocation_logs: boolean;
|
||||||
|
paused: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type EventTriggerOperationDefinition = {
|
export type EventTriggerOperationDefinition = {
|
||||||
columns: string[] | '*';
|
columns: string[] | '*';
|
||||||
};
|
};
|
||||||
@ -100,6 +109,7 @@ export type EventTrigger = {
|
|||||||
retry_conf: RetryConf;
|
retry_conf: RetryConf;
|
||||||
webhook: Nullable<string>;
|
webhook: Nullable<string>;
|
||||||
webhook_from_env?: Nullable<string>;
|
webhook_from_env?: Nullable<string>;
|
||||||
|
cleanup_config?: EventTriggerAutoCleanup;
|
||||||
};
|
};
|
||||||
request_transform?: RequestTransform;
|
request_transform?: RequestTransform;
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Button } from '@/new-components/Button';
|
/* eslint-disable no-underscore-dangle */
|
||||||
import { useConsoleConfig } from '@/hooks/useEnvVars';
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Button } from '@/new-components/Button';
|
||||||
|
import { isProConsole } from '@/utils/proConsole';
|
||||||
import { FaFolderPlus } from 'react-icons/fa';
|
import { FaFolderPlus } from 'react-icons/fa';
|
||||||
import { QueryCollectionCreateDialog } from './QueryCollectionCreateDialog';
|
import { QueryCollectionCreateDialog } from './QueryCollectionCreateDialog';
|
||||||
import { AllowListStatus } from './AllowListStatus';
|
import { AllowListStatus } from './AllowListStatus';
|
||||||
@ -13,7 +13,6 @@ interface AllowListSidebarHeaderProps {
|
|||||||
export const AllowListSidebarHeader = (props: AllowListSidebarHeaderProps) => {
|
export const AllowListSidebarHeader = (props: AllowListSidebarHeaderProps) => {
|
||||||
const { onQueryCollectionCreate } = props;
|
const { onQueryCollectionCreate } = props;
|
||||||
const [isCreateModalOpen, setIsCreateModalOpen] = React.useState(false);
|
const [isCreateModalOpen, setIsCreateModalOpen] = React.useState(false);
|
||||||
const { type } = useConsoleConfig();
|
|
||||||
return (
|
return (
|
||||||
<div className="pb-4">
|
<div className="pb-4">
|
||||||
{isCreateModalOpen && (
|
{isCreateModalOpen && (
|
||||||
@ -31,7 +30,7 @@ export const AllowListSidebarHeader = (props: AllowListSidebarHeaderProps) => {
|
|||||||
<AllowListStatus />
|
<AllowListStatus />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{type !== 'oss' && (
|
{isProConsole(window.__env) && (
|
||||||
<div className="mt-2 2xl:mt-0 2xl:ml-auto">
|
<div className="mt-2 2xl:mt-0 2xl:ml-auto">
|
||||||
<Button
|
<Button
|
||||||
icon={<FaFolderPlus />}
|
icon={<FaFolderPlus />}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import defaultState from '@/components/Services/Events/EventTriggers/state';
|
||||||
import {
|
import {
|
||||||
ColumnConfig,
|
ColumnConfig,
|
||||||
CustomRootFields,
|
CustomRootFields,
|
||||||
@ -523,6 +524,10 @@ export const generateCreateEventTriggerQuery = (
|
|||||||
enable_manual: state.operations.enable_manual,
|
enable_manual: state.operations.enable_manual,
|
||||||
retry_conf: state.retryConf,
|
retry_conf: state.retryConf,
|
||||||
headers: transformHeaders(state.headers),
|
headers: transformHeaders(state.headers),
|
||||||
|
cleanup_config: {
|
||||||
|
...defaultState.cleanupConfig,
|
||||||
|
...state.cleanupConfig,
|
||||||
|
},
|
||||||
replace,
|
replace,
|
||||||
request_transform: requestTransform,
|
request_transform: requestTransform,
|
||||||
},
|
},
|
||||||
|
@ -291,6 +291,7 @@ export const getEventTriggers = createSelector(getMetadata, metadata => {
|
|||||||
retry_conf: trigger.retry_conf,
|
retry_conf: trigger.retry_conf,
|
||||||
webhook: trigger.webhook || '',
|
webhook: trigger.webhook || '',
|
||||||
webhook_from_env: trigger.webhook_from_env,
|
webhook_from_env: trigger.webhook_from_env,
|
||||||
|
cleanup_config: trigger.cleanup_config,
|
||||||
},
|
},
|
||||||
request_transform: trigger.request_transform,
|
request_transform: trigger.request_transform,
|
||||||
})) || [];
|
})) || [];
|
||||||
@ -340,6 +341,7 @@ export const getEventTriggerByName = createSelector(
|
|||||||
retry_conf: trigger.retry_conf,
|
retry_conf: trigger.retry_conf,
|
||||||
webhook: trigger.webhook || '',
|
webhook: trigger.webhook || '',
|
||||||
webhook_from_env: trigger.webhook_from_env,
|
webhook_from_env: trigger.webhook_from_env,
|
||||||
|
cleanup_config: trigger.cleanup_config,
|
||||||
},
|
},
|
||||||
request_transform: trigger.request_transform,
|
request_transform: trigger.request_transform,
|
||||||
};
|
};
|
||||||
|
@ -422,6 +422,15 @@ export interface ComputedFieldDefinition {
|
|||||||
* NOTE: The metadata type doesn't QUITE match the 'create' arguments here
|
* NOTE: The metadata type doesn't QUITE match the 'create' arguments here
|
||||||
* https://hasura.io/docs/latest/graphql/core/api-reference/schema-metadata-api/event-triggers.html#create-event-trigger
|
* https://hasura.io/docs/latest/graphql/core/api-reference/schema-metadata-api/event-triggers.html#create-event-trigger
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export interface EventTriggerAutoCleanup {
|
||||||
|
schedule: string;
|
||||||
|
batch_size: number;
|
||||||
|
clear_older_than: number;
|
||||||
|
timeout: number;
|
||||||
|
clean_invocation_logs: boolean;
|
||||||
|
paused: boolean;
|
||||||
|
}
|
||||||
export interface EventTrigger {
|
export interface EventTrigger {
|
||||||
/** Name of the event trigger */
|
/** Name of the event trigger */
|
||||||
name: TriggerName;
|
name: TriggerName;
|
||||||
@ -436,6 +445,8 @@ export interface EventTrigger {
|
|||||||
headers?: ServerHeader[];
|
headers?: ServerHeader[];
|
||||||
/** Request transformation object */
|
/** Request transformation object */
|
||||||
request_transform?: RequestTransform;
|
request_transform?: RequestTransform;
|
||||||
|
/** Auto-cleanup configuration object */
|
||||||
|
cleanup_config?: EventTriggerAutoCleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EventTriggerDefinition {
|
export interface EventTriggerDefinition {
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
import { within, userEvent, screen } from '@storybook/testing-library';
|
||||||
|
import { expect } from '@storybook/jest';
|
||||||
|
import { Button } from '@/new-components/Button';
|
||||||
|
|
||||||
|
import { DropdownButton } from './DropdownButton';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'components/Dropdown Button 🧬',
|
||||||
|
parameters: {
|
||||||
|
docs: { source: { type: 'code' } },
|
||||||
|
chromatic: { disableSnapshot: true },
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
Story => (
|
||||||
|
<div className="p-4 flex gap-5 items-center max-w-screen">{Story()}</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
component: DropdownButton,
|
||||||
|
argTypes: {
|
||||||
|
onClick: { action: true },
|
||||||
|
},
|
||||||
|
} as ComponentMeta<typeof DropdownButton>;
|
||||||
|
|
||||||
|
export const Default: ComponentStory<typeof Button> = ({ onClick }) => (
|
||||||
|
<DropdownButton
|
||||||
|
data-testid="dropdown-button"
|
||||||
|
items={[
|
||||||
|
[
|
||||||
|
<span onClick={onClick}>Action</span>,
|
||||||
|
<span onClick={onClick} className="text-red-600">
|
||||||
|
Destructive Action
|
||||||
|
</span>,
|
||||||
|
],
|
||||||
|
[<span onClick={onClick}>Another action</span>],
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
The DropdownButton label
|
||||||
|
</DropdownButton>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ApiPlayground: ComponentStory<typeof Button> = args => (
|
||||||
|
<DropdownButton
|
||||||
|
items={[
|
||||||
|
['Action', <span className="text-red-600">Destructive Action</span>],
|
||||||
|
['Another action'],
|
||||||
|
]}
|
||||||
|
{...args}
|
||||||
|
>
|
||||||
|
The DropdownButton label
|
||||||
|
</DropdownButton>
|
||||||
|
);
|
||||||
|
|
||||||
|
Default.play = async ({ args, canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
// click the trigger
|
||||||
|
userEvent.click(canvas.getByTestId('dropdown-button'));
|
||||||
|
// the menu is visible
|
||||||
|
expect(screen.getByText('Another action')).toBeVisible();
|
||||||
|
// click the item
|
||||||
|
userEvent.click(screen.getByText('Another action'));
|
||||||
|
// the menu is not visible
|
||||||
|
expect(screen.queryByText('Another action')).not.toBeInTheDocument();
|
||||||
|
// the action is called
|
||||||
|
expect(args.onClick).toHaveBeenCalled();
|
||||||
|
};
|
23
console/src/new-components/DropdownButton/DropdownButton.tsx
Normal file
23
console/src/new-components/DropdownButton/DropdownButton.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FaChevronDown } from 'react-icons/fa';
|
||||||
|
import { Button } from '../Button';
|
||||||
|
import { DropdownMenu } from '../DropdownMenu';
|
||||||
|
|
||||||
|
interface DropdownButtonProps extends React.ComponentProps<typeof Button> {
|
||||||
|
items: React.ReactNode[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DropdownButton: React.FC<DropdownButtonProps> = ({
|
||||||
|
items,
|
||||||
|
...rest
|
||||||
|
}) => (
|
||||||
|
<DropdownMenu items={items}>
|
||||||
|
<Button
|
||||||
|
iconPosition="end"
|
||||||
|
icon={
|
||||||
|
<FaChevronDown className="transition-transform group-radix-state-open:rotate-180 w-3 h-3" />
|
||||||
|
}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
1
console/src/new-components/DropdownButton/index.ts
Normal file
1
console/src/new-components/DropdownButton/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './DropdownButton';
|
@ -57,28 +57,6 @@ import { DropdownMenu } from '@/new-components/DropdownMenu';
|
|||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story name="Dropdown Button">
|
|
||||||
<div>
|
|
||||||
<DropdownMenu
|
|
||||||
items={[
|
|
||||||
['Action', <span className="text-red-600">Destructive Action</span>],
|
|
||||||
['Another action'],
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
iconPosition="end"
|
|
||||||
icon={
|
|
||||||
<FaChevronUp className="transition-transform group-radix-state-open:rotate-180" />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Dropdown Button
|
|
||||||
</Button>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
#### 🚦 Usage
|
#### 🚦 Usage
|
||||||
|
|
||||||
- The dropdownMenu button is used for opening a contextual menu with list of actions
|
- The dropdownMenu button is used for opening a contextual menu with list of actions
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { IconTooltip } from '../Tooltip';
|
||||||
|
|
||||||
|
export const InputSection = (props: {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
placeholder: string;
|
||||||
|
tooltip: string;
|
||||||
|
}) => {
|
||||||
|
const { label, value, onChange, placeholder, tooltip } = props;
|
||||||
|
return (
|
||||||
|
<div className="mb-sm">
|
||||||
|
<label className="flex items-center mb-xs font-semibold text-muted">
|
||||||
|
{label}
|
||||||
|
<IconTooltip message={tooltip} />
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={value}
|
||||||
|
onChange={event => onChange(event.target.value)}
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export { InputSection } from './InputSection';
|
@ -3,11 +3,12 @@ import * as RadixSwitch from '@radix-ui/react-switch';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
export const Switch = (props: RadixSwitch.SwitchProps) => {
|
export const Switch = (props: RadixSwitch.SwitchProps) => {
|
||||||
const { checked } = props;
|
const { checked, disabled } = props;
|
||||||
return (
|
return (
|
||||||
<RadixSwitch.Root
|
<RadixSwitch.Root
|
||||||
className={clsx(
|
className={clsx(
|
||||||
checked ? 'bg-green-600' : 'bg-gray-200',
|
checked ? 'bg-green-600' : 'bg-gray-200',
|
||||||
|
disabled ? 'cursor-not-allowed opacity-40' : 'cursor-pointer',
|
||||||
'relative inline-flex shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500'
|
'relative inline-flex shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500'
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
Loading…
Reference in New Issue
Block a user