Skip to content

Commit 8a24837

Browse files
authored
Merge pull request #1167 from merico-dev/1165-able-to-list-delete-unused-queries
1165 able to list delete unused queries
2 parents 8052bab + 7135270 commit 8a24837

File tree

20 files changed

+205
-34
lines changed

20 files changed

+205
-34
lines changed

api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtable/api",
3-
"version": "10.30.0",
3+
"version": "10.31.0",
44
"description": "",
55
"main": "index.js",
66
"scripts": {

dashboard/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtable/dashboard",
3-
"version": "10.30.0",
3+
"version": "10.31.0",
44
"license": "Apache-2.0",
55
"publishConfig": {
66
"access": "public",

dashboard/src/dashboard-editor/model/content/index.ts

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -165,44 +165,68 @@ const _ContentModel = types
165165
},
166166
}))
167167
.views((self) => ({
168-
findQueryUsage(queryID: string) {
168+
get queriesUsage() {
169169
const panelIDMap = self.panels.idMap;
170-
const panels: QueryUsageType[] = self.views.current.flatMap((v) =>
171-
v.panelIDs
172-
.map((id) => panelIDMap.get(id))
173-
.filter((p): p is PanelModelInstance => p?.queryIDSet.has(queryID) ?? false)
174-
.map((p) => ({
175-
type: 'panel',
176-
id: p.id,
177-
label: p.title ? p.title : p.viz.type,
178-
views: [
179-
{
180-
id: v.id,
181-
label: v.name,
182-
},
183-
],
184-
})),
185-
);
170+
const usages: QueryUsageType[] = [];
171+
self.views.current.forEach((v) => {
172+
v.panelIDs.forEach((pid) => {
173+
const p = panelIDMap.get(pid);
174+
if (!p) {
175+
return;
176+
}
177+
178+
const type = 'panel';
179+
const label = p.title ? p.title : p.viz.type;
180+
const views = [
181+
{
182+
id: v.id,
183+
label: v.name,
184+
},
185+
];
186+
p.queryIDs.forEach((queryID) => {
187+
usages.push({
188+
id: pid,
189+
queryID,
190+
type,
191+
label,
192+
views,
193+
});
194+
});
195+
});
196+
});
186197

187198
const viewIDMap = self.views.idMap;
188-
const filters: QueryUsageType[] = self.filters.current
199+
self.filters.current
189200
.filter((f) => {
190201
const filterQueryID = _.get(f, 'config.options_query_id');
191-
return filterQueryID === queryID;
202+
return !!filterQueryID;
192203
})
193204
.map((f) => ({
194205
type: 'filter',
195206
id: f.id,
207+
queryID: _.get(f, 'config.options_query_id'),
196208
label: f.label,
197209
views: f.visibleInViewsIDs.map((id) => ({
198210
id,
199211
label: viewIDMap.get(id)?.name ?? id,
200212
})),
201213
}));
202-
return panels.concat(filters);
214+
215+
return _.groupBy(usages, 'queryID');
216+
},
217+
get hasUnusedQueries() {
218+
return self.queries.current.length > Object.keys(this.queriesUsage).length;
219+
},
220+
findQueryUsage(queryID: string) {
221+
return this.queriesUsage[queryID] ?? [];
203222
},
204223
}))
205224
.actions((self) => ({
225+
removeUnusedQueries() {
226+
const usedQueries = new Set(Object.keys(self.queriesUsage));
227+
const ids = self.queries.current.filter((q) => !usedQueries.has(q.id)).map((q) => q.id);
228+
self.queries.removeQueries(ids);
229+
},
206230
duplicatePanelByID(panelID: string, viewID: string) {
207231
const newID = self.panels.duplicateByID(panelID);
208232
if (!newID) {

dashboard/src/dashboard-editor/model/editor/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@ export type NavActionType = {
1818
label: string;
1919
value: string;
2020
_type: 'ACTION';
21-
_action_type: '_Add_A_Filter_' | '_Add_A_SQL_SNIPPET_' | '_Add_A_QUERY_' | '_Add_A_VIEW_' | '_Add_A_PANEL_';
21+
_action_type:
22+
| '_Add_A_Filter_'
23+
| '_Add_A_SQL_SNIPPET_'
24+
| '_Add_A_QUERY_'
25+
| '_Add_A_VIEW_'
26+
| '_Add_A_PANEL_'
27+
| '_QUERIES_SETTINGS_';
2228
parentID?: string; // for panel only
2329
Icon: null;
2430
children: null;
@@ -42,6 +48,7 @@ export type ValidEditorPathType =
4248
| ['_MOCK_CONTEXT_']
4349
| ['_FILTERS_', string]
4450
| ['_SQL_SNIPPETS_', string]
51+
| ['_QUERIES_']
4552
| ['_QUERIES_', string]
4653
| ['_VIEWS_', string]
4754
| ['_VIEWS_', string, '_PANELS_', string]
@@ -113,7 +120,7 @@ export const EditorModel = types
113120
label: 'Queries',
114121
value: '_QUERIES_',
115122
Icon: IconDatabase,
116-
children: [...queries.options, getActionOption('_Add_A_QUERY_')],
123+
children: [getActionOption('_QUERIES_SETTINGS_'), ...queries.options, getActionOption('_Add_A_QUERY_')],
117124
_type: 'GROUP',
118125
},
119126
{

dashboard/src/dashboard-editor/model/queries/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export const QueriesModel = QueriesRenderModel.views((self) => ({
1414
);
1515
return _.sortBy(options, (o) => o.label.toLowerCase());
1616
},
17+
get sortedList() {
18+
return _.sortBy(self.current, (o) => o.name.toLowerCase());
19+
},
1720
})).actions((self) => ({
1821
replace(current: Array<QueryRenderModelInstance>) {
1922
self.current = cast(current);
@@ -34,6 +37,17 @@ export const QueriesModel = QueriesRenderModel.views((self) => ({
3437
self.current.remove(query);
3538
}
3639
},
40+
removeQueries(queryIDs: string[]) {
41+
const set = new Set(queryIDs);
42+
self.current.forEach((q) => {
43+
if (set.has(q.id)) {
44+
detach(q);
45+
}
46+
});
47+
const list = [...self.current];
48+
_.remove(list, (q) => set.has(q.id));
49+
self.current = cast(list);
50+
},
3751
}));
3852

3953
export type QueriesModelInstance = Instance<typeof QueriesModel>;
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { Box, Button, Flex, Stack, Table, Text } from '@mantine/core';
2+
import { useModals } from '@mantine/modals';
3+
import { IconTrash } from '@tabler/icons-react';
4+
import { observer } from 'mobx-react-lite';
5+
import { useEditDashboardContext } from '~/contexts';
6+
7+
export const EditQueries = observer(() => {
8+
const modals = useModals();
9+
const model = useEditDashboardContext();
10+
const navigateToQuery = (queryID: string) => {
11+
model.editor.setPath(['_QUERIES_', queryID]);
12+
};
13+
14+
const removeUnusedQueriesWithConfirmation = () => {
15+
modals.openConfirmModal({
16+
title: 'Delete ununsed queries?',
17+
children: <Text size="sm">This action cannot be undone.</Text>,
18+
labels: { confirm: 'Confirm', cancel: 'Cancel' },
19+
onCancel: () => console.log('Cancel'),
20+
onConfirm: () => model.content.removeUnusedQueries(),
21+
confirmProps: { color: 'red' },
22+
zIndex: 320,
23+
});
24+
};
25+
26+
const usages = model.content.queriesUsage;
27+
return (
28+
<Stack sx={{ height: '100%' }} spacing="sm" pb={'59px'}>
29+
<Box pt={9} pb={8} sx={{ borderBottom: '1px solid #eee' }}>
30+
<Text px="md" align="left" sx={{ userSelect: 'none', cursor: 'default' }}>
31+
Manage Queries
32+
</Text>
33+
</Box>
34+
<Flex justify="flex-end" align="center" px={12}>
35+
<Button
36+
variant="subtle"
37+
size="xs"
38+
color="red"
39+
leftIcon={<IconTrash size={14} />}
40+
disabled={!model.content.hasUnusedQueries}
41+
onClick={removeUnusedQueriesWithConfirmation}
42+
>
43+
Delete unused queries
44+
</Button>
45+
</Flex>
46+
<Box sx={{ flexGrow: 1, overflow: 'auto' }}>
47+
<Table fontSize="sm" highlightOnHover sx={{ tableLayout: 'fixed' }}>
48+
<thead>
49+
<tr>
50+
<th>Name</th>
51+
<th style={{ width: '200px' }}>Data Source</th>
52+
<th style={{ width: '100px', textAlign: 'right' }}>Type</th>
53+
<th style={{ width: '100px', textAlign: 'center' }}>Usage</th>
54+
<th style={{ width: '300px', paddingLeft: '24px' }}>Action</th>
55+
</tr>
56+
</thead>
57+
<tbody>
58+
{model.content.queries.sortedList.map((q) => {
59+
const usageCount = usages[q.id]?.length ?? 0;
60+
return (
61+
<tr key={q.id}>
62+
<td>{q.name}</td>
63+
<td>{q.key}</td>
64+
<td style={{ textAlign: 'right' }}>{q.type}</td>
65+
<td
66+
style={{
67+
color: usageCount === 0 ? '#ff0000' : '#000',
68+
fontWeight: usageCount === 0 ? 'bold' : 'normal',
69+
textAlign: 'center',
70+
}}
71+
>
72+
{usageCount}
73+
</td>
74+
<td>
75+
<Button variant="subtle" size="xs" onClick={() => navigateToQuery(q.id)}>
76+
Open
77+
</Button>
78+
</td>
79+
</tr>
80+
);
81+
})}
82+
</tbody>
83+
</Table>
84+
</Box>
85+
</Stack>
86+
);
87+
});

dashboard/src/dashboard-editor/ui/settings/content/index.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import { EditPanel } from './edit-panel';
77
import { EditQuery } from './edit-query';
88
import { EditSQLSnippet } from './edit-sql-snippet';
99
import { EditView } from './edit-view';
10-
import { isQueryVars, isMockContext, isFilter, isSQLSnippet, isQuery, isView, isPanel } from './utils';
10+
import { isQueryVars, isMockContext, isFilter, isSQLSnippet, isQuery, isView, isPanel, isQueries } from './utils';
1111
import { ViewQueryVars } from './view-query-vars';
12+
import { EditQueries } from './edit-queries';
1213

1314
const Content = observer(() => {
1415
const editor = useEditDashboardContext().editor;
@@ -29,6 +30,9 @@ const Content = observer(() => {
2930
if (isSQLSnippet(path)) {
3031
return <EditSQLSnippet id={path[1]} />;
3132
}
33+
if (isQueries(path)) {
34+
return <EditQueries />;
35+
}
3236
if (isQuery(path)) {
3337
return <EditQuery id={path[1]} />;
3438
}

dashboard/src/dashboard-editor/ui/settings/content/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ export function isQuery(path: ValidEditorPathType) {
2020
return path.length === 2 && path[0] === '_QUERIES_';
2121
}
2222

23+
export function isQueries(path: ValidEditorPathType) {
24+
return path.length === 1 && path[0] === '_QUERIES_';
25+
}
26+
2327
export function isView(path: ValidEditorPathType) {
2428
return path.length === 2 && path[0] === '_VIEWS_';
2529
}
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)