Skip to content

Commit d970e19

Browse files
Merge pull request #13 from Zipstack/fix/ui-ux-improvements
fix: UI/UX improvements — tooltips, nav label, connection save and refresh
2 parents ebdd3d7 + 2defd76 commit d970e19

9 files changed

Lines changed: 209 additions & 106 deletions

File tree

backend/backend/application/context/connection.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@ def _merge_with_stored(incoming: dict[str, Any], stored: dict[str, Any]) -> dict
6363
return merged
6464

6565

66+
def _has_credentials_changed(incoming: dict[str, Any], stored: dict[str, Any]) -> bool:
67+
"""Return True if any connection credential field differs from stored values."""
68+
if not stored:
69+
return True
70+
for key, value in incoming.items():
71+
if stored.get(key) != value:
72+
return True
73+
return False
74+
75+
6676
class ConnectionContext:
6777

6878
def __init__(self):
@@ -143,12 +153,16 @@ def update_connection(self, connection_id: str, connection_details: dict) -> dic
143153

144154
# Merge masked sentinels with stored real values
145155
stored_model = self.connection_session.get_connection_model(connection_id)
156+
stored_details = {}
146157
if stored_model:
147158
stored_details = stored_model.decrypted_connection_details
148159
decrypted_connection_data = _merge_with_stored(decrypted_connection_data, stored_details)
149160

150-
# Test connection with decrypted data
151-
test_connection_data(datasource=datasource, connection_data=decrypted_connection_data)
161+
# Skip connection test only for metadata-only updates where credentials are unchanged
162+
metadata_only = connection_details.pop("metadata_only", False)
163+
credentials_changed = _has_credentials_changed(decrypted_connection_data, stored_details)
164+
if not metadata_only or credentials_changed:
165+
test_connection_data(datasource=datasource, connection_data=decrypted_connection_data)
152166
_conn_details = get_connection_data(datasource=datasource, connection_data=decrypted_connection_data)
153167
connection_details["connection_details"] = _conn_details
154168

frontend/src/base/components/environment/CreateConnection.jsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ const CreateConnection = ({
137137
connection_type: connType,
138138
}),
139139
},
140+
...(connectionId && hasDetailsChanged && { metadata_only: true }),
140141
};
141142

142143
// Encrypt sensitive fields if encryption service is available
@@ -161,10 +162,10 @@ const CreateConnection = ({
161162
connectionData
162163
);
163164
if (res.status === 200) {
164-
const { id, datasource_name } = res.data?.data || {};
165-
setConnectionId({ id });
166-
setConnectionDbType(datasource_name);
167-
getAllConnection();
165+
const newConnection = res.data?.data || {};
166+
setConnectionId({ id: newConnection.id });
167+
setConnectionDbType(newConnection.datasource_name);
168+
getAllConnection(newConnection);
168169
setIsModalOpen(false);
169170
notify({
170171
type: "success",
@@ -186,8 +187,8 @@ const CreateConnection = ({
186187
message: "Success",
187188
description: "Connection updated successfully.",
188189
});
190+
getAllConnection(res.data?.data);
189191
setIsModalOpen(false);
190-
getAllConnection();
191192
}
192193
}
193194
} catch (error) {

frontend/src/base/components/environment/NewEnv.jsx

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -249,18 +249,34 @@ const NewEnv = ({
249249
[connectionDataSource, selectedOrgId, csrfToken, connType, connection]
250250
);
251251

252-
const getAllConnections = useCallback(async () => {
253-
setLoading(true);
254-
try {
255-
const data = await fetchAllConnections(axiosRef, selectedOrgId);
256-
setConnectionList(data?.filter((el) => !el?.is_sample_project));
257-
} catch (error) {
258-
console.error(error);
259-
notify({ error });
260-
} finally {
261-
setLoading(false);
262-
}
263-
}, [selectedOrgId]);
252+
const getAllConnections = useCallback(
253+
async (updatedConnection) => {
254+
if (updatedConnection?.id) {
255+
setConnectionList((prev) => {
256+
const list = prev || [];
257+
const exists = list.some((c) => c?.id === updatedConnection.id);
258+
if (exists) {
259+
return list.map((c) =>
260+
c?.id === updatedConnection.id ? updatedConnection : c
261+
);
262+
}
263+
return [...list, updatedConnection];
264+
});
265+
return;
266+
}
267+
setLoading(true);
268+
try {
269+
const data = await fetchAllConnections(axiosRef, selectedOrgId);
270+
setConnectionList(data?.filter((el) => !el?.is_sample_project) || []);
271+
} catch (error) {
272+
console.error(error);
273+
notify({ error });
274+
} finally {
275+
setLoading(false);
276+
}
277+
},
278+
[selectedOrgId]
279+
);
264280

265281
const getProjectDependency = useCallback(async () => {
266282
try {

frontend/src/base/components/topbar/NavigationTabs.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const NavigationTabs = memo(({ activeTab }) => {
3434
return (
3535
<div className="flex-space-between">
3636
<Space size={5}>
37-
{/* Dashboard */}
37+
{/* Projects */}
3838
<Typography
3939
className={
4040
activeTab === "project"
@@ -45,7 +45,7 @@ const NavigationTabs = memo(({ activeTab }) => {
4545
>
4646
<AppstoreOutlined className="clr-white" />
4747
<Typography className="menu_label cursor-pointer">
48-
Dashboard
48+
Projects
4949
</Typography>
5050
</Typography>
5151

frontend/src/base/new-project/NewProject.jsx

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,31 @@ function NewProject({ open, setOpen, getAllProject, id }) {
6666
setOpen(false);
6767
}, [form, setOpen]);
6868

69-
const getAllConnections = useCallback(async () => {
70-
try {
71-
const data = await getAllConnectionsApi(axiosPrivate, selectedOrgId);
72-
setConnectionList(data);
73-
} catch (error) {
74-
console.error(error);
75-
notify({ error });
76-
}
77-
}, [selectedOrgId]);
69+
const getAllConnections = useCallback(
70+
async (updatedConnection) => {
71+
if (updatedConnection?.id) {
72+
setConnectionList((prev) => {
73+
const list = prev || [];
74+
const exists = list.some((c) => c?.id === updatedConnection.id);
75+
if (exists) {
76+
return list.map((c) =>
77+
c?.id === updatedConnection.id ? updatedConnection : c
78+
);
79+
}
80+
return [...list, updatedConnection];
81+
});
82+
return;
83+
}
84+
try {
85+
const data = await getAllConnectionsApi(axiosPrivate, selectedOrgId);
86+
setConnectionList(data || []);
87+
} catch (error) {
88+
console.error(error);
89+
notify({ error });
90+
}
91+
},
92+
[selectedOrgId]
93+
);
7894

7995
const getAllEnvironments = useCallback(async () => {
8096
try {

frontend/src/ide/chat-ai/Header.jsx

Lines changed: 49 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { memo, useState } from "react";
22
import PropTypes from "prop-types";
3-
import { Button, Space, Typography } from "antd";
3+
import { Button, Space, Tooltip, Typography } from "antd";
44
import {
55
FileDoneOutlined,
6-
HistoryOutlined,
76
CloseOutlined,
87
MinusOutlined,
98
PlusOutlined,
@@ -61,59 +60,60 @@ const Header = memo(function Header({
6160
</div>
6261
<div>
6362
<Space>
64-
<Button
65-
type="text"
66-
size="small"
67-
icon={<PlusOutlined />}
68-
disabled={isPromptRunning}
69-
onClick={resetSelectedChatId}
70-
/>
71-
<Button
72-
type="text"
73-
size="small"
74-
icon={<FileDoneOutlined />}
75-
disabled={isPromptRunning}
76-
onClick={handleSettingsClick}
77-
title="Context & Rules Manager"
78-
/>
79-
<Button
80-
type="text"
81-
size="small"
82-
icon={<HistoryOutlined />}
83-
disabled={isPromptRunning}
84-
onClick={resetSelectedChatId}
85-
/>
86-
{isOnboardingCompleted && (
63+
<Tooltip title="New Chat">
64+
<Button
65+
type="text"
66+
size="small"
67+
icon={<PlusOutlined />}
68+
disabled={isPromptRunning}
69+
onClick={resetSelectedChatId}
70+
/>
71+
</Tooltip>
72+
<Tooltip title="Context & Rules Manager">
8773
<Button
8874
type="text"
8975
size="small"
90-
icon={<RocketOutlined />}
76+
icon={<FileDoneOutlined />}
9177
disabled={isPromptRunning}
92-
onClick={onResetOnboarding}
93-
title="Reset Onboarding"
94-
style={{ color: "#1890ff" }}
78+
onClick={handleSettingsClick}
9579
/>
80+
</Tooltip>
81+
{isOnboardingCompleted && (
82+
<Tooltip title="Reset Onboarding">
83+
<Button
84+
type="text"
85+
size="small"
86+
icon={<RocketOutlined />}
87+
disabled={isPromptRunning}
88+
onClick={onResetOnboarding}
89+
style={{ color: "#1890ff" }}
90+
/>
91+
</Tooltip>
9692
)}
97-
<Button
98-
type="text"
99-
size="small"
100-
icon={isFullWidth ? <CompressOutlined /> : <ExpandOutlined />}
101-
onClick={toggleFullWidth}
102-
title={isFullWidth ? "Restore" : "Full Width"}
103-
/>
104-
<Button
105-
type="text"
106-
size="small"
107-
icon={<MinusOutlined />}
108-
onClick={collapseDrawer}
109-
title="Collapse"
110-
/>
111-
<Button
112-
type="text"
113-
size="small"
114-
icon={<CloseOutlined />}
115-
onClick={closeChatDrawer}
116-
/>
93+
<Tooltip title={isFullWidth ? "Restore" : "Full Width"}>
94+
<Button
95+
type="text"
96+
size="small"
97+
icon={isFullWidth ? <CompressOutlined /> : <ExpandOutlined />}
98+
onClick={toggleFullWidth}
99+
/>
100+
</Tooltip>
101+
<Tooltip title="Collapse">
102+
<Button
103+
type="text"
104+
size="small"
105+
icon={<MinusOutlined />}
106+
onClick={collapseDrawer}
107+
/>
108+
</Tooltip>
109+
<Tooltip title="Close">
110+
<Button
111+
type="text"
112+
size="small"
113+
icon={<CloseOutlined />}
114+
onClick={closeChatDrawer}
115+
/>
116+
</Tooltip>
117117
</Space>
118118
</div>
119119
</div>

frontend/src/ide/editor/no-code-toolbar/no-code-toolbar.css

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,52 @@
77

88
.no-code-toolbar-content {
99
flex: 1;
10-
overflow-x: hidden;
10+
overflow: hidden;
1111
scroll-behavior: smooth;
12-
padding: 8px 0px;
12+
padding: 12px 0px 8px;
13+
display: flex;
14+
align-items: center;
1315
}
1416

1517
/* Hidden toolbar items (overflow) */
1618
.toolbar-item-hidden {
1719
display: none !important;
1820
}
1921

20-
/* Ellipsis button */
21-
.toolbar-ellipsis-button {
22-
display: flex;
22+
/* More button — align with toolbar items that have seq_badge_wrapper above */
23+
.toolbar-more-item {
24+
margin-top: -11px;
25+
}
26+
27+
.no-code-toolbar-wrapper .toolbar-ellipsis-button {
28+
display: inline-flex;
2329
align-items: center;
2430
justify-content: center;
25-
width: 40px;
26-
height: 40px;
31+
gap: 4px;
32+
height: 32px;
33+
padding: 0 12px;
2734
border-radius: 4px;
2835
flex-shrink: 0;
2936
transition: background-color 0.2s;
3037
color: var(--icons-color);
38+
font-size: 13px;
39+
white-space: nowrap;
40+
vertical-align: middle;
41+
}
42+
43+
.toolbar-more-badge {
44+
display: inline-flex;
45+
align-items: center;
46+
justify-content: center;
47+
min-width: 18px;
48+
height: 18px;
49+
padding: 0 4px;
50+
border-radius: 4px;
51+
background-color: var(--primary, #1890ff);
52+
color: #fff;
53+
font-size: 11px;
54+
font-weight: 600;
55+
line-height: 1;
3156
}
3257

3358
.toolbar-ellipsis-button:hover {

0 commit comments

Comments
 (0)