Skip to content
This repository was archived by the owner on Mar 4, 2025. It is now read-only.

Commit 761aa87

Browse files
committed
webui: Complete redesign of the execute SQL page
This migrates the execute SQL page to React. While doing so the design and features are changed a lot. The layout vaguely resembles a terminal session now which means that you see the history of the last executed SQL statements as you type in the next statement. The idea is to differentiate the execute and the visualise page by making the execute page aim more at modifying statements (no need to be able to save them under a name most of the time, multiple in a row are common, feedback what was already done is helpful). Besides showing a command history also the editor is improved. It now features a better font as well as syntax highlighting. It auto focuses when the page is loaded so you can just start typing. There is also a feature to execute the commands using the keyboard (including by just pressing the Enter key), making this more similar to using a shell. Less common options like the format SQL button are moved to a menu. This drops the concept of saved SQL statements for the execute page. The database migrations are therefore a bit more drastic: DROP TABLE live_user_sql_statements; Besides not being used in the current frontend approach anymore, the live_user_sql_statements table also had no column to save statements per database. In many contexts however, applying a statement between different databases (and also on a regular basis) seems pretty rare.
1 parent f80d173 commit 761aa87

File tree

13 files changed

+358
-982
lines changed

13 files changed

+358
-982
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ webui/js/discussion-list.js
6666
webui/js/format.js
6767
webui/js/markdown-editor.js
6868
webui/js/profile-page.js
69+
webui/js/sql-terminal.js
6970
webui/js/user-page.js
7071

7172
# Local secrets

common/postgresql.go

Lines changed: 0 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -2601,124 +2601,6 @@ func LiveAddDatabasePG(dbOwner, dbName, bucketName, liveNode string, accessType
26012601
return nil
26022602
}
26032603

2604-
// LiveExecuteSQLDelete deletes a saved SQL statement for a user
2605-
func LiveExecuteSQLDelete(dbOwner, sqlName string) (err error) {
2606-
var commandTag pgx.CommandTag
2607-
dbQuery := `
2608-
WITH u AS (
2609-
SELECT user_id
2610-
FROM users
2611-
WHERE lower(user_name) = lower($1)
2612-
)
2613-
DELETE FROM live_user_sql_statements
2614-
WHERE user_id = (SELECT user_id FROM u)
2615-
AND sql_name = $2`
2616-
commandTag, err = pdb.Exec(dbQuery, dbOwner, sqlName)
2617-
if err != nil {
2618-
log.Printf("Deleting SQL statement '%s' for user '%s' failed: %s", SanitiseLogString(sqlName), SanitiseLogString(dbOwner), err)
2619-
return err
2620-
}
2621-
if numRows := commandTag.RowsAffected(); numRows != 1 {
2622-
log.Printf("Wrong number of rows (%d) affected while deleting SQL statement '%s' for user '%s'", numRows,
2623-
SanitiseLogString(sqlName), SanitiseLogString(dbOwner))
2624-
}
2625-
return
2626-
}
2627-
2628-
// LiveExecuteSQLGet returns the SQL text for a given users' requested SQL statement
2629-
func LiveExecuteSQLGet(dbOwner, sqlName string) (sqlText string, err error) {
2630-
dbQuery := `
2631-
WITH u AS (
2632-
SELECT user_id
2633-
FROM users
2634-
WHERE lower(user_name) = lower($1)
2635-
)
2636-
SELECT sql_stmt
2637-
FROM live_user_sql_statements, u
2638-
WHERE live_user_sql_statements.user_id = u.user_id
2639-
AND live_user_sql_statements.sql_name = $2`
2640-
e := pdb.QueryRow(dbQuery, dbOwner, sqlName).Scan(&sqlText)
2641-
if e != nil {
2642-
if e == pgx.ErrNoRows {
2643-
// There weren't any saved parameters for this database visualisation
2644-
return
2645-
}
2646-
2647-
// A real database error occurred
2648-
err = e
2649-
log.Printf("Retrieving SQL statement text for '%s', statement '%s' failed: %s", SanitiseLogString(dbOwner),
2650-
SanitiseLogString(sqlName), e)
2651-
return
2652-
}
2653-
return
2654-
}
2655-
2656-
// LiveExecuteSQLList returns the list of saved SQL statements for a given user
2657-
func LiveExecuteSQLList(dbOwner string) (sqlNames []string, err error) {
2658-
dbQuery := `
2659-
WITH u AS (
2660-
SELECT user_id
2661-
FROM users
2662-
WHERE lower(user_name) = lower($1)
2663-
)
2664-
SELECT sql_name
2665-
FROM live_user_sql_statements, u
2666-
WHERE live_user_sql_statements.user_id = u.user_id`
2667-
rows, e := pdb.Query(dbQuery, dbOwner)
2668-
if e != nil {
2669-
if e == pgx.ErrNoRows {
2670-
// There weren't any saved SQL statements for this user
2671-
return
2672-
}
2673-
2674-
// A real database error occurred
2675-
err = e
2676-
log.Printf("Retrieving SQL statement list for user '%s' failed: %s", dbOwner, e)
2677-
return
2678-
}
2679-
defer rows.Close()
2680-
2681-
var s string
2682-
for rows.Next() {
2683-
err = rows.Scan(&s)
2684-
if err != nil {
2685-
log.Printf("Error retrieving SQL statement list: %s", err.Error())
2686-
return
2687-
}
2688-
sqlNames = append(sqlNames, s)
2689-
}
2690-
sort.Strings(sqlNames)
2691-
return
2692-
}
2693-
2694-
// LiveExecuteSQLSave saves an "Execute SQL" statement for live databases
2695-
// Note: as a first attempt at this, lets have the SQL statements not be bound to any one database belonging to the
2696-
// user. eg if the user saves a SQL statement for database A, it'll still appear in the drop-down list for their other
2697-
// databases. Could be useful, but we can change this if it turns out to be a pain
2698-
func LiveExecuteSQLSave(dbOwner, sqlName, sqlText string) (err error) {
2699-
var commandTag pgx.CommandTag
2700-
dbQuery := `
2701-
WITH u AS (
2702-
SELECT user_id
2703-
FROM users
2704-
WHERE lower(user_name) = lower($1)
2705-
)
2706-
INSERT INTO live_user_sql_statements (user_id, sql_name, sql_stmt)
2707-
VALUES ((SELECT user_id FROM u), $2, $3)
2708-
ON CONFLICT (user_id, sql_name)
2709-
DO UPDATE
2710-
SET sql_stmt = $3`
2711-
commandTag, err = pdb.Exec(dbQuery, dbOwner, sqlName, sqlText)
2712-
if err != nil {
2713-
return err
2714-
}
2715-
if numRows := commandTag.RowsAffected(); numRows != 1 {
2716-
log.Printf("Wrong number of rows (%d) affected while saving SQL statement '%s' for user '%s'", numRows,
2717-
SanitiseLogString(sqlName), SanitiseLogString(dbOwner))
2718-
}
2719-
return
2720-
}
2721-
27222604
// LiveGenerateMinioNames generates Minio bucket and object names for a live database
27232605
func LiveGenerateMinioNames(userName string) (bucketName, objectName string, err error) {
27242606
// If the user already has a Minio bucket name assigned, then we use it
@@ -3162,7 +3044,6 @@ func ResetDB() error {
31623044
"discussions",
31633045
"email_queue",
31643046
"events",
3165-
"live_user_sql_statements",
31663047
"sqlite_databases",
31673048
"users",
31683049
"vis_params",
@@ -3182,7 +3063,6 @@ func ResetDB() error {
31823063
"discussions_disc_id_seq",
31833064
"email_queue_email_id_seq",
31843065
"events_event_id_seq",
3185-
"live_user_sql_statements_stmt_id_seq",
31863066
"sqlite_databases_db_id_seq",
31873067
"users_user_id_seq",
31883068
"vis_query_runs_query_run_id_seq",

cypress/e2e/1-webui/live_execute_sql.cy.js

Lines changed: 10 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -7,86 +7,23 @@ describe('live execute', () => {
77
cy.request('/x/test/seed')
88
})
99

10-
// Save a SQL statement
11-
it('save a SQL statement using non-default name', () => {
10+
// "Format SQL" button
11+
it('"Format SQL" button', () => {
1212
cy.visit('/exec/default/Join Testing with index.sqlite')
13-
cy.get('[data-cy="sqltab"]').click()
14-
cy.get('[data-cy="usersqltext"]').type(
13+
14+
// Type in some SQL statement
15+
cy.get('.sql-terminal-input').find('textarea').type(
1516
'CREATE TABLE livetest1(\n' +
1617
'field1 INTEGER, field2 TEXT, field3 INTEGER\n' +
1718
')')
18-
cy.get('[data-cy="nameinput"]').type('{selectall}{backspace}').type('livetest1')
19-
cy.get('[data-cy="savebtn"]').click()
20-
cy.get('[data-cy="statusmsg"]').should('contain.text', 'SQL statement \'livetest1\' saved')
21-
22-
// Verify the SQL statement - it should be automatically selected in the drop down list as it's the only one
23-
cy.visit('/exec/default/Join Testing with index.sqlite')
24-
cy.get('[data-cy="selectedexec"]').should('contain.text', 'livetest1')
25-
})
26-
27-
// Check if 'default' SQL statement is still chosen by default even when created after non-default ones
28-
it('save a SQL statement using default name', () => {
29-
cy.visit('/exec/default/Join Testing with index.sqlite')
30-
cy.get('[data-cy="sqltab"]').click()
31-
cy.get('[data-cy="usersqltext"]').type('{selectall}{backspace}').type(
32-
'CREATE TABLE defaulttest (field2 INTEGER)')
33-
cy.get('[data-cy="nameinput"]').type('{selectall}{backspace}').type('default')
34-
cy.get('[data-cy="savebtn"]').click()
35-
cy.get('[data-cy="statusmsg"]').should('contain.text', 'SQL statement \'default\' saved')
36-
37-
// Verify the SQL statement - it should be automatically selected in the drop down list as it's the default
38-
cy.visit('/exec/default/Join Testing with index.sqlite')
39-
cy.get('[data-cy="selectedexec"]').should('contain.text', 'default')
40-
cy.get('[data-cy="usersqltext"]').should('contain.text',
41-
'CREATE TABLE defaulttest (field2 INTEGER)')
42-
})
43-
44-
// Save a SQL statement
45-
it('save a SQL statement with name alphabetically lower than \'default\'', () => {
46-
cy.visit('/exec/default/Join Testing with index.sqlite')
47-
cy.get('[data-cy="sqltab"]').click()
48-
cy.get('[data-cy="usersqltext"]').type('{selectall}{backspace}').type(
49-
'CREATE TABLE stuff (field3 INTEGER)')
50-
cy.get('[data-cy="nameinput"]').type('{selectall}{backspace}').type('abc')
51-
cy.get('[data-cy="savebtn"]').click()
52-
cy.get('[data-cy="statusmsg"]').should('contain.text', 'SQL statement \'abc\' saved')
53-
54-
// Check that the SQL statement is present, but not automatically selected when the page loads
55-
cy.visit('/exec/default/Join Testing with index.sqlite')
56-
cy.get('[data-cy="selectedexec"]').should('not.contain.text', 'abc')
57-
})
58-
59-
// Save over an existing SQL statement
60-
it('save over an existing SQL statement', () => {
61-
cy.visit('/exec/default/Join Testing with index.sqlite')
62-
cy.get('[data-cy="sqltab"]').click()
63-
cy.get('[data-cy="usersqltext"]').type('{selectall}{backspace}').type(
64-
'DROP TABLE table1')
65-
cy.get('[data-cy="nameinput"]').type('{selectall}{backspace}').type('default')
66-
cy.get('[data-cy="savebtn"]').click()
67-
cy.get('[data-cy="statusmsg"]').should('contain.text', 'SQL statement \'default\' saved')
68-
69-
// Verify the new SQL statement text
70-
cy.visit('/exec/default/Join Testing with index.sqlite')
71-
cy.get('[data-cy="execdropdown"]').click()
72-
cy.get('[data-cy="exec-default"]').click()
73-
cy.get('[data-cy="usersqltext"]').should('contain',
74-
'DROP TABLE table1')
75-
})
76-
77-
// "Format SQL" button
78-
it('"Format SQL" button', () => {
79-
// Start with the existing "livetest1" test
80-
cy.visit('/exec/default/Join Testing with index.sqlite')
81-
cy.get('[data-cy="execdropdown"]').click()
82-
cy.get('[data-cy="exec-livetest1"]').click()
8319

8420
// Click the format button
85-
cy.get('[data-cy="formatsqlbtn"]').click()
21+
cy.get('[data-cy="dropdownbtn"]').click()
22+
cy.get('[data-cy="formatbtn"]').click()
8623

8724
// Verify the changed text
8825
cy.wait(waitTime)
89-
cy.get('[data-cy="usersqltext"]').should('contain',
26+
cy.get('.sql-terminal-input').find('textarea').should('contain',
9027
'CREATE TABLE\n' +
9128
' livetest1 (field1 INTEGER, field2 TEXT, field3 INTEGER)')
9229
})
@@ -100,32 +37,16 @@ describe('live execute', () => {
10037

10138
// Remove the view using the Execute SQL button
10239
cy.visit('/exec/default/Join Testing with index.sqlite')
103-
cy.get('[data-cy="sqltab"]').click()
104-
cy.get('[data-cy="usersqltext"]').type('{selectall}{backspace}').type(
40+
cy.get('.sql-terminal-input').find('textarea').type(
10541
'DROP VIEW joinedView')
106-
cy.get('[data-cy="execsqlbtn"]').click()
42+
cy.get('[data-cy="executebtn"]').click()
10743

10844
// Verify the view has been removed
10945
cy.visit('/default/Join Testing with index.sqlite')
11046
cy.get('[name="viewtable"]').parent('.react-dropdown-select').click()
11147
cy.get('[name="viewtable"]').siblings('.react-dropdown-select-dropdown').find('.react-dropdown-select-item').contains('joinedView').should('not.exist')
11248
})
11349

114-
// "Delete" button
115-
it('Delete button', () => {
116-
cy.visit('/exec/default/Join Testing with index.sqlite')
117-
cy.get('[data-cy="execdropdown"]').click()
118-
cy.get('[data-cy="exec-abc"]').click()
119-
120-
// Click the Delete button
121-
cy.get('[data-cy="delexecbtn"]').click()
122-
123-
// Verify the result
124-
cy.wait(waitTime)
125-
cy.get('[data-cy="execdropdown"]').click()
126-
cy.get('[data-cy="exec-abc"]').should('not.exist')
127-
})
128-
12950
// Verify only the owner can see this Execute SQL page
13051
it('Verify private database Execute SQL page is indeed private', () => {
13152
// Switch to a different user

cypress/e2e/1-webui/live_visualisation.cy.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,9 @@ describe('live visualisation', () => {
156156
it('Y axis column drop down', () => {
157157
// Add a third column to table2
158158
cy.visit('/exec/default/Join Testing with index.sqlite')
159-
cy.get('[data-cy="sqltab"]').click()
160-
cy.get('[data-cy="usersqltext"]').type('{selectall}{backspace}').type(
159+
cy.get('.sql-terminal-input').find('textarea').type(
161160
'ALTER TABLE table2 ADD COLUMN value2 INTEGER DEFAULT 8')
162-
cy.get('[data-cy="execsqlbtn"]').click()
161+
cy.get('[data-cy="executebtn"]').click()
163162

164163
// Create a visualisation with a third column
165164
cy.visit('/vis/default/Join Testing with index.sqlite')
@@ -293,4 +292,4 @@ describe('live visualisation', () => {
293292
// Switch back to the default user
294293
cy.request('/x/test/switchdefault')
295294
})
296-
})
295+
})

database/dbhub.sql

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -425,44 +425,6 @@ CREATE SEQUENCE public.events_event_id_seq
425425
ALTER SEQUENCE public.events_event_id_seq OWNED BY public.events.event_id;
426426

427427

428-
--
429-
-- Name: live_user_sql_statements; Type: TABLE; Schema: public; Owner: -
430-
--
431-
432-
CREATE TABLE public.live_user_sql_statements (
433-
stmt_id bigint NOT NULL,
434-
user_id bigint NOT NULL,
435-
sql_name text NOT NULL,
436-
sql_stmt text
437-
);
438-
439-
440-
--
441-
-- Name: TABLE live_user_sql_statements; Type: COMMENT; Schema: public; Owner: -
442-
--
443-
444-
COMMENT ON TABLE public.live_user_sql_statements IS 'This table holds sql statements saved by users for their Live databases';
445-
446-
447-
--
448-
-- Name: live_user_sql_statements_stmt_id_seq; Type: SEQUENCE; Schema: public; Owner: -
449-
--
450-
451-
CREATE SEQUENCE public.live_user_sql_statements_stmt_id_seq
452-
START WITH 1
453-
INCREMENT BY 1
454-
NO MINVALUE
455-
NO MAXVALUE
456-
CACHE 1;
457-
458-
459-
--
460-
-- Name: live_user_sql_statements_stmt_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
461-
--
462-
463-
ALTER SEQUENCE public.live_user_sql_statements_stmt_id_seq OWNED BY public.live_user_sql_statements.stmt_id;
464-
465-
466428
--
467429
-- Name: sqlite_databases; Type: TABLE; Schema: public; Owner: -
468430
--
@@ -707,13 +669,6 @@ ALTER TABLE ONLY public.email_queue ALTER COLUMN email_id SET DEFAULT nextval('p
707669
ALTER TABLE ONLY public.events ALTER COLUMN event_id SET DEFAULT nextval('public.events_event_id_seq'::regclass);
708670

709671

710-
--
711-
-- Name: live_user_sql_statements stmt_id; Type: DEFAULT; Schema: public; Owner: -
712-
--
713-
714-
ALTER TABLE ONLY public.live_user_sql_statements ALTER COLUMN stmt_id SET DEFAULT nextval('public.live_user_sql_statements_stmt_id_seq'::regclass);
715-
716-
717672
--
718673
-- Name: sqlite_databases db_id; Type: DEFAULT; Schema: public; Owner: -
719674
--
@@ -1001,13 +956,6 @@ CREATE INDEX fki_discussion_comments_db_id_fkey ON public.discussion_comments US
1001956
CREATE INDEX fki_discussions_source_db_id_fkey ON public.discussions USING btree (mr_source_db_id);
1002957

1003958

1004-
--
1005-
-- Name: live_user_sql_statements_user_id_sql_name_uindex; Type: INDEX; Schema: public; Owner: -
1006-
--
1007-
1008-
CREATE UNIQUE INDEX live_user_sql_statements_user_id_sql_name_uindex ON public.live_user_sql_statements USING btree (user_id, sql_name);
1009-
1010-
1011959
--
1012960
-- Name: users_lower_user_name_idx; Type: INDEX; Schema: public; Owner: -
1013961
--
@@ -1187,14 +1135,6 @@ ALTER TABLE ONLY public.events
11871135
ADD CONSTRAINT events_db_id_fkey FOREIGN KEY (db_id) REFERENCES public.sqlite_databases(db_id) ON UPDATE CASCADE ON DELETE CASCADE;
11881136

11891137

1190-
--
1191-
-- Name: live_user_sql_statements live_user_sql_statements_users_user_id_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
1192-
--
1193-
1194-
ALTER TABLE ONLY public.live_user_sql_statements
1195-
ADD CONSTRAINT live_user_sql_statements_users_user_id_fk FOREIGN KEY (user_id) REFERENCES public.users(user_id);
1196-
1197-
11981138
--
11991139
-- Name: sqlite_databases sqlite_databases_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
12001140
--

0 commit comments

Comments
 (0)