Skip to content

Commit 1014880

Browse files
committed
Drop table button in alter dialog
1 parent 93eff78 commit 1014880

4 files changed

Lines changed: 111 additions & 1 deletion

File tree

datasette/static/app.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2542,6 +2542,22 @@ dialog.table-alter-dialog::backdrop {
25422542
color: var(--ink);
25432543
}
25442544

2545+
.table-alter-dialog .btn-danger {
2546+
background: #b91c1c;
2547+
color: #fff;
2548+
margin-right: auto;
2549+
}
2550+
2551+
.table-alter-dialog .btn-danger:hover {
2552+
background: #991b1b;
2553+
}
2554+
2555+
.table-alter-dialog .btn-danger:disabled,
2556+
.table-alter-dialog .btn-danger:disabled:hover {
2557+
background: #d98c8c;
2558+
color: #fff;
2559+
}
2560+
25452561
.table-alter-dialog .btn-primary {
25462562
background: var(--accent);
25472563
color: #fff;

datasette/static/edit-tools.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,6 +1571,7 @@ function setTableAlterDialogSaving(state, isSaving) {
15711571
state.cancelButton.disabled = isSaving;
15721572
state.addColumnButton.disabled = isSaving;
15731573
state.backButton.disabled = isSaving;
1574+
state.dropButton.disabled = isSaving;
15741575
state.saveButton.textContent = isSaving
15751576
? state.mode === "review"
15761577
? "Applying..."
@@ -2465,6 +2466,8 @@ function showTableAlterEditor(state) {
24652466
state.review.hidden = true;
24662467
state.review.textContent = "";
24672468
state.backButton.hidden = true;
2469+
var data = tableAlterData();
2470+
state.dropButton.hidden = !(data && data.dropPath);
24682471
state.saveButton.textContent = tableAlterSaveButtonText(state);
24692472
updateTableAlterMoveButtons(state);
24702473
updateTableAlterSaveButtonState(state);
@@ -2479,6 +2482,7 @@ function showTableAlterReview(state, result) {
24792482
state.review.hidden = false;
24802483
state.review.textContent = "";
24812484
state.backButton.hidden = false;
2485+
state.dropButton.hidden = true;
24822486
state.saveButton.textContent = tableAlterSaveButtonText(state);
24832487
updateTableAlterSaveButtonState(state);
24842488

@@ -2565,6 +2569,64 @@ async function applyTableAlterChanges(state, result) {
25652569
}
25662570
}
25672571

2572+
function tableAlterDatabaseUrl() {
2573+
var data = tableAlterData();
2574+
if (!data || !data.path) {
2575+
return null;
2576+
}
2577+
var url = new URL(data.path, location.href);
2578+
url.pathname = url.pathname.replace(/\/[^/]+\/-\/alter\/?$/, "");
2579+
url.search = "";
2580+
url.hash = "";
2581+
return url.toString();
2582+
}
2583+
2584+
async function dropTableFromAlterDialog(state) {
2585+
if (state.isSaving) {
2586+
return;
2587+
}
2588+
var data = tableAlterData();
2589+
if (!data || !data.dropPath) {
2590+
return;
2591+
}
2592+
if (
2593+
!window.confirm(
2594+
'Permanently delete the table "' +
2595+
data.tableName +
2596+
'"? This will delete all of its data and cannot be undone.',
2597+
)
2598+
) {
2599+
return;
2600+
}
2601+
clearTableAlterDialogError(state);
2602+
setTableAlterDialogSaving(state, true);
2603+
try {
2604+
var response = await fetch(data.dropPath, {
2605+
method: "POST",
2606+
headers: {
2607+
"Content-Type": "application/json",
2608+
Accept: "application/json",
2609+
},
2610+
body: JSON.stringify({ confirm: true }),
2611+
});
2612+
var responseData = null;
2613+
try {
2614+
responseData = await response.json();
2615+
} catch (_error) {
2616+
responseData = null;
2617+
}
2618+
if (!response.ok || (responseData && responseData.ok === false)) {
2619+
throw rowMutationRequestError(response, responseData);
2620+
}
2621+
state.shouldRestoreFocus = false;
2622+
state.dialog.close();
2623+
window.location.href = tableAlterDatabaseUrl() || "/";
2624+
} catch (error) {
2625+
setTableAlterDialogSaving(state, false);
2626+
showTableAlterDialogError(state, error.message || "Could not drop table");
2627+
}
2628+
}
2629+
25682630
async function saveTableAlterDialog(state) {
25692631
if (state.isSaving) {
25702632
return;
@@ -2646,6 +2708,7 @@ function ensureTableAlterDialog(manager) {
26462708
</div>
26472709
<div class="table-alter-review" hidden></div>
26482710
<div class="modal-footer">
2711+
<button type="button" class="btn btn-danger table-alter-drop" hidden>Drop table</button>
26492712
<button type="button" class="btn btn-ghost table-alter-back" hidden>Back</button>
26502713
<button type="button" class="btn btn-ghost table-alter-cancel">Cancel</button>
26512714
<button type="submit" class="btn btn-primary table-alter-save">Review changes</button>
@@ -2664,6 +2727,7 @@ function ensureTableAlterDialog(manager) {
26642727
columnList: dialog.querySelector(".table-alter-column-list"),
26652728
addColumnButton: dialog.querySelector(".table-alter-add-column"),
26662729
backButton: dialog.querySelector(".table-alter-back"),
2730+
dropButton: dialog.querySelector(".table-alter-drop"),
26672731
cancelButton: dialog.querySelector(".table-alter-cancel"),
26682732
saveButton: dialog.querySelector(".table-alter-save"),
26692733
currentButton: null,
@@ -2703,6 +2767,10 @@ function ensureTableAlterDialog(manager) {
27032767
closeTableAlterDialog(tableAlterDialogState);
27042768
});
27052769

2770+
tableAlterDialogState.dropButton.addEventListener("click", function () {
2771+
dropTableFromAlterDialog(tableAlterDialogState);
2772+
});
2773+
27062774
tableAlterDialogState.backButton.addEventListener("click", function () {
27072775
if (tableAlterDialogState.isSaving) {
27082776
return;

datasette/views/table.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,15 @@ async def _table_alter_ui(
422422
data["customColumnTypes"] = _custom_column_type_options_for_create_table(
423423
datasette
424424
)
425+
can_drop_table = await datasette.allowed(
426+
action="drop-table",
427+
resource=TableResource(database=database_name, table=table_name),
428+
actor=request.actor,
429+
)
430+
if can_drop_table:
431+
data["dropPath"] = "{}/-/drop".format(
432+
datasette.urls.table(database_name, table_name)
433+
)
425434
return data
426435

427436

@@ -1193,6 +1202,11 @@ def drop_table(conn):
11931202
actor=request.actor, database=database_name, table=table_name
11941203
)
11951204
)
1205+
self.ds.add_message(
1206+
request,
1207+
"Table {} dropped".format(table_name),
1208+
self.ds.WARNING,
1209+
)
11961210
return Response.json({"ok": True}, status=200)
11971211

11981212

tests/test_table_html.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1089,8 +1089,9 @@ async def test_table_alter_action_button_and_data():
10891089
"tables": {
10901090
"items": {
10911091
"permissions": {
1092-
"alter-table": {"id": "root"},
1092+
"alter-table": {"id": ["root", "alter-only"]},
10931093
"set-column-type": {"id": "root"},
1094+
"drop-table": {"id": "root"},
10941095
},
10951096
"column_types": {"name": "textarea"},
10961097
},
@@ -1147,6 +1148,7 @@ async def test_table_alter_action_button_and_data():
11471148
"textarea",
11481149
"url",
11491150
]
1151+
assert alter_data["dropPath"] == "/data/items/-/drop"
11501152
assert alter_data["columns"] == [
11511153
{
11521154
"name": "id",
@@ -1192,6 +1194,16 @@ async def test_table_alter_action_button_and_data():
11921194
is None
11931195
)
11941196
assert "alterTable" not in table_data_from_soup(soup_without_permission)
1197+
1198+
# An actor that can alter but not drop should not get a dropPath
1199+
response_alter_only = await ds.client.get(
1200+
"/data/items", actor={"id": "alter-only"}
1201+
)
1202+
assert response_alter_only.status_code == 200
1203+
alter_only_data = table_data_from_soup(
1204+
Soup(response_alter_only.text, "html.parser")
1205+
)["alterTable"]
1206+
assert "dropPath" not in alter_only_data
11951207
finally:
11961208
ds.close()
11971209

0 commit comments

Comments
 (0)