From b3ec71eb61cd7356e9b7436bbc5639a7537ef6c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 03:54:27 +0000 Subject: [PATCH 1/4] Add failed tool update alert on My Tools page in dashboard Co-authored-by: Power-Maverick <36135520+Power-Maverick@users.noreply.github.com> Agent-Logs-Url: https://github.com/PowerPlatformToolBox/pptb-web/sessions/a322546a-2407-447c-bfa0-cf9af3c19780 --- app/(authenticated)/dashboard/page.tsx | 63 +++++++++++++++++++++++++- app/api/dashboard/route.ts | 19 ++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/app/(authenticated)/dashboard/page.tsx b/app/(authenticated)/dashboard/page.tsx index d443349..16f7532 100644 --- a/app/(authenticated)/dashboard/page.tsx +++ b/app/(authenticated)/dashboard/page.tsx @@ -24,6 +24,7 @@ interface Tool { name: string; description: string; icon: string; + packagename?: string; user_id?: string; status?: string; tool_categories?: Array<{ @@ -40,9 +41,17 @@ interface Tool { }; } +interface FailedToolUpdate { + id: string; + package_name: string; + version: string; + validation_warnings: string[] | null; +} + export default function DashboardPage() { const [user, setUser] = useState(null); const [tools, setTools] = useState([]); + const [failedToolUpdates, setFailedToolUpdates] = useState([]); const [loading, setLoading] = useState(true); const [sortBy, setSortBy] = useState<"downloads" | "rating" | "mau">("downloads"); const [viewMode, setViewMode] = useState<"all" | "my">("all"); @@ -69,12 +78,13 @@ export default function DashboardPage() { if (!response.ok) throw new Error("Failed to fetch dashboard data"); - const { user: dashUser, isAdmin: admin, tools: dashTools } = await response.json(); + const { user: dashUser, isAdmin: admin, tools: dashTools, failedToolUpdates: failedUpdates } = await response.json(); if (dashUser) { setUser(dashUser); setIsAdmin(admin); setTools(dashTools); + setFailedToolUpdates(failedUpdates || []); } } catch (error) { console.error("Error fetching dashboard data:", error); @@ -295,6 +305,42 @@ export default function DashboardPage() { + {/* Failed Tool Updates Alert - shown only in My Tools view */} + {viewMode === "my" && failedToolUpdates.length > 0 && ( + +
+
+
+ + + +
+
+

+ {failedToolUpdates.length === 1 + ? "1 tool update failed validation" + : `${failedToolUpdates.length} tool updates failed validation`} +

+

+ The following tool{failedToolUpdates.length > 1 ? "s have" : " has"} a pending update that failed validation. Please review and fix the issues to publish the update. +

+
    + {failedToolUpdates.map((update) => ( +
  • + {update.package_name} + {update.version && v{update.version}} + {update.validation_warnings && update.validation_warnings.length > 0 && ( + ({update.validation_warnings[0]}{update.validation_warnings.length > 1 ? ` +${update.validation_warnings.length - 1} more` : ""}) + )} +
  • + ))} +
+
+
+
+
+ )} + {/* Tools Table */} {sortedTools.length === 0 ? ( @@ -336,6 +382,9 @@ export default function DashboardPage() { {sortedTools.map((tool) => { const analytics = tool.tool_analytics; + const toolFailedUpdate = viewMode === "my" + ? failedToolUpdates.find((u) => u.package_name === tool.packagename) + : undefined; return ( @@ -348,7 +397,17 @@ export default function DashboardPage() { className="rounded" />
-
{tool.name}
+
+ {tool.name} + {toolFailedUpdate && ( + + Update failed + + )} +
{tool.description}
diff --git a/app/api/dashboard/route.ts b/app/api/dashboard/route.ts index 19aa7c7..73001dd 100644 --- a/app/api/dashboard/route.ts +++ b/app/api/dashboard/route.ts @@ -56,6 +56,7 @@ export async function GET(request: NextRequest) { name, description, icon, + packagename, user_id, status, tool_analytics (downloads, rating, mau), @@ -79,10 +80,28 @@ export async function GET(request: NextRequest) { categories: tool.tool_categories?.map((tc: any) => tc.categories).filter(Boolean) || [], })) || []; + // Fetch failed tool updates for the authenticated user's tools + let failedToolUpdates: Array<{ id: string; package_name: string; version: string; validation_warnings: string[] | null }> = []; + if (user) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const userToolPackageNames = (toolsData || []).filter((t: any) => t.user_id === user.id).map((t: any) => t.packagename).filter(Boolean); + + if (userToolPackageNames.length > 0) { + const { data: failedUpdates } = await supabase + .from("tool_updates") + .select("id, package_name, version, validation_warnings") + .in("package_name", userToolPackageNames) + .eq("status", "validation_failed"); + + failedToolUpdates = failedUpdates || []; + } + } + return NextResponse.json({ user, isAdmin, tools: transformedTools, + failedToolUpdates, }); } catch (error) { console.error("Error fetching dashboard data:", error); From 0134ac7072ec29baf9bc6fd8835154d57324ab9a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 04:03:02 +0000 Subject: [PATCH 2/4] Filter failed tool updates by version >= tool's current version Co-authored-by: Power-Maverick <36135520+Power-Maverick@users.noreply.github.com> Agent-Logs-Url: https://github.com/PowerPlatformToolBox/pptb-web/sessions/1c1aea27-e369-4886-bfdd-6301990932d5 --- app/api/dashboard/route.ts | 45 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/app/api/dashboard/route.ts b/app/api/dashboard/route.ts index 73001dd..2d1d4cf 100644 --- a/app/api/dashboard/route.ts +++ b/app/api/dashboard/route.ts @@ -13,6 +13,36 @@ function getSupabaseClient() { return createClient(supabaseUrl, supabaseServiceKey); } +/** + * Compare two semver strings. Returns: + * > 0 if a > b + * 0 if a === b + * < 0 if a < b + * Non-numeric pre-release segments are compared lexicographically. + */ +function compareSemver(a: string, b: string): number { + const parse = (v: string) => + v + .replace(/^v/, "") + .split(".") + .map((p) => { + const n = parseInt(p, 10); + return isNaN(n) ? p : n; + }); + + const aParts = parse(a); + const bParts = parse(b); + const len = Math.max(aParts.length, bParts.length); + + for (let i = 0; i < len; i++) { + const ap = aParts[i] ?? 0; + const bp = bParts[i] ?? 0; + if (ap < bp) return -1; + if (ap > bp) return 1; + } + return 0; +} + export async function GET(request: NextRequest) { try { const supabase = getSupabaseClient(); @@ -57,6 +87,7 @@ export async function GET(request: NextRequest) { description, icon, packagename, + version, user_id, status, tool_analytics (downloads, rating, mau), @@ -84,7 +115,9 @@ export async function GET(request: NextRequest) { let failedToolUpdates: Array<{ id: string; package_name: string; version: string; validation_warnings: string[] | null }> = []; if (user) { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const userToolPackageNames = (toolsData || []).filter((t: any) => t.user_id === user.id).map((t: any) => t.packagename).filter(Boolean); + const userTools = (toolsData || []).filter((t: any) => t.user_id === user.id && t.packagename); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const userToolPackageNames = userTools.map((t: any) => t.packagename); if (userToolPackageNames.length > 0) { const { data: failedUpdates } = await supabase @@ -93,7 +126,15 @@ export async function GET(request: NextRequest) { .in("package_name", userToolPackageNames) .eq("status", "validation_failed"); - failedToolUpdates = failedUpdates || []; + if (failedUpdates) { + // Only surface failed updates whose version is >= the tool's current published version + failedToolUpdates = failedUpdates.filter((update) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const tool = userTools.find((t: any) => t.packagename === update.package_name); + if (!tool || !tool.version || !update.version) return true; + return compareSemver(update.version, tool.version) >= 0; + }); + } } } From aeb72f4749a274bd3579bc39aa0cce2b1a45ac1e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:19:12 +0000 Subject: [PATCH 3/4] Use useMemo Set for O(1) failed update lookups in dashboard row render Co-authored-by: Power-Maverick <36135520+Power-Maverick@users.noreply.github.com> Agent-Logs-Url: https://github.com/PowerPlatformToolBox/pptb-web/sessions/f0f92a86-1792-4fbd-a7a8-d72f7874a1d9 --- app/(authenticated)/dashboard/page.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/(authenticated)/dashboard/page.tsx b/app/(authenticated)/dashboard/page.tsx index 16f7532..7549ab6 100644 --- a/app/(authenticated)/dashboard/page.tsx +++ b/app/(authenticated)/dashboard/page.tsx @@ -2,7 +2,7 @@ import Image from "next/image"; import Link from "next/link"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { Container } from "@/components/Container"; import { FadeIn, SlideIn } from "@/components/animations"; @@ -153,6 +153,12 @@ export default function DashboardPage() { } }); + // Precompute a Set of package names with failed updates for O(1) row lookups + const failedUpdatePackageNames = useMemo( + () => new Set(failedToolUpdates.map((u) => u.package_name)), + [failedToolUpdates], + ); + if (loading) { return (
@@ -382,9 +388,7 @@ export default function DashboardPage() { {sortedTools.map((tool) => { const analytics = tool.tool_analytics; - const toolFailedUpdate = viewMode === "my" - ? failedToolUpdates.find((u) => u.package_name === tool.packagename) - : undefined; + const toolHasFailedUpdate = viewMode === "my" && !!tool.packagename && failedUpdatePackageNames.has(tool.packagename); return ( @@ -399,7 +403,7 @@ export default function DashboardPage() {
{tool.name} - {toolFailedUpdate && ( + {toolHasFailedUpdate && ( Date: Wed, 25 Mar 2026 09:38:16 -0700 Subject: [PATCH 4/4] fix: remove duplicate packagename field and optimize failed update alert rendering --- app/(authenticated)/dashboard/page.tsx | 95 +++++++++++++++++++------- next-env.d.ts | 2 +- 2 files changed, 72 insertions(+), 25 deletions(-) diff --git a/app/(authenticated)/dashboard/page.tsx b/app/(authenticated)/dashboard/page.tsx index 3503c78..3bef4a5 100644 --- a/app/(authenticated)/dashboard/page.tsx +++ b/app/(authenticated)/dashboard/page.tsx @@ -27,7 +27,6 @@ interface Tool { packagename?: string; user_id?: string; status?: string; - packagename?: string; version?: string; tool_categories?: Array<{ categories: { @@ -217,10 +216,7 @@ export default function DashboardPage() { }); // Precompute a Set of package names with failed updates for O(1) row lookups - const failedUpdatePackageNames = useMemo( - () => new Set(failedToolUpdates.map((u) => u.package_name)), - [failedToolUpdates], - ); + const failedUpdatePackageNames = useMemo(() => new Set(failedToolUpdates.map((u) => u.package_name)), [failedToolUpdates]); if (loading) { return ( @@ -381,17 +377,21 @@ export default function DashboardPage() {
- +

- {failedToolUpdates.length === 1 - ? "1 tool update failed validation" - : `${failedToolUpdates.length} tool updates failed validation`} + {failedToolUpdates.length === 1 ? "1 tool update failed validation" : `${failedToolUpdates.length} tool updates failed validation`}

- The following tool{failedToolUpdates.length > 1 ? "s have" : " has"} a pending update that failed validation. Please review and fix the issues to publish the update. + The following tool{failedToolUpdates.length > 1 ? "s have" : " has"} a pending update that failed validation. Please review and fix the issues to + publish the update.

    {failedToolUpdates.map((update) => ( @@ -399,7 +399,10 @@ export default function DashboardPage() { {update.package_name} {update.version && v{update.version}} {update.validation_warnings && update.validation_warnings.length > 0 && ( - ({update.validation_warnings[0]}{update.validation_warnings.length > 1 ? ` +${update.validation_warnings.length - 1} more` : ""}) + + ({update.validation_warnings[0]} + {update.validation_warnings.length > 1 ? ` +${update.validation_warnings.length - 1} more` : ""}) + )} ))} @@ -553,19 +556,36 @@ export default function DashboardPage() { <>
    { setOpenMoreMenuForToolId(null); moreMenuAnchorRef.current = null; }} - onKeyDown={(e) => { if (e.key === "Escape") { setOpenMoreMenuForToolId(null); moreMenuAnchorRef.current = null; } }} + onClick={() => { + setOpenMoreMenuForToolId(null); + moreMenuAnchorRef.current = null; + }} + onKeyDown={(e) => { + if (e.key === "Escape") { + setOpenMoreMenuForToolId(null); + moreMenuAnchorRef.current = null; + } + }} />
    { if (e.key === "Escape") { setOpenMoreMenuForToolId(null); moreMenuAnchorRef.current = null; } }} + style={ + moreMenuAnchorRef.current + ? { + top: moreMenuAnchorRef.current.bottom + 4, + left: moreMenuAnchorRef.current.right - 192, + } + : undefined + } + onKeyDown={(e) => { + if (e.key === "Escape") { + setOpenMoreMenuForToolId(null); + moreMenuAnchorRef.current = null; + } + }} > @@ -593,7 +618,12 @@ export default function DashboardPage() { className="flex w-full items-center gap-2 px-4 py-2 text-sm text-amber-700 hover:bg-amber-50 disabled:cursor-not-allowed disabled:text-slate-400" > - + {tool.status === TOOL_STATUSES.DEPRECATED ? "Deprecated" : "Deprecate"} @@ -609,7 +639,12 @@ export default function DashboardPage() { className="flex w-full items-center gap-2 px-4 py-2 text-sm text-red-600 hover:bg-red-50 disabled:cursor-not-allowed disabled:text-slate-400" > - + {tool.status === TOOL_STATUSES.DELETED ? "Deleted" : "Delete"} @@ -661,11 +696,18 @@ export default function DashboardPage() {
    - +
    -

    Package Validation Failed

    +

    + Package Validation Failed +

    {validationModal.packageName}

    @@ -700,7 +742,12 @@ export default function DashboardPage() { {validationModal.warnings.map((warn, i) => (
  • - + {warn}
  • diff --git a/next-env.d.ts b/next-env.d.ts index c4b7818..9edff1c 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.