diff --git a/client/packages/lowcoder-design/src/components/keyValueList.tsx b/client/packages/lowcoder-design/src/components/keyValueList.tsx index 7bd9f317a..6cedce3c4 100644 --- a/client/packages/lowcoder-design/src/components/keyValueList.tsx +++ b/client/packages/lowcoder-design/src/components/keyValueList.tsx @@ -76,20 +76,25 @@ export const KeyValueList = (props: { list: ReactNode[]; onAdd: () => void; onDelete: (item: ReactNode, index: number) => void; + isStatic?: boolean; }) => ( <> {props.list.map((item, index) => ( {item} - props.list.length > 1 && props.onDelete(item, index)} - $forbidden={props.list.length === 1} - /> + {!props.isStatic && + props.list.length > 1 && props.onDelete(item, index)} + $forbidden={props.list.length === 1} + /> + } ))} + {!props.isStatic && {trans("addItem")} + } ); diff --git a/client/packages/lowcoder/src/comps/controls/actionSelector/executeQueryAction.tsx b/client/packages/lowcoder/src/comps/controls/actionSelector/executeQueryAction.tsx index 720fc93fe..cc4018bd5 100644 --- a/client/packages/lowcoder/src/comps/controls/actionSelector/executeQueryAction.tsx +++ b/client/packages/lowcoder/src/comps/controls/actionSelector/executeQueryAction.tsx @@ -7,10 +7,100 @@ import { BranchDiv, Dropdown } from "lowcoder-design"; import { BottomResTypeEnum } from "types/bottomRes"; import { getPromiseAfterDispatch } from "util/promiseUtils"; import { trans } from "i18n"; +import {keyValueListControl, keyValueListToSearchStr, withDefault} from "lowcoder-sdk"; +import {KeyValue} from "@lowcoder-ee/types/common"; +import { useCallback, useContext, useEffect, useMemo } from "react"; +const ExecuteQueryPropertyView = ({ + comp, + placement, +}: { + comp: any, + placement?: "query" | "table" +}) => { + const getQueryOptions = useCallback((editorState?: EditorState) => { + const options: { label: string; value: string; variable?: Record }[] = + editorState + ?.queryCompInfoList() + .map((info) => { + return { + label: info.name, + value: info.name, + variable: info.data.variable, + } + }) + .filter( + // Filter out the current query under query + (option) => { + if ( + placement === "query" && + 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; + }, [placement]); + + const getVariableOptions = useCallback((editorState?: EditorState) => { + return comp.children.queryVariables.propertyView({ + label: trans("eventHandler.queryVariables"), + layout: "vertical", + isStatic: true, + keyFixed: true, + }); + }, [comp.children.queryVariables.getView()]) + + return ( + <> + + + {(editorState) => ( + <> + { + const options = getQueryOptions(editorState); + const selectedQuery = options.find(option => option.value === value); + const variables = selectedQuery ? Object.keys(selectedQuery.variable || {}) : []; + comp.dispatchChangeValueAction({ + queryName: value, + queryVariables: variables.map((variable) => ({key: variable, value: ''})), + }); + }} + /> + + )} + + + + + {(editorState) => getVariableOptions(editorState)} + + + + ); +} const ExecuteQueryTmpAction = (function () { const childrenMap = { queryName: SimpleNameComp, + queryVariables: withDefault(keyValueListControl(false, [], "string"), []) }; return new MultiCompBuilder(childrenMap, () => { return () => Promise.resolve(undefined as unknown); @@ -22,6 +112,15 @@ const ExecuteQueryTmpAction = (function () { export class ExecuteQueryAction extends ExecuteQueryTmpAction { override getView() { const queryName = this.children.queryName.getView(); + // const queryParams = keyValueListToSearchStr(Array.isArray(this?.children?.query) ? (this.children.query as unknown as any[]).map((i: any) => i.getView() as KeyValue) : []); + const result = Object.values(this.children.queryVariables.children as Record) + .filter(item => item.children.key.unevaledValue !== "" && item.children.value.unevaledValue !== "") + .map(item => ({[item.children.key.unevaledValue]: item.children.value.unevaledValue})) + .reduce((acc, curr) => Object.assign(acc, curr), {}); if (!queryName) { return () => Promise.resolve(); } @@ -30,9 +129,7 @@ export class ExecuteQueryAction extends ExecuteQueryTmpAction { this.dispatch, routeByNameAction( queryName, - executeQueryAction({ - // can add context in the future - }) + executeQueryAction({args: result}) ), { notHandledError: trans("eventHandler.notHandledError") } ); @@ -46,55 +143,11 @@ export class ExecuteQueryAction extends ExecuteQueryTmpAction { } propertyView({ placement }: { placement?: "query" | "table" }) { - const getQueryOptions = (editorState?: EditorState) => { - const options: { label: string; value: string }[] = - editorState - ?.queryCompInfoList() - .map((info) => ({ - label: info.name, - value: info.name, - })) - .filter( - // Filter out the current query under query - (option) => { - if ( - placement === "query" && - 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; - }; return ( - - - {(editorState) => ( - <> - this.dispatchChangeValueAction({ queryName: value })} - /> - - )} - - - ); + + ) } } diff --git a/client/packages/lowcoder/src/comps/controls/actionSelector/goToURLAction.tsx b/client/packages/lowcoder/src/comps/controls/actionSelector/goToURLAction.tsx index 462cd6213..2223079ef 100644 --- a/client/packages/lowcoder/src/comps/controls/actionSelector/goToURLAction.tsx +++ b/client/packages/lowcoder/src/comps/controls/actionSelector/goToURLAction.tsx @@ -20,7 +20,7 @@ const childrenMap = { }; export const GoToURLAction = new MultiCompBuilder(childrenMap, (props) => { - return () => { + return () => { const queryParams = keyValueListToSearchStr( props.query.map((i) => i.getView() as KeyValue) ); diff --git a/client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx b/client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx index 3a22afe71..70bf77962 100644 --- a/client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/eventHandlerControl.tsx @@ -167,12 +167,18 @@ const EventHandlerControlPropertyView = (props: { if (eventConfigs.length === 0) { return; } + + const queryVariables = editorState + ?.selectedOrFirstQueryComp() + ?.children.variables.children.variables.toJsonValue(); + const queryExecHandler = { compType: "executeQuery", comp: { queryName: editorState ?.selectedOrFirstQueryComp() ?.children.name.getView(), + queryVariables: queryVariables?.map((variable) => ({...variable, value: ''})), }, }; const messageHandler = { diff --git a/client/packages/lowcoder/src/comps/controls/keyValueControl.tsx b/client/packages/lowcoder/src/comps/controls/keyValueControl.tsx index 89cc6d276..1f4d722a2 100644 --- a/client/packages/lowcoder/src/comps/controls/keyValueControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/keyValueControl.tsx @@ -47,6 +47,8 @@ export type KeyValueControlParams = ControlParams & { typeTooltip?: ReactNode; keyFlexBasics?: number; valueFlexBasics?: number; + isStatic?: boolean; + keyFixed?: boolean; }; /** @@ -82,16 +84,20 @@ function keyValueControl( return ( - {this.children.key.propertyView({ placeholder: "key", indentWithTab: false })} - {hasType && params.showType && ( - - {this.children.type.propertyView({ - placeholder: "key", - indentWithTab: false, - tooltip: params.typeTooltip, - })} - - )} + {params.keyFixed? + <>{this.children.key.getView()} + :<> + {this.children.key.propertyView({ placeholder: "key", indentWithTab: false })} + {hasType && params.showType && ( + + {this.children.type.propertyView({ + placeholder: "key", + indentWithTab: false, + tooltip: params.typeTooltip, + })} + + )} + } {this.children.value.propertyView({ @@ -136,6 +142,7 @@ export function keyValueListControl( list={this.getView().map((child) => child.propertyView(params))} onAdd={() => this.dispatch(this.pushAction({}))} onDelete={(item, index) => this.dispatch(this.deleteAction(index))} + isStatic={params.isStatic} /> ); diff --git a/client/packages/lowcoder/src/comps/queries/queryComp.tsx b/client/packages/lowcoder/src/comps/queries/queryComp.tsx index e90bf8d19..fbfabeaaa 100644 --- a/client/packages/lowcoder/src/comps/queries/queryComp.tsx +++ b/client/packages/lowcoder/src/comps/queries/queryComp.tsx @@ -67,7 +67,7 @@ import { JSONObject, JSONValue } from "../../util/jsonTypes"; import { BoolPureControl } from "../controls/boolControl"; import { millisecondsControl } from "../controls/millisecondControl"; import { paramsMillisecondsControl } from "../controls/paramsControl"; -import { NameConfig, withExposingConfigs } from "../generators/withExposing"; +import { DepsConfig, NameConfig, withExposingConfigs } from "../generators/withExposing"; import { HttpQuery } from "./httpQuery/httpQuery"; import { StreamQuery } from "./httpQuery/streamQuery"; import { QueryConfirmationModal } from "./queryComp/queryConfirmationModal"; @@ -75,6 +75,7 @@ import { QueryNotificationControl } from "./queryComp/queryNotificationControl"; import { QueryPropertyView } from "./queryComp/queryPropertyView"; import { getTriggerType, onlyManualTrigger } from "./queryCompUtils"; import { messageInstance } from "lowcoder-design/src/components/GlobalInstances"; +import {VariablesComp} from "@lowcoder-ee/comps/queries/queryComp/variablesComp"; const latestExecution: Record = {}; @@ -153,6 +154,7 @@ const childrenMap = { defaultValue: 10 * 1000, }), confirmationModal: QueryConfirmationModal, + variables: VariablesComp, periodic: BoolPureControl, periodicTime: millisecondsControl({ defaultValue: Number.NaN, @@ -361,6 +363,8 @@ QueryCompTmp = class extends QueryCompTmp { } if (action.type === CompActionTypes.EXECUTE_QUERY) { if (getReduceContext().disableUpdateState) return this; + if(!action.args) action.args = this.children.variables.children.variables.toJsonValue().reduce((acc, curr) => Object.assign(acc, {[curr.key as string]:curr.value}), {}); + return this.executeQuery(action); } if (action.type === CompActionTypes.CHANGE_VALUE) { @@ -404,16 +408,21 @@ QueryCompTmp = class extends QueryCompTmp { return this; } + + + /** * Process the execution result */ private processResult(result: QueryResult, action: ExecuteQueryAction, startTime: number) { const lastQueryStartTime = this.children.lastQueryStartTime.getView(); + if (lastQueryStartTime > startTime) { // There are more new requests, ignore this result // FIXME: cancel this request in advance in the future return; } + const changeAction = multiChangeAction({ code: this.children.code.changeValueAction(result.code ?? QUERY_EXECUTION_OK), success: this.children.success.changeValueAction(result.success ?? true), @@ -470,6 +479,7 @@ QueryCompTmp = class extends QueryCompTmp { applicationId: applicationId, applicationPath: parentApplicationPath, args: action.args, + variables: action.args, timeout: this.children.timeout, callback: (result) => this.processResult(result, action, startTime) }); @@ -653,6 +663,23 @@ export const QueryComp = withExposingConfigs(QueryCompTmp, [ new NameConfig("isFetching", trans("query.isFetchingExportDesc")), new NameConfig("runTime", trans("query.runTimeExportDesc")), new NameConfig("latestEndTime", trans("query.latestEndTimeExportDesc")), + new DepsConfig( + "variable", + (children: any) => { + return {data: children.variables.children.variables.node()}; + }, + (input) => { + if (!input.data) { + return undefined; + } + const newNode = Object.values(input.data) + .filter((kvNode: any) => kvNode.key.text.value) + .map((kvNode: any) => ({[kvNode.key.text.value]: kvNode.value.text.value})) + .reduce((prev, obj) => ({...prev, ...obj}), {}); + return newNode; + }, + trans("query.variables") + ), new NameConfig("triggerType", trans("query.triggerTypeExportDesc")), ]); diff --git a/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx b/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx index 07f4ef1e0..fa0f078e1 100644 --- a/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx +++ b/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx @@ -171,6 +171,17 @@ export function QueryPropertyView(props: { comp: InstanceType ), }, + { + key: "variables", + title: trans("query.variablesTab"), + children: ( + + + {children.variables.getPropertyView()} + + + ), + }, ] as const } tabTitle={children.name.getView()} @@ -501,6 +512,29 @@ export const QueryGeneralPropertyView = (props: { ); }; +export const QueryVariablesPropertyView = (props: { + comp: InstanceType; + placement?: PageType; +}) => { + const { comp, placement = "editor" } = props; + + const children = comp.children; + let datasourceId = children.datasourceId.getView(); + + console.log(children.datasourceId); + return ( + + + {isCompWithPropertyView(children.comp) + ? children.comp.propertyView({ + datasourceId: datasourceId, + }) + : children.comp.getPropertyView()} + + + ); +}; + function findQueryInNestedStructure( structure: any, queryName: string, diff --git a/client/packages/lowcoder/src/comps/queries/queryComp/variablesComp.tsx b/client/packages/lowcoder/src/comps/queries/queryComp/variablesComp.tsx new file mode 100644 index 000000000..87c5bd432 --- /dev/null +++ b/client/packages/lowcoder/src/comps/queries/queryComp/variablesComp.tsx @@ -0,0 +1,16 @@ +import {MultiCompBuilder, withDefault} from "../../generators"; +import {keyValueListControl} from "lowcoder-sdk"; + +export const VariablesComp = new MultiCompBuilder( + { + variables: withDefault(keyValueListControl(), [{ key: "", value: "" }]), + }, + (props) => + props.variables + ) + .setPropertyViewFn((children) => ( + <> + {children.variables.propertyView({})} + + )) + .build(); diff --git a/client/packages/lowcoder/src/comps/queries/queryCompUtils.tsx b/client/packages/lowcoder/src/comps/queries/queryCompUtils.tsx index 1971e8ec5..1203e1cfd 100644 --- a/client/packages/lowcoder/src/comps/queries/queryCompUtils.tsx +++ b/client/packages/lowcoder/src/comps/queries/queryCompUtils.tsx @@ -25,18 +25,23 @@ export function toQueryView(params: FunctionProperty[]) { applicationId: string; applicationPath: string[]; args?: Record; + variables?: any; timeout: InstanceType; }): Promise => { const { applicationId, isViewMode } = getGlobalSettings(); + const mappedVariables = Object.keys(props.variables).map(key => ({key: `query1.variable.${key}`, value: props.variables[key]})); let request: QueryExecuteRequest = { path: props.applicationPath, params: [ - ...params.map(({ key, value }) => ({ key, value: value(props.args) })), + ...params.filter(param => { + return !mappedVariables.map(v => v.key).includes(param.key); + }).map(({ key, value }) => ({ key, value: value(props.args) })), ...Object.entries(props.timeout.getView()).map(([key, value]) => ({ key, value: value(props.args), })), + ...mappedVariables, ], viewMode: !!isViewMode, }; diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index 9c8cdddfa..7623177c6 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -277,6 +277,7 @@ export const en = { "moduleEvent": "Module Event", "goToApp": "Go to an other App", "queryParams": "Query Parameters", + "queryVariables": "Query Variables", "hashParams": "Hash Parameters", "showNotification": "Show a Notification", "text": "Text", @@ -705,6 +706,7 @@ export const en = { "newDatasource": "New Data Source", "generalTab": "General", "notificationTab": "Notification", + "variablesTab": "Variables", "advancedTab": "Advanced", "showFailNotification": "Show Notification on Failure", "failCondition": "Failure Conditions", diff --git a/client/packages/lowcoder/src/util/appUtils.tsx b/client/packages/lowcoder/src/util/appUtils.tsx index 5aed643ae..be38dfc22 100644 --- a/client/packages/lowcoder/src/util/appUtils.tsx +++ b/client/packages/lowcoder/src/util/appUtils.tsx @@ -32,6 +32,7 @@ export function openApp(props: { hashParams?: string; newTab?: boolean; }) { + console.log(props.queryParams) const m = matchPath(window.location.pathname, APP_EDITOR_URL); if (!m || !props.applicationId) { return;