Skip to content

Commit c61fe37

Browse files
fizzbooptdstein
andauthored
Add Delete Content Functionality (#134)
* Remove cog icon and add delete content functionality * Rename modal and troubleshooting delete method * fix: remove trailing '/' For some reason, FastAPI does not accept a trailing slash on the end of the URL path. * Reload list of content after deletion and version bump * Update manifest.json * Fix build issue with manifest * Add dist/ files for manifest * Refresh content list after delete instead of reload * Remove unused `id` parameter * Remove `dist/` directory and fix manifest --------- Co-authored-by: tdstein <[email protected]>
1 parent 46f2578 commit c61fe37

File tree

6 files changed

+160
-56
lines changed

6 files changed

+160
-56
lines changed

extensions/publisher-command-center/app.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ async def get_content_processes(
7474
active_jobs = [job for job in content.jobs if job["status"] == 0]
7575
return active_jobs
7676

77+
@app.delete("/api/contents/{content_id}")
78+
async def delete_content(
79+
content_id: str,
80+
posit_connect_user_session_token: str = Header(None),
81+
):
82+
visitor = get_visitor_client(posit_connect_user_session_token)
83+
84+
content = visitor.content.get(content_id)
85+
content.delete()
7786

7887
@app.delete("/api/contents/{content_id}/processes/{process_id}")
7988
async def destroy_process(
@@ -93,7 +102,6 @@ async def destroy_process(
93102
return
94103
await asyncio.sleep(1)
95104

96-
97105
@app.get("/api/contents/{content_id}/author")
98106
async def get_author(
99107
content_id,
Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,40 @@
11
{
2-
"version": 1,
3-
"metadata": {
4-
"appmode": "python-fastapi",
5-
"entrypoint": "app.py"
6-
},
7-
"python": {
8-
"version": "3.12.3",
9-
"package_manager": {
10-
"name": "pip",
11-
"package_file": "requirements.txt"
12-
}
13-
},
14-
"environment": {
15-
"python": {
16-
"requires": "~=3.8"
17-
}
18-
},
19-
"packages": {},
20-
"files": {
2+
"version": 1,
3+
"locale": "en_US.UTF-8",
4+
"metadata": {
5+
"appmode": "python-fastapi",
6+
"entrypoint": "app"
7+
},
8+
"python": {
9+
"version": "3.12.3",
10+
"package_manager": {
11+
"name": "pip",
12+
"package_file": "requirements.txt"
13+
}
14+
},
15+
"environment": {
16+
"python": {
17+
"requires": "~=3.8"
18+
}
19+
},
20+
"extension": {
21+
"name": "publisher-command-center",
22+
"title": "Publisher Command Center",
23+
"description": "An app that helps publishers view and manage details about apps and dashboards deployed to Connect: View app metadata and history for all of the apps you are collaborating on as well as see and managing running process for individual apps.",
24+
"homepage": "https://github.com/posit-dev/connect-extensions/tree/main/extensions/publisher-command-center",
25+
"minimumConnectVersion": "2025.04.0",
26+
"requiredFeatures": [
27+
"API Publishing",
28+
"OAuth Integrations"
29+
],
30+
"version": "0.0.4"
31+
},
32+
"files": {
2133
"requirements.txt": {
2234
"checksum": "a162a98758867a701ce693948ffdfa67"
2335
},
2436
"app.py": {
25-
"checksum": "92709c50369eca0216b8e121cd87c94c"
37+
"checksum": "55729c283443e02bcfc4233ccf0c2b3d"
2638
},
2739
"dist/assets/fa-brands-400.808443ae.ttf": {
2840
"checksum": "15d54d142da2f2d6f2e90ed1d55121af"
@@ -48,26 +60,14 @@
4860
"dist/assets/fa-v4compatibility.30f6abf6.ttf": {
4961
"checksum": "4ed293ceaca9b5b2d9cd74a477963fae"
5062
},
51-
"dist/assets/index.0c2ac2eb.css": {
52-
"checksum": "96fb5a8d93644a29ece62d52354fbc73"
63+
"dist/assets/index.eba02280.css": {
64+
"checksum": "cb5a34d5ea88d4969288161bd3123ee4"
5365
},
54-
"dist/assets/index.3b47d0e1.js": {
55-
"checksum": "b7a54352e0a5437ef97c8d3202983dac"
66+
"dist/assets/index.f987f996.js": {
67+
"checksum": "f8a777db2d3d8e211a31f70bbca57234"
5668
},
5769
"dist/index.html": {
58-
"checksum": "a64aa56406944bd2184562c18bfc3b4e"
70+
"checksum": "c61715903d067cc4ece8190fbdddb516"
5971
}
60-
},
61-
"extension": {
62-
"name": "publisher-command-center",
63-
"title": "Publisher Command Center",
64-
"description": "An app that helps publishers view and manage details about apps and dashboards deployed to Connect: View app metadata and history for all of the apps you are collaborating on as well as see and managing running process for individual apps.",
65-
"homepage": "https://github.com/posit-dev/connect-extensions/tree/main/extensions/publisher-command-center",
66-
"minimumConnectVersion": "2025.04.0",
67-
"requiredFeatures": [
68-
"API Publishing",
69-
"OAuth Integrations"
70-
],
71-
"version": "0.0.3"
72-
}
72+
}
7373
}

extensions/publisher-command-center/scss/index.scss

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,19 @@ $colors: (
2727
);
2828

2929
@import "bootstrap/scss/bootstrap";
30+
31+
.content-page-link {
32+
cursor: pointer;
33+
text-decoration: none;
34+
}
35+
36+
.action-btn {
37+
background: none;
38+
border: none;
39+
color: $blue;
40+
}
41+
42+
// Removes the white background of the modal
43+
.modal.show {
44+
background: none;
45+
}

extensions/publisher-command-center/src/components/ContentsComponent.js

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import m from "mithril";
22
import { format } from "date-fns";
33
import Contents from "../models/Contents";
44
import Languages from "./Languages";
5+
import DeleteModal from "./DeleteModal";
56

67
const ContentsComponent = {
78
error: null,
@@ -35,38 +36,30 @@ const ContentsComponent = {
3536
m(
3637
"thead",
3738
m("tr", [
38-
m("th", { scope: "col" }, ""),
3939
m("th", { scope: "col" }, "Title"),
4040
m("th", { scope: "col" }, "Language"),
4141
m("th", { scope: "col" }, "Running Processes"),
4242
m("th", { scope: "col" }, "Last Updated"),
4343
m("th", { scope: "col" }, "Date Added"),
4444
m("th", { scope: "col" }, ""),
45+
m("th", { scope: "col" }, ""),
4546
]),
4647
),
4748
m(
4849
"tbody",
4950
Contents.data.map((content) => {
5051
const guid = content["guid"];
52+
const title = content["title"];
5153
return m(
5254
"tr",
53-
{
54-
style: { cursor: "pointer" },
55-
onclick: () => m.route.set(`/contents/${guid}`),
56-
},
5755
[
5856
m(
5957
"td",
60-
m("", {
61-
class: "fa-solid fa-gear text-secondary",
62-
}),
63-
),
64-
m(
65-
"td",
66-
m(
67-
".link-primary",
68-
content["title"] || m("i", "No Name"),
69-
),
58+
{
59+
class: "link-primary content-page-link",
60+
onclick: () => m.route.set(`/contents/${guid}`),
61+
},
62+
title || m("i", "No Name"),
7063
),
7164
m(
7265
"td",
@@ -75,6 +68,16 @@ const ContentsComponent = {
7568
m("td", content?.active_jobs?.length),
7669
m("td", format(content["last_deployed_time"], "MMM do, yyyy")),
7770
m("td", format(content["created_time"], "MMM do, yyyy")),
71+
m(
72+
"td",
73+
m("button", {
74+
class: "action-btn",
75+
"data-bs-toggle": "modal",
76+
"data-bs-target": `#deleteModal-${guid}`,
77+
}, [
78+
m("i", { class: "fa-solid fa-trash" })
79+
]),
80+
),
7881
m(
7982
"td",
8083
m("a", {
@@ -84,11 +87,15 @@ const ContentsComponent = {
8487
onclick: (e) => e.stopPropagation(),
8588
}),
8689
),
90+
m(DeleteModal, {
91+
contentId: guid,
92+
contentTitle: title,
93+
}),
8794
],
8895
);
8996
}),
9097
),
91-
);
98+
)
9299
},
93100
};
94101

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import m from "mithril";
2+
import Contents from "../models/Contents";
3+
4+
const ConfirmDeleteButton = {
5+
view(vnode) {
6+
return m(
7+
"button",
8+
{
9+
class: "btn btn-primary",
10+
ariaLabel: "Yes",
11+
"data-bs-dismiss": "modal",
12+
onclick: () => {
13+
Contents.delete(vnode.attrs.contentId);
14+
},
15+
},
16+
"Yes",
17+
)
18+
},
19+
};
20+
21+
const CancelDeleteButton = {
22+
view(_vnode) {
23+
return m(
24+
"button",
25+
{
26+
class: "btn btn-secondary",
27+
ariaLabel: "No",
28+
"data-bs-dismiss": "modal",
29+
},
30+
"No",
31+
)
32+
},
33+
};
34+
35+
const DeleteModal = {
36+
view: function(vnode) {
37+
return m("div", { class: "modal", id: `deleteModal-${vnode.attrs.contentId}`, tabindex: "-1", ariaHidden: true }, [
38+
m("div", { class: "modal-dialog modal-dialog-centered" }, [
39+
m("div", { class: "modal-content" }, [
40+
m("div", { class: "modal-header"}, [
41+
m("h1", { class: "modal-title fs-6" }, "Delete Content"),
42+
m("button", {
43+
class: "btn-close",
44+
ariaLabel: "Close modal",
45+
"data-bs-dismiss": "modal"
46+
}),
47+
]),
48+
m("section", { class: "modal-body" }, [
49+
m("p", {
50+
id: "modal-message",
51+
class: "mb-3",
52+
}, `Are you sure you want to delete ${vnode.attrs.contentTitle}?`)
53+
]),
54+
m("div", { class: "modal-footer" }, [
55+
m(CancelDeleteButton),
56+
m(ConfirmDeleteButton, { contentId: vnode.attrs.contentId }),
57+
]),
58+
]),
59+
]),
60+
])
61+
},
62+
};
63+
64+
export default DeleteModal;

extensions/publisher-command-center/src/models/Contents.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export default {
44
data: null,
55
_fetch: null,
66

7-
load: function (id) {
7+
load: function () {
88
if (this.data) {
99
return Promise.resolve(this.data);
1010
}
@@ -25,6 +25,15 @@ export default {
2525
});
2626
},
2727

28+
delete: async function (guid) {
29+
await m.request({
30+
method: "DELETE",
31+
url: `api/contents/${guid}`,
32+
});
33+
34+
this.data = this.data.filter((c) => c.guid !== guid);
35+
},
36+
2837
reset: function () {
2938
this.data = null;
3039
this._fetch = null;

0 commit comments

Comments
 (0)