diff --git a/client/packages/lowcoder/src/comps/queries/queryComp.tsx b/client/packages/lowcoder/src/comps/queries/queryComp.tsx index 1937bf289..e90bf8d19 100644 --- a/client/packages/lowcoder/src/comps/queries/queryComp.tsx +++ b/client/packages/lowcoder/src/comps/queries/queryComp.tsx @@ -97,10 +97,25 @@ interface AfterExecuteQueryAction { result: QueryResult; } -const TriggerTypeOptions = [ +const CommonTriggerOptions = [ + { label: trans("query.triggerTypeInputChange"), value: "onInputChange"}, + { label: trans("query.triggerTypeQueryExec"), value: "onQueryExecution"}, + { label: trans("query.triggerTypeTimeout"), value: "onTimeout"}, +] + +export const TriggerTypeOptions = [ + { label: trans("query.triggerTypePageLoad"), value: "onPageLoad"}, + ...CommonTriggerOptions, { label: trans("query.triggerTypeAuto"), value: "automatic" }, { label: trans("query.triggerTypeManual"), value: "manual" }, ] as const; + +export const JSTriggerTypeOptions = [ + ...CommonTriggerOptions, + { label: trans("query.triggerTypePageLoad"), value: "automatic" }, + { label: trans("query.triggerTypeManual"), value: "manual" }, +]; + export type TriggerType = ValueFromOption; const EventOptions = [ @@ -151,6 +166,13 @@ const childrenMap = { }, }), cancelPrevious: withDefault(BoolPureControl, false), + // use only for onQueryExecution trigger + depQueryName: SimpleNameComp, + // use only for onTimeout trigger, triggers query after x time passed on page load + delayTime: millisecondsControl({ + left: 0, + defaultValue: 5 * 1000, + }) }; let QueryCompTmp = withTypeAndChildren>( @@ -174,6 +196,7 @@ export type QueryChildrenType = InstanceType extends MultiB ? X : never; +let blockInputChangeQueries = true; /** * Logic to automatically trigger execution */ @@ -222,11 +245,17 @@ QueryCompTmp = class extends QueryCompTmp { const isJsQuery = this.children.compType.getView() === "js"; const notExecuted = this.children.lastQueryStartTime.getView() === -1; const isAutomatic = getTriggerType(this) === "automatic"; + const isPageLoadTrigger = getTriggerType(this) === "onPageLoad"; + const isInputChangeTrigger = getTriggerType(this) === "onInputChange"; if ( - action.type === CompActionTypes.UPDATE_NODES_V2 && - isAutomatic && - (!isJsQuery || (isJsQuery && notExecuted)) // query which has deps can be executed on page load(first time) + action.type === CompActionTypes.UPDATE_NODES_V2 + && ( + isAutomatic + || isInputChangeTrigger + || (isPageLoadTrigger && notExecuted) + ) + // && (!isJsQuery || (isJsQuery && notExecuted)) // query which has deps can be executed on page load(first time) ) { const next = super.reduce(action); const depends = this.children.comp.node()?.dependValues(); @@ -250,6 +279,18 @@ QueryCompTmp = class extends QueryCompTmp { const dependsChanged = !_.isEqual(preDepends, depends); const dslNotChanged = _.isEqual(preDsl, dsl); + if(isInputChangeTrigger && blockInputChangeQueries && dependsChanged) { + // block executing input change queries initially on page refresh + setTimeout(() => { + blockInputChangeQueries = false; + }, 500) + + return setFieldsNoTypeCheck(next, { + [lastDependsKey]: depends, + [lastDslKey]: dsl, + }); + } + // If the dsl has not changed, but the dependent node value has changed, then trigger the query execution // FIXME, this should be changed to a reference judgement, but for unknown reasons if the reference is modified once, it will change twice. if (dependsChanged) { @@ -277,7 +318,10 @@ function QueryView(props: QueryViewProps) { useEffect(() => { // Automatically load when page load if ( - getTriggerType(comp) === "automatic" && + ( + getTriggerType(comp) === "automatic" + || getTriggerType(comp) === "onPageLoad" + ) && (comp as any).isDepReady && !comp.children.isNewCreate.value ) { @@ -285,6 +329,12 @@ function QueryView(props: QueryViewProps) { comp.dispatch(deferAction(executeQueryAction({}))); }, 300); } + + if(getTriggerType(comp) === "onTimeout") { + setTimeout(() => { + comp.dispatch(deferAction(executeQueryAction({}))); + }, comp.children.delayTime.getView()); + } }, []); useFixedDelay( @@ -292,9 +342,9 @@ function QueryView(props: QueryViewProps) { getPromiseAfterDispatch(comp.dispatch, executeQueryAction({}), { notHandledError: trans("query.fixedDelayError"), }), - getTriggerType(comp) === "automatic" && comp.children.periodic.getView() - ? comp.children.periodicTime.getView() - : null + getTriggerType(comp) === "automatic" && comp.children.periodic.getView() + ? comp.children.periodicTime.getView() + : null ); return null; @@ -609,6 +659,29 @@ export const QueryComp = withExposingConfigs(QueryCompTmp, [ const QueryListTmpComp = list(QueryComp); class QueryListComp extends QueryListTmpComp implements BottomResListComp { + override reduce(action: CompAction): this { + if (isCustomAction(action, "afterExecQuery")) { + if (action.path?.length === 1 && !isNaN(parseInt(action.path[0]))) { + const queryIdx = parseInt(action.path[0]); + const queryComps = this.getView(); + const queryName = queryComps?.[queryIdx]?.children.name.getView(); + const dependentQueries = queryComps.filter((query, idx) => { + if (queryIdx === idx) return false; + if ( + getTriggerType(query) === 'onQueryExecution' + && query.children.depQueryName.getView() === queryName + ) { + return true; + } + }) + dependentQueries?.forEach((query) => { + query.dispatch(deferAction(executeQueryAction({}))); + }) + } + } + return super.reduce(action); + } + nameAndExposingInfo(): NameAndExposingInfo { const result: NameAndExposingInfo = {}; Object.values(this.children).forEach((comp) => { diff --git a/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx b/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx index 910365185..07f4ef1e0 100644 --- a/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx +++ b/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx @@ -26,7 +26,7 @@ import { useSelector } from "react-redux"; import { getDataSource, getDataSourceTypes } from "redux/selectors/datasourceSelectors"; import { BottomResTypeEnum } from "types/bottomRes"; import { EditorContext } from "../../editorState"; -import { QueryComp } from "../queryComp"; +import { JSTriggerTypeOptions, QueryComp, TriggerType, TriggerTypeOptions } from "../queryComp"; import { ResourceDropdown } from "../resourceDropdown"; import { NOT_SUPPORT_GUI_SQL_QUERY, SQLQuery } from "../sqlQuery/SQLQuery"; import { StreamQuery } from "../httpQuery/streamQuery"; @@ -37,6 +37,7 @@ import styled from "styled-components"; import { DataSourceButton } from "pages/datasource/pluginPanel"; import { Tooltip, Divider } from "antd"; import { uiCompRegistry } from "comps/uiCompRegistry"; +import { InputTypeEnum } from "@lowcoder-ee/comps/comps/moduleContainerComp/ioComp/inputListItemComp"; const Wrapper = styled.div` width: 100%; @@ -226,6 +227,42 @@ export const QueryGeneralPropertyView = (props: { comp.children.datasourceId.dispatchChangeValueAction(QUICK_REST_API_ID); } + const triggerOptions = useMemo(() => { + if (datasourceType === "js" || datasourceType === "streamApi") { + return JSTriggerTypeOptions; + } + return TriggerTypeOptions; + }, [datasourceType]); + + const getQueryOptions = useMemo(() => { + const options: { label: string; value: string }[] = + editorState + ?.queryCompInfoList() + .map((info) => ({ + label: info.name, + value: info.name, + })) + .filter((option) => { + // Filter out the current query under query + if (editorState.selectedBottomResType === BottomResTypeEnum.Query) { + return option.value !== editorState.selectedBottomResName; + } + return true; + }) || []; + + // input queries + editorState + ?.getModuleLayoutComp() + ?.getInputs() + .forEach((i) => { + const { name, type } = i.getView(); + if (type === InputTypeEnum.Query) { + options.push({ label: name, value: name }); + } + }); + return options; + }, [editorState]); + return ( @@ -329,26 +366,38 @@ export const QueryGeneralPropertyView = (props: { {placement === "editor" && ( - - children.triggerType.dispatchChangeValueAction(value)} - /> - + <> + + children.triggerType.dispatchChangeValueAction(value as TriggerType)} + /> + + {children.triggerType.getView() === 'onQueryExecution' && ( + + children.depQueryName.dispatchChangeValueAction(value)} + /> + + )} + {children.triggerType.getView() === 'onTimeout' && ( + + {children.delayTime.propertyView({ + label: trans("query.delayTime"), + placeholder: "5s", + placement: "bottom", + })} + + )} + )} diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index baef755e8..be0378e20 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -738,6 +738,10 @@ export const en = { "triggerTypeAuto": "Inputs Change or On Page Load", "triggerTypePageLoad": "When the Application (Page) loads", "triggerTypeManual": "Only when you trigger it manually", + "triggerTypeInputChange": "When Inputs Change", + "triggerTypeQueryExec": "After Query Execution", + "triggerTypeTimeout": "After the Timeout Interval", + "delayTime": "Delay Time", "chooseDataSource": "Choose Data Source", "method": "Method", "updateExceptionDataSourceTitle": "Update Failing Data Source",