Skip to content

Commit e8d8999

Browse files
authored
Merge pull request #374 from raheeliftikhar5/sockets-extension
feat: Allow user to send message and disconnect socket connection
2 parents 9a3126a + 2907b39 commit e8d8999

File tree

3 files changed

+140
-19
lines changed

3 files changed

+140
-19
lines changed

client/packages/lowcoder/src/comps/queries/httpQuery/streamQuery.tsx

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { simpleMultiComp } from "comps/generators/multi";
2-
import { ParamsStringControl } from "../../controls/paramsControl";
2+
import { ParamsStringControl } from "comps/controls/paramsControl";
33
import {
44
HttpPathPropertyView,
55
} from "./httpQueryConstants";
66
import { QueryResult } from "../queryComp";
77
import { QUERY_EXECUTION_ERROR, QUERY_EXECUTION_OK } from "constants/queryConstants";
88
import { FunctionControl } from "comps/controls/codeControl";
99
import { JSONValue } from "util/jsonTypes";
10+
import { withMethodExposing } from "comps/generators/withMethodExposing";
11+
import { stateComp } from "comps/generators";
12+
import { multiChangeAction } from "lowcoder-core";
1013

1114
const socketConnection = async (socket: WebSocket, timeout = 10000) => {
1215
const isOpened = () => (socket.readyState === WebSocket.OPEN)
@@ -52,9 +55,29 @@ const createErrorResponse = (
5255
const childrenMap = {
5356
path: ParamsStringControl,
5457
destroySocketConnection: FunctionControl,
58+
isSocketConnected: stateComp<boolean>(false),
5559
};
5660

57-
const StreamTmpQuery = simpleMultiComp(childrenMap);
61+
let StreamTmpQuery = simpleMultiComp(childrenMap);
62+
63+
StreamTmpQuery = withMethodExposing(StreamTmpQuery, [
64+
{
65+
method: {
66+
name: "broadcast",
67+
params: [{ name: "args", type: "JSON" }],
68+
},
69+
execute: (comp, params) => {
70+
return new Promise((resolve, reject) => {
71+
const tmpComp = (comp as StreamQuery);
72+
if(!tmpComp.getSocket()) {
73+
return reject('Socket message send failed')
74+
}
75+
tmpComp.broadcast(params);
76+
resolve({});
77+
})
78+
},
79+
},
80+
])
5881

5982
export class StreamQuery extends StreamTmpQuery {
6083
private socket: WebSocket | undefined;
@@ -89,17 +112,29 @@ export class StreamQuery extends StreamTmpQuery {
89112
console.log(`[WebSocket] Connection closed`);
90113
}
91114

92-
this.socket.onerror = function(error) {
115+
this.socket.onerror = (error) => {
116+
this.destroy()
93117
throw new Error(error as any)
94118
}
95119

96120
const isConnectionOpen = await socketConnection(this.socket);
121+
97122
if(!isConnectionOpen) {
123+
this.destroy();
98124
return createErrorResponse("Socket connection failed")
99125
}
100126

101-
return createSuccessResponse("", Number((performance.now() - timer).toFixed()))
127+
this.dispatch(
128+
multiChangeAction({
129+
isSocketConnected: this.children.isSocketConnected.changeValueAction(true),
130+
})
131+
);
132+
return createSuccessResponse(
133+
"Socket connection successfull",
134+
Number((performance.now() - timer).toFixed())
135+
)
102136
} catch (e) {
137+
this.destroy();
103138
return createErrorResponse((e as any).message || "")
104139
}
105140
};
@@ -111,6 +146,19 @@ export class StreamQuery extends StreamTmpQuery {
111146

112147
destroy() {
113148
this.socket?.close();
149+
this.dispatch(
150+
multiChangeAction({
151+
isSocketConnected: this.children.isSocketConnected.changeValueAction(false),
152+
})
153+
);
154+
}
155+
156+
broadcast(data: any) {
157+
this.socket?.send(JSON.stringify(data));
158+
}
159+
160+
getSocket() {
161+
return this.socket;
114162
}
115163
}
116164

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { EditorContext } from "../../editorState";
3030
import { QueryComp } from "../queryComp";
3131
import { ResourceDropdown } from "../resourceDropdown";
3232
import { NOT_SUPPORT_GUI_SQL_QUERY, SQLQuery } from "../sqlQuery/SQLQuery";
33+
import { StreamQuery } from "../httpQuery/streamQuery";
3334

3435
export function QueryPropertyView(props: { comp: InstanceType<typeof QueryComp> }) {
3536
const { comp } = props;
@@ -45,6 +46,7 @@ export function QueryPropertyView(props: { comp: InstanceType<typeof QueryComp>
4546
.datasourceConfig;
4647

4748
const datasourceStatus = useDatasourceStatus(datasourceId, datasourceType);
49+
const isStreamQuery = children.compType.getView() === 'streamApi';
4850

4951
return (
5052
<BottomTabs
@@ -142,6 +144,16 @@ export function QueryPropertyView(props: { comp: InstanceType<typeof QueryComp>
142144
btnLoading={children.isFetching.getView()}
143145
status={datasourceStatus}
144146
message={datasourceStatus === "error" ? trans("query.dataSourceStatusError") : undefined}
147+
isStreamQuery={isStreamQuery}
148+
isSocketConnected={
149+
isStreamQuery
150+
? (children.comp as StreamQuery).children.isSocketConnected.getView()
151+
: false
152+
}
153+
disconnectSocket={() => {
154+
const streamQueryComp = comp.children.comp as StreamQuery;
155+
streamQueryComp?.destroy();
156+
}}
145157
/>
146158
);
147159
}

client/packages/lowcoder/src/pages/editor/bottom/BottomTabs.tsx

Lines changed: 76 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ const TabContainer = styled.div`
108108
}
109109
}
110110
`;
111-
const Button = styled(TacoButton)`
111+
const RunButton = styled(TacoButton)`
112112
padding: 0 11px;
113113
flex: 0 0 64px;
114114
height: 24px;
@@ -130,14 +130,49 @@ const Button = styled(TacoButton)`
130130
content: "";
131131
}
132132
`;
133+
const DisconnectButton = styled(TacoButton)`
134+
padding: 0 11px;
135+
flex: 0 0 64px;
136+
height: 24px;
137+
border: none;
138+
color: white;
139+
border-color: #079968;
140+
background-color: #079968;
141+
142+
:hover, :focus, :disabled, :disabled:hover {
143+
padding: 0 11px;
144+
border: none;
145+
box-shadow: none;
146+
}
147+
148+
:disabled,
149+
:disabled:hover {
150+
background-color: #bbdecd !important;
151+
border-color: #bbdecd;
152+
}
153+
154+
:hover {
155+
background-color: #07714e;
156+
border-color: #07714e;
157+
}
158+
159+
:focus {
160+
background-color: #07714e;
161+
border-color: #07714e;
162+
}
163+
164+
:after {
165+
content: "";
166+
}
167+
`;
133168
const ButtonLabel = styled.span`
134169
font-weight: 500;
135170
font-size: 13px;
136171
color: #ffffff;
137172
text-align: center;
138173
line-height: 24px;
139174
`;
140-
const Icon = styled(UnfoldWhiteIcon)`
175+
const RunIcon = styled(UnfoldWhiteIcon)`
141176
transform: rotate(-90deg);
142177
display: inline-block;
143178
padding-right: 2px;
@@ -165,6 +200,9 @@ export function BottomTabs<T extends TabsConfigType>(props: {
165200
status?: "" | "error";
166201
message?: string;
167202
runButtonText?: string;
203+
isStreamQuery?: boolean;
204+
isSocketConnected?: boolean;
205+
disconnectSocket?: () => void;
168206
}) {
169207
const {
170208
type,
@@ -175,6 +213,9 @@ export function BottomTabs<T extends TabsConfigType>(props: {
175213
status,
176214
message,
177215
runButtonText = trans("bottomPanel.run"),
216+
isStreamQuery = false,
217+
isSocketConnected = false,
218+
disconnectSocket,
178219
} = props;
179220
const [key, setKey] = useState<TabsConfigKeyType<typeof tabsConfig>>("general");
180221
const [error, setError] = useState<string | undefined>(undefined);
@@ -186,6 +227,31 @@ export function BottomTabs<T extends TabsConfigType>(props: {
186227

187228
useEffect(() => setKey("general"), [editorState.selectedBottomResName]);
188229

230+
const RunButtonWrapper = () => (
231+
<RunButton
232+
onClick={onRunBtnClick}
233+
loading={btnLoading}
234+
buttonType="primary"
235+
disabled={readOnly}
236+
>
237+
<RunIcon />
238+
<ButtonLabel>{runButtonText}</ButtonLabel>
239+
</RunButton>
240+
)
241+
242+
const DisconnectButtonWrapper = () => (
243+
<DisconnectButton
244+
onClick={disconnectSocket}
245+
loading={btnLoading}
246+
buttonType="normal"
247+
disabled={readOnly}
248+
>
249+
<ButtonLabel>
250+
{'Disconnect'}
251+
</ButtonLabel>
252+
</DisconnectButton>
253+
)
254+
189255
return (
190256
<>
191257
<TabContainer>
@@ -220,16 +286,11 @@ export function BottomTabs<T extends TabsConfigType>(props: {
220286
hasError={!!error}
221287
/>
222288
</div>
223-
{onRunBtnClick && (
224-
<Button
225-
onClick={onRunBtnClick}
226-
loading={btnLoading}
227-
buttonType="primary"
228-
disabled={readOnly}
229-
>
230-
<Icon />
231-
<ButtonLabel>{runButtonText}</ButtonLabel>
232-
</Button>
289+
{!isSocketConnected && onRunBtnClick && (
290+
<RunButtonWrapper />
291+
)}
292+
{isSocketConnected && onRunBtnClick && disconnectSocket && (
293+
<DisconnectButtonWrapper />
233294
)}
234295
</TabContainer>
235296

@@ -246,9 +307,9 @@ export function BottomTabs<T extends TabsConfigType>(props: {
246307

247308
export const EmptyTab = (
248309
<TabContainer>
249-
<Button disabled={true} style={{ marginLeft: "auto" }}>
250-
<Icon></Icon>
310+
<RunButton disabled={true} style={{ marginLeft: "auto" }}>
311+
<RunIcon />
251312
<ButtonLabel>{trans("bottomPanel.run")}</ButtonLabel>
252-
</Button>
313+
</RunButton>
253314
</TabContainer>
254315
);

0 commit comments

Comments
 (0)