Skip to content

Commit dffc683

Browse files
committed
Seperate Queries Tab
1 parent 0a1879b commit dffc683

File tree

2 files changed

+276
-5
lines changed

2 files changed

+276
-5
lines changed

client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import { queryConfig } from "./config/query.config";
3636

3737
import AppsTab from "./components/AppsTab";
3838
import DataSourcesTab from "./components/DataSourcesTab";
39+
import QueriesTab from "./components/QueriesTab";
40+
3941
const { Title, Text } = Typography;
4042
const { TabPane } = Tabs;
4143

@@ -176,15 +178,13 @@ const WorkspaceDetail: React.FC = () => {
176178
workspace={workspace}
177179
/>
178180
</TabPane>
179-
180181
<TabPane tab={<span><CodeOutlined /> Queries</span>} key="queries">
181-
<DeployableItemsTab
182+
<QueriesTab
182183
environment={environment}
183-
config={queryConfig}
184-
additionalParams={{ workspaceId: workspace.id }}
185-
title="Queries in this Workspace"
184+
workspace={workspace}
186185
/>
187186
</TabPane>
187+
188188
</Tabs>
189189
</div>
190190
);
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip } from 'antd';
3+
import { SyncOutlined, CloudUploadOutlined, CodeOutlined } from '@ant-design/icons';
4+
import Title from 'antd/lib/typography/Title';
5+
import { Environment } from '../types/environment.types';
6+
import { Workspace } from '../types/workspace.types';
7+
import { Query } from '../types/query.types';
8+
import { getMergedWorkspaceQueries } from '../services/query.service';
9+
import { Switch, Spin, Empty } from 'antd';
10+
import { ManagedObjectType, setManagedObject, unsetManagedObject } from '../services/managed-objects.service';
11+
import { useDeployModal } from '../context/DeployModalContext';
12+
import { queryConfig } from '../config/query.config';
13+
14+
const { Search } = Input;
15+
16+
interface QueriesTabProps {
17+
environment: Environment;
18+
workspace: Workspace;
19+
}
20+
21+
const QueriesTab: React.FC<QueriesTabProps> = ({ environment, workspace }) => {
22+
const [queries, setQueries] = useState<Query[]>([]);
23+
const [stats, setStats] = useState({
24+
total: 0,
25+
managed: 0,
26+
unmanaged: 0
27+
});
28+
const [loading, setLoading] = useState(false);
29+
const [refreshing, setRefreshing] = useState(false);
30+
const [error, setError] = useState<string | null>(null);
31+
const [searchText, setSearchText] = useState('');
32+
const { openDeployModal } = useDeployModal();
33+
34+
// Fetch queries
35+
const fetchQueries = async () => {
36+
if (!workspace.id || !environment) return;
37+
38+
setLoading(true);
39+
setError(null);
40+
41+
try {
42+
const result = await getMergedWorkspaceQueries(
43+
workspace.id,
44+
environment.environmentId,
45+
environment.environmentApikey,
46+
environment.environmentApiServiceUrl!
47+
);
48+
49+
setQueries(result.queries);
50+
setStats(result.stats);
51+
} catch (err) {
52+
setError(err instanceof Error ? err.message : "Failed to fetch queries");
53+
} finally {
54+
setLoading(false);
55+
setRefreshing(false);
56+
}
57+
};
58+
59+
useEffect(() => {
60+
fetchQueries();
61+
}, [environment, workspace]);
62+
63+
// Handle refresh
64+
const handleRefresh = () => {
65+
setRefreshing(true);
66+
fetchQueries();
67+
};
68+
69+
// Toggle managed status
70+
const handleToggleManaged = async (query: Query, checked: boolean) => {
71+
setRefreshing(true);
72+
try {
73+
if (checked) {
74+
await setManagedObject(
75+
query.gid,
76+
environment.environmentId,
77+
ManagedObjectType.QUERY,
78+
query.name
79+
);
80+
} else {
81+
await unsetManagedObject(
82+
query.gid,
83+
environment.environmentId,
84+
ManagedObjectType.QUERY
85+
);
86+
}
87+
88+
// Update the query in state
89+
const updatedQueries = queries.map(item => {
90+
if (item.id === query.id) {
91+
return { ...item, managed: checked };
92+
}
93+
return item;
94+
});
95+
96+
setQueries(updatedQueries);
97+
98+
// Update stats
99+
const managed = updatedQueries.filter(q => q.managed).length;
100+
setStats(prev => ({
101+
...prev,
102+
managed,
103+
unmanaged: prev.total - managed
104+
}));
105+
106+
message.success(`${query.name} is now ${checked ? 'Managed' : 'Unmanaged'}`);
107+
return true;
108+
} catch (error) {
109+
message.error(`Failed to change managed status for ${query.name}`);
110+
return false;
111+
} finally {
112+
setRefreshing(false);
113+
}
114+
};
115+
116+
// Filter queries based on search
117+
const filteredQueries = searchText
118+
? queries.filter(query =>
119+
query.name.toLowerCase().includes(searchText.toLowerCase()) ||
120+
query.id.toLowerCase().includes(searchText.toLowerCase()))
121+
: queries;
122+
123+
// Table columns
124+
const columns = [
125+
{
126+
title: 'Name',
127+
dataIndex: 'name',
128+
key: 'name',
129+
render: (text: string) => <span className="query-name">{text}</span>
130+
},
131+
{
132+
title: 'ID',
133+
dataIndex: 'id',
134+
key: 'id',
135+
ellipsis: true,
136+
},
137+
{
138+
title: 'Creator',
139+
dataIndex: 'creatorName',
140+
key: 'creatorName',
141+
},
142+
{
143+
title: 'Managed',
144+
key: 'managed',
145+
render: (_: any, query: Query) => (
146+
<Switch
147+
checked={!!query.managed}
148+
onChange={(checked: boolean) => handleToggleManaged(query, checked)}
149+
loading={refreshing}
150+
size="small"
151+
/>
152+
),
153+
},
154+
{
155+
title: 'Actions',
156+
key: 'actions',
157+
render: (_: any, query: Query) => (
158+
<Space onClick={(e) => e.stopPropagation()}>
159+
<Tooltip title={!query.managed ? "Query must be managed before it can be deployed" : "Deploy this query to another environment"}>
160+
<Button
161+
type="primary"
162+
size="small"
163+
icon={<CloudUploadOutlined />}
164+
onClick={() => openDeployModal(query, queryConfig, environment)}
165+
disabled={!query.managed}
166+
>
167+
Deploy
168+
</Button>
169+
</Tooltip>
170+
</Space>
171+
),
172+
}
173+
];
174+
175+
return (
176+
<Card>
177+
{/* Header with refresh button */}
178+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "16px" }}>
179+
<Title level={5}>Queries in this Workspace</Title>
180+
<Button
181+
icon={<SyncOutlined spin={refreshing} />}
182+
onClick={handleRefresh}
183+
loading={loading}
184+
>
185+
Refresh
186+
</Button>
187+
</div>
188+
189+
{/* Stats display */}
190+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '24px', marginBottom: '16px' }}>
191+
<div>
192+
<div style={{ fontSize: '14px', color: '#8c8c8c' }}>Total Queries</div>
193+
<div style={{ fontSize: '24px', fontWeight: 600 }}>{stats.total}</div>
194+
</div>
195+
<div>
196+
<div style={{ fontSize: '14px', color: '#8c8c8c' }}>Managed</div>
197+
<div style={{ fontSize: '24px', fontWeight: 600 }}>{stats.managed}</div>
198+
</div>
199+
<div>
200+
<div style={{ fontSize: '14px', color: '#8c8c8c' }}>Unmanaged</div>
201+
<div style={{ fontSize: '24px', fontWeight: 600 }}>{stats.unmanaged}</div>
202+
</div>
203+
</div>
204+
205+
<Divider style={{ margin: "16px 0" }} />
206+
207+
{/* Error display */}
208+
{error && (
209+
<Alert
210+
message="Error loading queries"
211+
description={error}
212+
type="error"
213+
showIcon
214+
style={{ marginBottom: "16px" }}
215+
/>
216+
)}
217+
218+
{/* Configuration warnings */}
219+
{(!environment.environmentApikey || !environment.environmentApiServiceUrl) && !error && (
220+
<Alert
221+
message="Configuration Issue"
222+
description="Missing required configuration: API key or API service URL"
223+
type="warning"
224+
showIcon
225+
style={{ marginBottom: "16px" }}
226+
/>
227+
)}
228+
229+
{/* Content */}
230+
{loading ? (
231+
<div style={{ display: 'flex', justifyContent: 'center', padding: '20px' }}>
232+
<Spin tip="Loading queries..." />
233+
</div>
234+
) : queries.length === 0 ? (
235+
<Empty
236+
description={error || "No queries found in this workspace"}
237+
image={Empty.PRESENTED_IMAGE_SIMPLE}
238+
/>
239+
) : (
240+
<>
241+
{/* Search Bar */}
242+
<div style={{ marginBottom: 16 }}>
243+
<Search
244+
placeholder="Search queries by name or ID"
245+
allowClear
246+
onSearch={value => setSearchText(value)}
247+
onChange={e => setSearchText(e.target.value)}
248+
style={{ width: 300 }}
249+
/>
250+
{searchText && filteredQueries.length !== queries.length && (
251+
<div style={{ marginTop: 8 }}>
252+
Showing {filteredQueries.length} of {queries.length} queries
253+
</div>
254+
)}
255+
</div>
256+
257+
<Table
258+
columns={columns}
259+
dataSource={filteredQueries}
260+
rowKey="id"
261+
pagination={{ pageSize: 10 }}
262+
size="middle"
263+
scroll={{ x: 'max-content' }}
264+
/>
265+
</>
266+
)}
267+
</Card>
268+
);
269+
};
270+
271+
export default QueriesTab;

0 commit comments

Comments
 (0)