Skip to content

Commit

Permalink
Merge pull request #1443 from lowcoder-org/feat/query_triggers
Browse files Browse the repository at this point in the history
Feat/query triggers
  • Loading branch information
FalkWolsky authored Jan 14, 2025
2 parents 4ced9ee + 9ce2172 commit 8d0eaa9
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 29 deletions.
89 changes: 81 additions & 8 deletions client/packages/lowcoder/src/comps/queries/queryComp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof TriggerTypeOptions>;

const EventOptions = [
Expand Down Expand Up @@ -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<typeof QueryMap, ToInstanceType<typeof childrenMap>>(
Expand All @@ -174,6 +196,7 @@ export type QueryChildrenType = InstanceType<typeof QueryCompTmp> extends MultiB
? X
: never;

let blockInputChangeQueries = true;
/**
* Logic to automatically trigger execution
*/
Expand Down Expand Up @@ -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();
Expand All @@ -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) {
Expand Down Expand Up @@ -277,24 +318,33 @@ 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
) {
setTimeout(() => {
comp.dispatch(deferAction(executeQueryAction({})));
}, 300);
}

if(getTriggerType(comp) === "onTimeout") {
setTimeout(() => {
comp.dispatch(deferAction(executeQueryAction({})));
}, comp.children.delayTime.getView());
}
}, []);

useFixedDelay(
() =>
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;
Expand Down Expand Up @@ -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<AfterExecuteQueryAction>(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) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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%;
Expand Down Expand Up @@ -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 (
<QueryPropertyViewWrapper>
<QuerySectionWrapper>
Expand Down Expand Up @@ -329,26 +366,38 @@ export const QueryGeneralPropertyView = (props: {
</QueryConfigWrapper>

{placement === "editor" && (
<TriggerTypeStyled>
<Dropdown
placement={"bottom"}
label={trans("query.triggerType")}
options={
[
{
label:
(children.compType.getView() === "js" || children.compType.getView() === "streamApi")
? trans("query.triggerTypePageLoad")
: trans("query.triggerTypeAuto"),
value: "automatic",
},
{ label: trans("query.triggerTypeManual"), value: "manual" },
] as const
}
value={children.triggerType.getView()}
onChange={(value) => children.triggerType.dispatchChangeValueAction(value)}
/>
</TriggerTypeStyled>
<>
<TriggerTypeStyled>
<Dropdown
placement={"bottom"}
label={trans("query.triggerType")}
options={triggerOptions}
value={children.triggerType.getView()}
onChange={(value) => children.triggerType.dispatchChangeValueAction(value as TriggerType)}
/>
</TriggerTypeStyled>
{children.triggerType.getView() === 'onQueryExecution' && (
<TriggerTypeStyled>
<Dropdown
showSearch={true}
placement={"bottom"}
value={children.depQueryName.getView()}
options={getQueryOptions}
label={trans("eventHandler.selectQuery")}
onChange={(value) => children.depQueryName.dispatchChangeValueAction(value)}
/>
</TriggerTypeStyled>
)}
{children.triggerType.getView() === 'onTimeout' && (
<TriggerTypeStyled>
{children.delayTime.propertyView({
label: trans("query.delayTime"),
placeholder: "5s",
placement: "bottom",
})}
</TriggerTypeStyled>
)}
</>
)}
</QuerySectionWrapper>

Expand Down
4 changes: 4 additions & 0 deletions client/packages/lowcoder/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 8d0eaa9

Please sign in to comment.