From a6c6c99e11f74a6d4d4629295035f28c11b4b722 Mon Sep 17 00:00:00 2001 From: James Maes Date: Fri, 16 Oct 2026 09:12:51 -0500 Subject: [PATCH] feat(home): business-only Page Speed home; provider setup and run queue become admin widgets Replace the provider Setup card on the home with a business "not set up" empty state (no config key names, empty once configured). Setup and Recent Runs leave the home and become WebPerfWidgets.ADMIN, gated to .admin.hasAccess with DenyBehavior.HIDDEN, for a host Admin area. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 3 + UPGRADE.md | 4 ++ .../webperf/metadata/WebPerfMetaData.java | 21 +++++- .../qbits/webperf/widgets/WebPerfWidgets.java | 38 ++++++++++- .../resources/help/qbit-web-performance.yaml | 6 +- .../qbits/webperf/WebPerfP1Test.java | 65 +++++++++++++++++++ 6 files changed, 131 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1490aaf..a04a268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project are documented here. The format follows Keep ## [0.1.0] - Unreleased +### Changed +- The Performance home is business-only. The provider Setup readout and Recent Runs left the home's widget list (`WebPerfWidgets.HOME`); they are `WebPerfWidgets.ADMIN` (`Setup`, `RecentRuns`), gated to `.admin.hasAccess` and hidden when denied, for a host to place on its Admin area. A new `NotConfigured` widget leads the home: while no Google API key is set it says "Page speed testing is not set up. Ask an admin to add a Google PageSpeed API key." (no config key names), and renders nothing once configured. + ### Fixed - Read the QBit metadata version from the build so release artifacts report their actual version, matching the help bundle. - Use dropdowns for tier, scope, source, strategy, metric, comparator and severity, preserving existing stored values. diff --git a/UPGRADE.md b/UPGRADE.md index 92e39f8..f7d24ea 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,5 +1,9 @@ # Upgrading +## Business-only home; admin widgets (unreleased) + +No DDL change. The Setup and Recent Runs widgets are no longer on the Performance home. They are still registered (`webPerformanceSetup`, `webPerformanceRecentRuns`), gated to `web_performance.admin.hasAccess` and hidden without it; add `WebPerfWidgets.ADMIN` (qualified with `config.qualify`) to your Admin app's widgets to keep them. The home gains a `webPerformanceNotConfigured` widget that shows only while no Google API key is set. + ## Table help, Setup card and `reportRetentionDays` (unreleased) No DDL change, no new changeset and no host include change. The qbit now fills empty `help` slots on its app and tables (help a host set is kept) and adds a Setup widget to the Performance home. `reportRetentionDays` (default 0, keep forever) is now enforced: set it above 0 only if you want Lighthouse Reports older than that many days deleted. diff --git a/src/main/java/com/kingsrook/qbits/webperf/metadata/WebPerfMetaData.java b/src/main/java/com/kingsrook/qbits/webperf/metadata/WebPerfMetaData.java index 836c663..ebaa660 100644 --- a/src/main/java/com/kingsrook/qbits/webperf/metadata/WebPerfMetaData.java +++ b/src/main/java/com/kingsrook/qbits/webperf/metadata/WebPerfMetaData.java @@ -50,6 +50,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection; import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon; +import com.kingsrook.qqq.backend.core.model.metadata.permissions.DenyBehavior; import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel; import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue; @@ -129,13 +130,15 @@ public final class WebPerfMetaData { qInstance.addProcess(process); } - addWidget(qInstance, WebPerfWidgets.SETUP, "Setup", WidgetType.HTML, WebPerfWidgets.Setup.class); + addWidget(qInstance, WebPerfWidgets.NOT_CONFIGURED, "Page Speed Setup", WidgetType.HTML, WebPerfWidgets.NotConfigured.class); + qInstance.getWidget(config.qualify(WebPerfWidgets.NOT_CONFIGURED)).setIsCard(false); + addAdminWidget(qInstance, WebPerfWidgets.SETUP, "Page Speed Setup (Admin)", WebPerfWidgets.Setup.class); addWidget(qInstance, WebPerfWidgets.SITE_SCORES, "Site Scores", WidgetType.HTML, WebPerfWidgets.SiteScores.class); addWidget(qInstance, WebPerfWidgets.TREND, "Performance Trend", WidgetType.LINE_CHART, WebPerfWidgets.Trend.class); addWidget(qInstance, WebPerfWidgets.SLOWEST_PAGES, "Slowest Pages", WidgetType.HTML, WebPerfWidgets.SlowestPages.class); addWidget(qInstance, WebPerfWidgets.OPPORTUNITIES, "Top Opportunities", WidgetType.HTML, WebPerfWidgets.TopOpportunities.class); addWidget(qInstance, WebPerfWidgets.FIELD_STATUS, "Google Field Data", WidgetType.HTML, WebPerfWidgets.FieldDataStatus.class); - addWidget(qInstance, WebPerfWidgets.RECENT_RUNS, "Recent Runs", WidgetType.HTML, WebPerfWidgets.RecentRuns.class); + addAdminWidget(qInstance, WebPerfWidgets.RECENT_RUNS, "Recent Runs", WebPerfWidgets.RecentRuns.class); addWidget(qInstance, WebPerfWidgets.RECORD_PANEL, "Performance", WidgetType.HTML, WebPerfWidgets.RecordPanel.class); List panelTables = new ArrayList<>(config.getContentTables()); @@ -258,6 +261,20 @@ public final class WebPerfMetaData + /******************************************************************************* + ** A system widget for a host's Admin area (WebPerfWidgets.ADMIN): gated to + ** ".admin.hasAccess" and HIDDEN, never a locked shell, when denied. + *******************************************************************************/ + private void addAdminWidget(QInstance qInstance, String name, String label, Class renderer) + { + addWidget(qInstance, name, label, WidgetType.HTML, renderer); + qInstance.getWidget(config.qualify(name)).setPermissionRules(new QPermissionRules().withLevel(PermissionLevel.HAS_ACCESS_PERMISSION) + .withPermissionBaseName(config.getPermissionBaseName() + ".admin") + .withDenyBehavior(DenyBehavior.HIDDEN)); + } + + + /******************************************************************************* ** The Performance app: its one home (the widgets) and the data tables. ** No process is in its nav. diff --git a/src/main/java/com/kingsrook/qbits/webperf/widgets/WebPerfWidgets.java b/src/main/java/com/kingsrook/qbits/webperf/widgets/WebPerfWidgets.java index e648096..e01a3c4 100644 --- a/src/main/java/com/kingsrook/qbits/webperf/widgets/WebPerfWidgets.java +++ b/src/main/java/com/kingsrook/qbits/webperf/widgets/WebPerfWidgets.java @@ -59,6 +59,7 @@ import com.kingsrook.qqq.backend.core.model.dashboard.widgets.RawHTML; public final class WebPerfWidgets { public static final String SETUP = "Setup"; + public static final String NOT_CONFIGURED = "NotConfigured"; public static final String SITE_SCORES = "SiteScores"; public static final String TREND = "Trend"; public static final String SLOWEST_PAGES = "SlowestPages"; @@ -67,8 +68,18 @@ public final class WebPerfWidgets public static final String FIELD_STATUS = "FieldDataStatus"; public static final String RECORD_PANEL = "RecordPanel"; - /** The home widgets, in order. */ - public static final List HOME = List.of(SETUP, SITE_SCORES, TREND, SLOWEST_PAGES, OPPORTUNITIES, FIELD_STATUS, RECENT_RUNS); + /** The business home's widgets, in order. No provider or run-queue detail (standard A-0). */ + public static final List HOME = List.of(NOT_CONFIGURED, SITE_SCORES, TREND, SLOWEST_PAGES, OPPORTUNITIES, FIELD_STATUS); + + /** + * The system widgets (provider setup readout, run queue). Not on the home: a host places them on its + * Admin area. Each is gated to "<base>.admin.hasAccess" and hidden (not shown locked) without it. + */ + public static final List ADMIN = List.of(SETUP, RECENT_RUNS); + + /** The business empty state shown on the home while no Google API key is set. */ + public static final String NOT_CONFIGURED_TITLE = "Page speed testing is not set up"; + public static final String NOT_CONFIGURED_BODY = "Ask an admin to add a Google PageSpeed API key."; static final int TREND_WEEKS = 26; @@ -416,6 +427,29 @@ public final class WebPerfWidgets + /******************************************************************************* + ** Not Configured: the home's business empty state. Shown only while lab tests + ** cannot run because no Google API key is set; says who can fix it and names + ** no configuration key. Renders nothing once the provider is configured. + *******************************************************************************/ + public static class NotConfigured extends AbstractWidgetRenderer + { + /******************************************************************************* + ** Render. + *******************************************************************************/ + @Override + public RenderWidgetOutput render(RenderWidgetInput input) throws QException + { + if(runtime(input).labRunner() != null) + { + return (html("")); + } + return (html("
" + Html.escape(NOT_CONFIGURED_TITLE) + "
" + Html.muted(NOT_CONFIGURED_BODY))); + } + } + + + /******************************************************************************* ** Setup: what is configured (pages, lab runner, budgets, field data) and ** the next step for each (WebPerfSetupStatus). diff --git a/src/main/resources/help/qbit-web-performance.yaml b/src/main/resources/help/qbit-web-performance.yaml index beead63..d6cf5a0 100644 --- a/src/main/resources/help/qbit-web-performance.yaml +++ b/src/main/resources/help/qbit-web-performance.yaml @@ -5,7 +5,7 @@ entries: - key: app:webPerformance;slot:help summary: Page speed tests, Google field data, budgets and regressions for your site. content: | - Use the Setup card to see what is configured and what to do next. Sync your pages, configure a lab runner and review your budgets. The dashboard shows site scores, trends, slow pages, opportunities, field data and recent runs. + Sync your pages and review your budgets, then run tests. The dashboard shows site scores, trends, slow pages, opportunities and field data. If page speed testing is not set up, it says so and tells you to ask an admin. Run **{{label:webPerformanceTestPageSpeed}}** for selected pages or **{{label:webPerformanceRunSiteAudit}}** for all enabled pages. Mobile and desktop are separate runs by default. Your host controls the enabled features, device strategies, daily run limit and scheduler. Without a configured runner, lab tests wait in the queue. @@ -623,7 +623,9 @@ entries: content: | The scheduled check records observations in **{{label:web_performance_change_event}}**. Repeated observations update the existing event instead of creating duplicates. - key: widget:webPerformanceSetup;slot:label - summary: Check your tested pages, lab runner, budgets and field-data setup, with the next step for each. + summary: Admin only. Check your tested pages, lab runner, budgets and field-data setup, with the next step for each. +- key: widget:webPerformanceNotConfigured;slot:label + summary: Says when page speed testing is not set up and who can fix it. - key: possibleValueSource:web_performance_target;slot:help summary: Choose a tested page. - key: possibleValueSource:web_performance_lab_run;slot:help diff --git a/src/test/java/com/kingsrook/qbits/webperf/WebPerfP1Test.java b/src/test/java/com/kingsrook/qbits/webperf/WebPerfP1Test.java index fd12ab9..66315e2 100644 --- a/src/test/java/com/kingsrook/qbits/webperf/WebPerfP1Test.java +++ b/src/test/java/com/kingsrook/qbits/webperf/WebPerfP1Test.java @@ -86,6 +86,71 @@ class WebPerfP1Test + /******************************************************************************* + ** The business home has no provider setup readout and no run-queue widget; + ** those are the admin-placeable list. + *******************************************************************************/ + @Test + void testHomeIsBusinessOnly() + { + List home = qInstance.getApp(config.getAppName()).getWidgets(); + assertThat(home).doesNotContain(config.qualify(WebPerfWidgets.SETUP), config.qualify(WebPerfWidgets.RECENT_RUNS)); + assertThat(WebPerfWidgets.HOME).doesNotContain(WebPerfWidgets.SETUP, WebPerfWidgets.RECENT_RUNS); + assertThat(WebPerfWidgets.ADMIN).containsExactly(WebPerfWidgets.SETUP, WebPerfWidgets.RECENT_RUNS); + assertThat(home).contains(config.qualify(WebPerfWidgets.SITE_SCORES), config.qualify(WebPerfWidgets.TREND), config.qualify(WebPerfWidgets.SLOWEST_PAGES), + config.qualify(WebPerfWidgets.OPPORTUNITIES), config.qualify(WebPerfWidgets.FIELD_STATUS)); + for(String name : WebPerfWidgets.ADMIN) + { + assertThat(qInstance.getWidget(config.qualify(name))).as(name + " is still defined for a host to place").isNotNull(); + } + } + + + + /******************************************************************************* + ** Unconfigured: a business empty state naming no config key. Configured: + ** nothing at all. + *******************************************************************************/ + @Test + void testNotConfiguredEmptyState() throws Exception + { + assertThat(html(WebPerfWidgets.NOT_CONFIGURED, Map.of())).as("configured shows nothing").isEmpty(); + + config.withLabRunner(null).withCruxClient(null); + String card = html(WebPerfWidgets.NOT_CONFIGURED, Map.of()); + assertThat(card).contains("Page speed testing is not set up").contains("Ask an admin to add a Google PageSpeed API key."); + assertThat(card).doesNotContain("googleApiKey").doesNotContain("qbit config").doesNotContain("environment variable"); + } + + + + /******************************************************************************* + ** The admin widgets render for an admin, and are hidden (permission denied + ** under enforcement) for a session without the admin permission. + *******************************************************************************/ + @Test + void testAdminWidgetsAdminOnly() throws Exception + { + for(String name : WebPerfWidgets.ADMIN) + { + assertThat(html(name, Map.of())).as(name + " for an admin").isNotEmpty(); + } + + WebPerfTestSupport.login(qInstance, "web_performance.read", "web_performance.write", "web_performance.view.hasAccess"); + for(String name : WebPerfWidgets.ADMIN) + { + assertThat(qInstance.getWidget(config.qualify(name)).getPermissionRules().getDenyBehavior()).isEqualTo(com.kingsrook.qqq.backend.core.model.metadata.permissions.DenyBehavior.HIDDEN); + RenderWidgetInput input = new RenderWidgetInput().withWidgetMetaData(qInstance.getWidget(config.qualify(name))); + input.setPermissionEnforcement(com.kingsrook.qqq.backend.core.actions.permissions.PermissionEnforcement.ENFORCE); + org.junit.jupiter.api.Assertions.assertThrows(com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException.class, + () -> new RenderWidgetAction().execute(input), name + " must not render without admin"); + assertThat(com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper.hasWidgetPermission(input, config.qualify(name))).isFalse(); + } + assertThat(html(WebPerfWidgets.SITE_SCORES, Map.of())).as("business widgets still render").isNotEmpty(); + } + + + /******************************************************************************* ** An instance with a content table "page", the QBit wired to it, and the ** local Google stand-in. -- 2.54.0