Skip to content

Commit 8d0eaa9

Browse files
authored
Merge pull request #1443 from lowcoder-org/feat/query_triggers
Feat/query triggers
2 parents 4ced9ee + 9ce2172 commit 8d0eaa9

File tree

3 files changed

+155
-29
lines changed

3 files changed

+155
-29
lines changed

client/packages/lowcoder/src/comps/queries/queryComp.tsx

Lines changed: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,25 @@ interface AfterExecuteQueryAction {
9797
result: QueryResult;
9898
}
9999

100-
const TriggerTypeOptions = [
100+
const CommonTriggerOptions = [
101+
{ label: trans("query.triggerTypeInputChange"), value: "onInputChange"},
102+
{ label: trans("query.triggerTypeQueryExec"), value: "onQueryExecution"},
103+
{ label: trans("query.triggerTypeTimeout"), value: "onTimeout"},
104+
]
105+
106+
export const TriggerTypeOptions = [
107+
{ label: trans("query.triggerTypePageLoad"), value: "onPageLoad"},
108+
...CommonTriggerOptions,
101109
{ label: trans("query.triggerTypeAuto"), value: "automatic" },
102110
{ label: trans("query.triggerTypeManual"), value: "manual" },
103111
] as const;
112+
113+
export const JSTriggerTypeOptions = [
114+
...CommonTriggerOptions,
115+
{ label: trans("query.triggerTypePageLoad"), value: "automatic" },
116+
{ label: trans("query.triggerTypeManual"), value: "manual" },
117+
];
118+
104119
export type TriggerType = ValueFromOption<typeof TriggerTypeOptions>;
105120

106121
const EventOptions = [
@@ -151,6 +166,13 @@ const childrenMap = {
151166
},
152167
}),
153168
cancelPrevious: withDefault(BoolPureControl, false),
169+
// use only for onQueryExecution trigger
170+
depQueryName: SimpleNameComp,
171+
// use only for onTimeout trigger, triggers query after x time passed on page load
172+
delayTime: millisecondsControl({
173+
left: 0,
174+
defaultValue: 5 * 1000,
175+
})
154176
};
155177

156178
let QueryCompTmp = withTypeAndChildren<typeof QueryMap, ToInstanceType<typeof childrenMap>>(
@@ -174,6 +196,7 @@ export type QueryChildrenType = InstanceType<typeof QueryCompTmp> extends MultiB
174196
? X
175197
: never;
176198

199+
let blockInputChangeQueries = true;
177200
/**
178201
* Logic to automatically trigger execution
179202
*/
@@ -222,11 +245,17 @@ QueryCompTmp = class extends QueryCompTmp {
222245
const isJsQuery = this.children.compType.getView() === "js";
223246
const notExecuted = this.children.lastQueryStartTime.getView() === -1;
224247
const isAutomatic = getTriggerType(this) === "automatic";
248+
const isPageLoadTrigger = getTriggerType(this) === "onPageLoad";
249+
const isInputChangeTrigger = getTriggerType(this) === "onInputChange";
225250

226251
if (
227-
action.type === CompActionTypes.UPDATE_NODES_V2 &&
228-
isAutomatic &&
229-
(!isJsQuery || (isJsQuery && notExecuted)) // query which has deps can be executed on page load(first time)
252+
action.type === CompActionTypes.UPDATE_NODES_V2
253+
&& (
254+
isAutomatic
255+
|| isInputChangeTrigger
256+
|| (isPageLoadTrigger && notExecuted)
257+
)
258+
// && (!isJsQuery || (isJsQuery && notExecuted)) // query which has deps can be executed on page load(first time)
230259
) {
231260
const next = super.reduce(action);
232261
const depends = this.children.comp.node()?.dependValues();
@@ -250,6 +279,18 @@ QueryCompTmp = class extends QueryCompTmp {
250279
const dependsChanged = !_.isEqual(preDepends, depends);
251280
const dslNotChanged = _.isEqual(preDsl, dsl);
252281

282+
if(isInputChangeTrigger && blockInputChangeQueries && dependsChanged) {
283+
// block executing input change queries initially on page refresh
284+
setTimeout(() => {
285+
blockInputChangeQueries = false;
286+
}, 500)
287+
288+
return setFieldsNoTypeCheck(next, {
289+
[lastDependsKey]: depends,
290+
[lastDslKey]: dsl,
291+
});
292+
}
293+
253294
// If the dsl has not changed, but the dependent node value has changed, then trigger the query execution
254295
// FIXME, this should be changed to a reference judgement, but for unknown reasons if the reference is modified once, it will change twice.
255296
if (dependsChanged) {
@@ -277,24 +318,33 @@ function QueryView(props: QueryViewProps) {
277318
useEffect(() => {
278319
// Automatically load when page load
279320
if (
280-
getTriggerType(comp) === "automatic" &&
321+
(
322+
getTriggerType(comp) === "automatic"
323+
|| getTriggerType(comp) === "onPageLoad"
324+
) &&
281325
(comp as any).isDepReady &&
282326
!comp.children.isNewCreate.value
283327
) {
284328
setTimeout(() => {
285329
comp.dispatch(deferAction(executeQueryAction({})));
286330
}, 300);
287331
}
332+
333+
if(getTriggerType(comp) === "onTimeout") {
334+
setTimeout(() => {
335+
comp.dispatch(deferAction(executeQueryAction({})));
336+
}, comp.children.delayTime.getView());
337+
}
288338
}, []);
289339

290340
useFixedDelay(
291341
() =>
292342
getPromiseAfterDispatch(comp.dispatch, executeQueryAction({}), {
293343
notHandledError: trans("query.fixedDelayError"),
294344
}),
295-
getTriggerType(comp) === "automatic" && comp.children.periodic.getView()
296-
? comp.children.periodicTime.getView()
297-
: null
345+
getTriggerType(comp) === "automatic" && comp.children.periodic.getView()
346+
? comp.children.periodicTime.getView()
347+
: null
298348
);
299349

300350
return null;
@@ -609,6 +659,29 @@ export const QueryComp = withExposingConfigs(QueryCompTmp, [
609659
const QueryListTmpComp = list(QueryComp);
610660

611661
class QueryListComp extends QueryListTmpComp implements BottomResListComp {
662+
override reduce(action: CompAction): this {
663+
if (isCustomAction<AfterExecuteQueryAction>(action, "afterExecQuery")) {
664+
if (action.path?.length === 1 && !isNaN(parseInt(action.path[0]))) {
665+
const queryIdx = parseInt(action.path[0]);
666+
const queryComps = this.getView();
667+
const queryName = queryComps?.[queryIdx]?.children.name.getView();
668+
const dependentQueries = queryComps.filter((query, idx) => {
669+
if (queryIdx === idx) return false;
670+
if (
671+
getTriggerType(query) === 'onQueryExecution'
672+
&& query.children.depQueryName.getView() === queryName
673+
) {
674+
return true;
675+
}
676+
})
677+
dependentQueries?.forEach((query) => {
678+
query.dispatch(deferAction(executeQueryAction({})));
679+
})
680+
}
681+
}
682+
return super.reduce(action);
683+
}
684+
612685
nameAndExposingInfo(): NameAndExposingInfo {
613686
const result: NameAndExposingInfo = {};
614687
Object.values(this.children).forEach((comp) => {

client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { useSelector } from "react-redux";
2626
import { getDataSource, getDataSourceTypes } from "redux/selectors/datasourceSelectors";
2727
import { BottomResTypeEnum } from "types/bottomRes";
2828
import { EditorContext } from "../../editorState";
29-
import { QueryComp } from "../queryComp";
29+
import { JSTriggerTypeOptions, QueryComp, TriggerType, TriggerTypeOptions } from "../queryComp";
3030
import { ResourceDropdown } from "../resourceDropdown";
3131
import { NOT_SUPPORT_GUI_SQL_QUERY, SQLQuery } from "../sqlQuery/SQLQuery";
3232
import { StreamQuery } from "../httpQuery/streamQuery";
@@ -37,6 +37,7 @@ import styled from "styled-components";
3737
import { DataSourceButton } from "pages/datasource/pluginPanel";
3838
import { Tooltip, Divider } from "antd";
3939
import { uiCompRegistry } from "comps/uiCompRegistry";
40+
import { InputTypeEnum } from "@lowcoder-ee/comps/comps/moduleContainerComp/ioComp/inputListItemComp";
4041

4142
const Wrapper = styled.div`
4243
width: 100%;
@@ -226,6 +227,42 @@ export const QueryGeneralPropertyView = (props: {
226227
comp.children.datasourceId.dispatchChangeValueAction(QUICK_REST_API_ID);
227228
}
228229

230+
const triggerOptions = useMemo(() => {
231+
if (datasourceType === "js" || datasourceType === "streamApi") {
232+
return JSTriggerTypeOptions;
233+
}
234+
return TriggerTypeOptions;
235+
}, [datasourceType]);
236+
237+
const getQueryOptions = useMemo(() => {
238+
const options: { label: string; value: string }[] =
239+
editorState
240+
?.queryCompInfoList()
241+
.map((info) => ({
242+
label: info.name,
243+
value: info.name,
244+
}))
245+
.filter((option) => {
246+
// Filter out the current query under query
247+
if (editorState.selectedBottomResType === BottomResTypeEnum.Query) {
248+
return option.value !== editorState.selectedBottomResName;
249+
}
250+
return true;
251+
}) || [];
252+
253+
// input queries
254+
editorState
255+
?.getModuleLayoutComp()
256+
?.getInputs()
257+
.forEach((i) => {
258+
const { name, type } = i.getView();
259+
if (type === InputTypeEnum.Query) {
260+
options.push({ label: name, value: name });
261+
}
262+
});
263+
return options;
264+
}, [editorState]);
265+
229266
return (
230267
<QueryPropertyViewWrapper>
231268
<QuerySectionWrapper>
@@ -329,26 +366,38 @@ export const QueryGeneralPropertyView = (props: {
329366
</QueryConfigWrapper>
330367

331368
{placement === "editor" && (
332-
<TriggerTypeStyled>
333-
<Dropdown
334-
placement={"bottom"}
335-
label={trans("query.triggerType")}
336-
options={
337-
[
338-
{
339-
label:
340-
(children.compType.getView() === "js" || children.compType.getView() === "streamApi")
341-
? trans("query.triggerTypePageLoad")
342-
: trans("query.triggerTypeAuto"),
343-
value: "automatic",
344-
},
345-
{ label: trans("query.triggerTypeManual"), value: "manual" },
346-
] as const
347-
}
348-
value={children.triggerType.getView()}
349-
onChange={(value) => children.triggerType.dispatchChangeValueAction(value)}
350-
/>
351-
</TriggerTypeStyled>
369+
<>
370+
<TriggerTypeStyled>
371+
<Dropdown
372+
placement={"bottom"}
373+
label={trans("query.triggerType")}
374+
options={triggerOptions}
375+
value={children.triggerType.getView()}
376+
onChange={(value) => children.triggerType.dispatchChangeValueAction(value as TriggerType)}
377+
/>
378+
</TriggerTypeStyled>
379+
{children.triggerType.getView() === 'onQueryExecution' && (
380+
<TriggerTypeStyled>
381+
<Dropdown
382+
showSearch={true}
383+
placement={"bottom"}
384+
value={children.depQueryName.getView()}
385+
options={getQueryOptions}
386+
label={trans("eventHandler.selectQuery")}
387+
onChange={(value) => children.depQueryName.dispatchChangeValueAction(value)}
388+
/>
389+
</TriggerTypeStyled>
390+
)}
391+
{children.triggerType.getView() === 'onTimeout' && (
392+
<TriggerTypeStyled>
393+
{children.delayTime.propertyView({
394+
label: trans("query.delayTime"),
395+
placeholder: "5s",
396+
placement: "bottom",
397+
})}
398+
</TriggerTypeStyled>
399+
)}
400+
</>
352401
)}
353402
</QuerySectionWrapper>
354403

client/packages/lowcoder/src/i18n/locales/en.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,10 @@ export const en = {
738738
"triggerTypeAuto": "Inputs Change or On Page Load",
739739
"triggerTypePageLoad": "When the Application (Page) loads",
740740
"triggerTypeManual": "Only when you trigger it manually",
741+
"triggerTypeInputChange": "When Inputs Change",
742+
"triggerTypeQueryExec": "After Query Execution",
743+
"triggerTypeTimeout": "After the Timeout Interval",
744+
"delayTime": "Delay Time",
741745
"chooseDataSource": "Choose Data Source",
742746
"method": "Method",
743747
"updateExceptionDataSourceTitle": "Update Failing Data Source",

0 commit comments

Comments
 (0)