From f88543546307444495b613c8b6b4d3e845eeb50c Mon Sep 17 00:00:00 2001 From: Vibhav Katre Date: Mon, 20 Jul 2026 15:31:29 +0530 Subject: [PATCH 1/2] feat: "Add to Drive" action in the editor (wire up the Drive integration) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Drive integration (register a diagram as a Drive link file) existed as backend plumbing but had no caller — nothing in the UI triggered it, so it couldn't be used or tested from the app. This wires it up: - api/drive_integration.py: new whitelisted is_available() -> {installed, ready} so the editor can show the action only when Drive is installed AND set up (a team exists). - toolbar/DriveMenu.vue: an "Add to Drive" button that appears only when ready; clicking registers the diagram (idempotent) and toasts success with an "Open Drive" action. Hidden entirely when Drive is absent. - toolbar/TopToolbar.vue: place it between Export and Share. - test: is_available() returns the status booleans without raising, Drive or not. Verified in the running app (Drive installed): button appears, click registers a Drive Link file at /draw/d/ ("Drawing 15" -> /draw/d/drawing-15). Server tests: 10 pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- draw/api/drive_integration.py | 7 +++ .../doctype/draw_diagram/test_draw_diagram.py | 11 ++++ frontend/src/components/toolbar/DriveMenu.vue | 52 +++++++++++++++++++ .../src/components/toolbar/TopToolbar.vue | 2 + 4 files changed, 72 insertions(+) create mode 100644 frontend/src/components/toolbar/DriveMenu.vue diff --git a/draw/api/drive_integration.py b/draw/api/drive_integration.py index df2a585..9d2ed41 100644 --- a/draw/api/drive_integration.py +++ b/draw/api/drive_integration.py @@ -58,6 +58,13 @@ def register_in_drive(diagram_name: str, team: str | None = None) -> str | None: return drive_file.name +@frappe.whitelist() +def is_available() -> dict: + """Whether the Drive integration can be used (Drive installed + a team exists). + The editor calls this once to decide whether to show the "Add to Drive" action.""" + return {"installed": drive_installed(), "ready": bool(drive_installed() and _default_team())} + + @frappe.whitelist() def add_to_drive(name: str) -> dict: """Whitelisted entry point for an "Add to Drive" action. Returns whether Drive diff --git a/draw/draw/doctype/draw_diagram/test_draw_diagram.py b/draw/draw/doctype/draw_diagram/test_draw_diagram.py index 0980978..8974456 100644 --- a/draw/draw/doctype/draw_diagram/test_draw_diagram.py +++ b/draw/draw/doctype/draw_diagram/test_draw_diagram.py @@ -136,6 +136,17 @@ def test_register_in_drive_when_available(self): # Idempotent — a second call reuses the same File. self.assertEqual(register_in_drive(doc.name, team=team), file_name) + def test_drive_is_available_reports_status(self): + # The editor calls is_available() to decide whether to show "Add to Drive". + # It must always return the two booleans without raising, Drive or not. + from draw.api.drive_integration import drive_installed, is_available + + status = is_available() + self.assertEqual(status["installed"], drive_installed()) + self.assertIn("ready", status) + if not drive_installed(): + self.assertFalse(status["ready"]) + def test_drive_registration_noops_without_team(self): # Registration is a safe no-op when Drive isn't set up / not installed. from draw.api import drive_integration diff --git a/frontend/src/components/toolbar/DriveMenu.vue b/frontend/src/components/toolbar/DriveMenu.vue new file mode 100644 index 0000000..42a5884 --- /dev/null +++ b/frontend/src/components/toolbar/DriveMenu.vue @@ -0,0 +1,52 @@ + + + diff --git a/frontend/src/components/toolbar/TopToolbar.vue b/frontend/src/components/toolbar/TopToolbar.vue index 9b92419..2984b46 100644 --- a/frontend/src/components/toolbar/TopToolbar.vue +++ b/frontend/src/components/toolbar/TopToolbar.vue @@ -12,6 +12,7 @@ import TitleEditor from './TitleEditor.vue' import SaveIndicator from './SaveIndicator.vue' import ExportMenu from './ExportMenu.vue' import ShareMenu from './ShareMenu.vue' +import DriveMenu from './DriveMenu.vue' import PresenceAvatars from './PresenceAvatars.vue' const props = defineProps({ @@ -76,6 +77,7 @@ function print() {
+ From d040619627c6be4b8d6f28f6b2db3e997080fae1 Mon Sep 17 00:00:00 2001 From: Vibhav Katre Date: Mon, 20 Jul 2026 15:35:39 +0530 Subject: [PATCH 2/2] Address review: cache drive_installed() in is_available() Avoid calling drive_installed() twice; use a local. (per Greptile P2) Co-Authored-By: Claude Opus 4.8 (1M context) --- draw/api/drive_integration.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/draw/api/drive_integration.py b/draw/api/drive_integration.py index 9d2ed41..da1f35b 100644 --- a/draw/api/drive_integration.py +++ b/draw/api/drive_integration.py @@ -62,7 +62,8 @@ def register_in_drive(diagram_name: str, team: str | None = None) -> str | None: def is_available() -> dict: """Whether the Drive integration can be used (Drive installed + a team exists). The editor calls this once to decide whether to show the "Add to Drive" action.""" - return {"installed": drive_installed(), "ready": bool(drive_installed() and _default_team())} + installed = drive_installed() + return {"installed": installed, "ready": bool(installed and _default_team())} @frappe.whitelist()