Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions commera/api/admin/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from frappe.query_builder.functions import Count
from frappe.utils.data import cint

from commera import storefront_plugins
from commera.api.admin.docfields import build_field_groups, get_editable_docfields, get_missing_fields
from commera.api.admin.integrations import write_settings
from commera.plugin_events import get_plugin_apps
Expand All @@ -33,6 +34,7 @@ def get_plugins() -> list[dict]:

registry = get_registry()
failed_deliveries = get_failed_delivery_counts()
storefront_switches = storefront_plugins.get_storefront_switches()
return [
{
"app": app,
Expand All @@ -46,11 +48,19 @@ def get_plugins() -> list[dict]:
],
"problems": [problem["message"] for problem in registry["problems"] if problem["app"] == app],
"failed_deliveries": failed_deliveries.get(app, 0),
# None for an app with no storefront hook; else whether its scripts and blocks load on store pages.
"storefront": storefront_switches.get(app),
}
for app in dict.fromkeys([*get_plugin_apps(), *registry["apps"]])
]


@frappe.whitelist(methods=["POST"])
def set_storefront_enabled(app: str, enabled: bool) -> None:
frappe.only_for("System Manager")
storefront_plugins.set_storefront_enabled(app, enabled)


@frappe.whitelist()
def get_plugin_deliveries(app: str, status: str | None = None, start: int = 0, page_length: int = 20) -> dict:
frappe.only_for("System Manager")
Expand Down
17 changes: 17 additions & 0 deletions commera/storefront_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os

import frappe
from frappe import _
from markupsafe import Markup

from commera.guest import is_guest
Expand Down Expand Up @@ -123,6 +124,22 @@ def sync_storefront_apps(app_name: str | None = None):
clear_storefront_plugin_cache()


def get_storefront_switches() -> dict[str, bool]:
"""Each app that declares a storefront hook, and whether the merchant has it switched on."""
settings = frappe.get_cached_doc(SETTINGS)
return {row.app: bool(row.enabled) for row in settings.storefront_apps}


def set_storefront_enabled(app: str, enabled: bool):
settings = frappe.get_single(SETTINGS)
row = next((row for row in settings.storefront_apps if row.app == app), None)
if not row:
frappe.throw(_("{0} adds nothing to the storefront.").format(app))
row.enabled = int(enabled)
settings.save()
clear_storefront_plugin_cache()


def clear_storefront_plugin_cache():
frappe.cache.delete_value(CACHE_KEY)

Expand Down
22 changes: 22 additions & 0 deletions commera/tests/test_storefront_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from frappe.utils import get_html_for_route, set_request

from commera import storefront_plugins
from commera.api.admin.plugins import set_storefront_enabled

BANNER = "commera/tests/storefront_plugin_templates/banner.html"
BROKEN = "commera/tests/storefront_plugin_templates/broken.html"
Expand Down Expand Up @@ -77,6 +78,27 @@ def test_switching_on_renders_tags_and_blocks(self):
currency = frappe.db.get_single_value("Global Defaults", "default_currency")
self.assertIn(f"{store_name}|{currency}|{frappe.local.lang}|False", banner)

def test_the_switch_turns_a_plugin_on_and_off_the_storefront(self):
self.declare_full_plugin()

set_storefront_enabled(app="commera", enabled=True)
self.assertEqual(storefront_plugins.get_storefront_switches()["commera"], True)
self.assertIn(SCRIPT, storefront_plugins.format_plugin_includes("js"))

set_storefront_enabled(app="commera", enabled=False)
self.assertEqual(storefront_plugins.format_plugin_includes("js"), "")

def test_the_switch_refuses_an_app_without_storefront_hooks(self):
with self.assertRaises(frappe.ValidationError):
set_storefront_enabled(app="frappe", enabled=True)

def test_only_a_system_manager_turns_the_switch(self):
self.declare_full_plugin()
frappe.set_user("Guest")
self.addCleanup(frappe.set_user, "Administrator")
with self.assertRaises(frappe.PermissionError):
set_storefront_enabled(app="commera", enabled=True)

def test_asset_outside_the_app_is_refused(self):
self.declare_hooks(
{
Expand Down
32 changes: 30 additions & 2 deletions dashboard/src/components/settings/PluginsSettings.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script setup>
import { computed, ref, watch } from 'vue'
import { Badge, Button, Icon, SettingsBody, Skeleton, TabButtons } from 'frappe-ui'
import { Badge, Button, Icon, SettingsBody, SettingsRow, Skeleton, Switch, TabButtons, toast } from 'frappe-ui'
import SettingsConfigHeader from './SettingsConfigHeader.vue'
import SettingsPanelHeader from './SettingsPanelHeader.vue'
import EmptyState from '../EmptyState.vue'
import ListPagination from '../ListPagination.vue'
import ResponsiveButton from '../ResponsiveButton.vue'
import StatusBadge from '../StatusBadge.vue'
import { useAdminRead } from '../../data/api'
import { useAdminAction, useAdminRead } from '../../data/api'
import { eventLabel, statusKey, timeLabel, useDeliveryRetry } from '../../data/pluginEvents'
import { appIcon, pluginSettingsTabs, placeLabel, settingsTabValue } from '../../ia/plugins'
import { settings } from '../../ia/settings'
Expand Down Expand Up @@ -73,6 +73,20 @@ const { retrying: retryingDelivery, retry: retryDeliveries } = useDeliveryRetry(
function retry(row) {
return retryDeliveries(row.delivery, row.app, [row.delivery])
}

const storefrontSave = useAdminAction('plugins.set_storefront_enabled')

// Saves on change like every settings switch; a refused save puts the switch back.
async function setStorefront(enabled) {
const plugin = current.value
plugin.storefront = enabled
await storefrontSave.submit({ app: plugin.app, enabled })
if (storefrontSave.error) {
plugin.storefront = !enabled
return
}
toast.success(enabled ? `${plugin.title} now runs on your storefront` : `${plugin.title} is off your storefront`)
}
</script>

<template>
Expand Down Expand Up @@ -146,6 +160,20 @@ function retry(row) {
</SettingsConfigHeader>

<SettingsBody v-scroll-fade>
<SettingsRow
v-if="current.storefront !== null"
class="mt-2 border-b border-outline-gray-1"
title="Run on the storefront"
description="This plugin adds scripts or blocks to your store pages, such as a popup or a banner. They load only while this is on."
>
<Switch
size="sm"
:model-value="current.storefront"
:disabled="storefrontSave.loading"
@update:model-value="setStorefront"
/>
</SettingsRow>

<section class="mt-2">
<h3 class="text-base font-medium text-ink-gray-8">What it adds</h3>
<ul
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading