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 apps/api/src/app/api/auth/[...all]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ const GET = async (req: Request) => {
}
}
}
if (url.pathname.endsWith("/jwks")) {
const response = await _GET(req);
if (!response.ok) return response;
const headers = new Headers(response.headers);
headers.set(
"Cache-Control",
"public, s-maxage=300, stale-while-revalidate=3600",
);
return new Response(response.body, { status: response.status, headers });
}
return _GET(req);
};

Expand Down
6 changes: 4 additions & 2 deletions apps/api/src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ export default function proxy(req: NextRequest) {

export const config = {
matcher: [
"/((?!_next|ingest|monitoring|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
"/(api|trpc)(.*)",
"/api/trpc/(.*)",
"/api/auth/((?!jwks$|oauth2/token$).*)",
"/api/desktop/version",
"/api/proxy/linear-image",
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,6 @@ export const PERSISTED_KEY_REGISTRY: ReadonlyArray<
"src/renderer/stores/terminal-close-confirm/store.ts",
["terminal-close-confirm-v1"],
],
[
"src/renderer/stores/automation-failures/store.ts",
["automation-failures-v1"],
],
[
"src/renderer/stores/app-version-history/store.ts",
["app-version-history-v1"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export const DEAD_KEYS: DeadKey[] = [
{ key: "leaderboard-asked-v1", match: "exact" },
// Opening a page no longer routes to the last-visited workspace
{ key: "last-active-v2-workspace", match: "exact" },
// Sidebar automations failure badge removed; it polled for every window
{ key: "automation-failures-v1", match: "exact" },
];

function matchesDeadKey(key: string): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
import { cn } from "@superset/ui/utils";
import { useNavigate } from "@tanstack/react-router";
import { LuEllipsis, LuPlay, LuRotateCw } from "react-icons/lu";
import type { AutomationLastRun } from "renderer/routes/_authenticated/_dashboard/hooks/useFailedAutomations";
import type { ProjectOption } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/types";
import { ProjectThumbnail } from "renderer/routes/_authenticated/components/ProjectThumbnail";
import { useCopyAutomationLink } from "../../hooks/useCopyAutomationLink";
import type { AutomationLastRun } from "../../hooks/useFailedAutomations";
import { AutomationActionsMenuItems } from "./components/AutomationActionsMenuItems";

type AutomationListItem = RouterOutputs["automation"]["list"][number];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { SelectAutomationRun } from "@superset/db/schema";
import { useMemo } from "react";
import { cloudTrpc } from "renderer/lib/cloud-trpc";

const LATEST_RUNS_POLL_MS = 120_000;

const FAILED_STATUSES: SelectAutomationRun["status"][] = [
"skipped_offline",
"dispatch_failed",
];

export interface AutomationLastRun {
status: SelectAutomationRun["status"];
/** createdAt as epoch ms; NaN-free (unparseable rows are dropped). */
at: number;
v2WorkspaceId: string | null;
chatSessionId: string | null;
terminalSessionId: string | null;
}

interface FailedAutomations {
/** Most recent run per automation, with its workspace/session links. */
lastRunById: Map<string, AutomationLastRun>;
/** Automations whose most recent run failed. */
failedIds: Set<string>;
}

export function useFailedAutomations(): FailedAutomations {
const { data: runRows = [] } = cloudTrpc.automation.latestRuns.useQuery(
undefined,
{ refetchInterval: LATEST_RUNS_POLL_MS, staleTime: 30_000 },
);

return useMemo(() => {
const lastRunById = new Map<string, AutomationLastRun>();
for (const run of runRows) {
const at = new Date(run.createdAt).getTime();
if (!Number.isFinite(at)) continue;
lastRunById.set(run.automationId, {
status: run.status,
at,
v2WorkspaceId: run.v2WorkspaceId ?? null,
chatSessionId: run.chatSessionId ?? null,
terminalSessionId: run.terminalSessionId ?? null,
});
}
const failedIds = new Set<string>();
for (const [id, run] of lastRunById) {
if (FAILED_STATUSES.includes(run.status)) failedIds.add(id);
}
return { lastRunById, failedIds };
}, [runRows]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ import {
SortableHeader,
type SortDirection,
} from "renderer/routes/_authenticated/_dashboard/components/SortableHeader";
import { useFailedAutomations } from "renderer/routes/_authenticated/_dashboard/hooks/useFailedAutomations";
import { AGENT_STORAGE_KEY } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/types";
import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider";
import { AutomationRow } from "./components/AutomationRow";
import { AutomationStatCards } from "./components/AutomationStatCards";
import { AutomationsEmptyState } from "./components/AutomationsEmptyState";
import { HostOfflineRunDialog } from "./components/HostOfflineRunDialog";
import { useFailedAutomations } from "./hooks/useFailedAutomations";
import type { AutomationTemplate } from "./templates";
import { matchAgentChoice, portableAgentValue } from "./utils/agentIdentity";
import { dispatchErrorCode, runErrorHelp } from "./utils/runErrorHelp";
Expand Down Expand Up @@ -301,15 +301,9 @@ function AutomationsPage() {
undefined,
{},
);
const { lastRunById, failedIds, markMyFailuresSeen } = useFailedAutomations();
const { lastRunById, failedIds } = useFailedAutomations();
const now = useNow(30_000);

// Opening the page clears the sidebar failure badge; failures that sync in
// while it stays open are marked seen too, until a newer run fails.
useEffect(() => {
markMyFailuresSeen();
}, [markMyFailuresSeen]);

const recentProjects = useRecentProjects();

const usersById = useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ import {
} from "renderer/hooks/useOpenNewWorkspace";
import { useZoomFactor } from "renderer/hooks/useZoomFactor";
import { useHotkeyDisplay } from "renderer/hotkeys";
import { cloudTrpc } from "renderer/lib/cloud-trpc";
import { electronTrpc } from "renderer/lib/electron-trpc";
import { useFolderFirstImport } from "renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport";
import { AppMenuButton } from "renderer/routes/_authenticated/_dashboard/components/AppMenuButton";
import { NavigationControls } from "renderer/routes/_authenticated/_dashboard/components/NavigationControls";
import { SidebarToggle } from "renderer/routes/_authenticated/_dashboard/components/SidebarToggle";
import { TopBarPortsDropdown } from "renderer/routes/_authenticated/_dashboard/components/TopBar/components/TopBarPortsDropdown";
import { useFailedAutomations } from "renderer/routes/_authenticated/_dashboard/hooks/useFailedAutomations";
import {
pullRequestsSearchFromFilters,
usePullRequestsFilterStore,
Expand Down Expand Up @@ -140,7 +140,7 @@ export function DashboardSidebarHeader({
const isMac = platform === undefined || platform === "darwin";
const zoomFactor = useZoomFactor();
const matchRoute = useMatchRoute();
const { gateFeature } = usePaywall();
const { gateFeature, hasAccess } = usePaywall();
const isWorkspacesListOpen = !!matchRoute({ to: "/v2-workspaces" });
const v2WorkspaceMatch = matchRoute({
to: "/v2-workspace/$workspaceId",
Expand Down Expand Up @@ -169,8 +169,7 @@ export function DashboardSidebarHeader({
const isPluginsEnabled =
(useFeatureFlagEnabled(FEATURE_FLAGS.PLUGINS) ?? false) ||
env.NODE_ENV === "development";
const { myFailedCount, hasAutomations, automationsPending } =
useFailedAutomations();
const cloudUtils = cloudTrpc.useUtils();

const {
tab: lastTab,
Expand All @@ -197,11 +196,18 @@ export function DashboardSidebarHeader({
// Automations are Pro, but an org that already has some (a downgrade) can
// still reach the list to pause, edit, or delete them; the page gates the
// actions that need the plan. A Free org with none meets the paywall here.
// While the list is still loading the answer is unknown, so let the click
// If the list can't be read the answer is unknown, so let the click
// through: an empty list page gates every action itself, and a wrong
// paywall on a downgraded org would be the worse mistake.
const handleAutomationsClick = () => {
if (hasAutomations || automationsPending) {
const handleAutomationsClick = async () => {
if (hasAccess(GATED_FEATURES.AUTOMATIONS)) {
navigate({ to: "/automations" });
return;
}
const automations = await cloudUtils.automation.list
.fetch()
.catch(() => null);
if (automations === null || automations.length > 0) {
navigate({ to: "/automations" });
return;
}
Expand Down Expand Up @@ -341,37 +347,21 @@ export function DashboardSidebarHeader({
<button
type="button"
onClick={handleAutomationsClick}
aria-label={
myFailedCount > 0
? t({
message: `Automations, ${myFailedCount} failing`,
})
: t({
message: "Automations",
})
}
aria-label={t({
message: "Automations",
})}
className={cn(
"relative flex size-7 items-center justify-center rounded-md transition-colors",
"flex size-7 items-center justify-center rounded-md transition-colors",
isAutomationsOpen
? "bg-fill-selected text-muted-foreground"
: "text-muted-foreground hover:bg-fill-hover",
)}
>
<LuClock className="size-3.5" strokeWidth={1.5} />
{myFailedCount > 0 && (
<span
aria-hidden="true"
className="absolute right-1 top-1 size-1.5 rounded-full bg-red-500"
/>
)}
</button>
</TooltipTrigger>
<TooltipContent side="right">
{myFailedCount > 0 ? (
<Trans>Automations ({myFailedCount} failing)</Trans>
) : (
<Trans>Automations</Trans>
)}
<Trans>Automations</Trans>
</TooltipContent>
</Tooltip>

Expand Down Expand Up @@ -645,16 +635,6 @@ export function DashboardSidebarHeader({
<span className="flex-1 text-left">
<Trans>Automations</Trans>
</span>
{myFailedCount > 0 && (
<span
title={t({
message: `${myFailedCount} of your automations failed their last run`,
})}
className="flex h-4 min-w-4 shrink-0 items-center justify-center rounded-full bg-red-500/15 px-1 text-[10px] font-medium tabular-nums text-red-600 dark:text-red-400"
>
{myFailedCount > 9 ? "9+" : myFailedCount}
</span>
)}
</button>

<button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export function useGettingStartedCard(): SidebarCardEntry | null {
});
const { data: automations } = cloudTrpc.automation.list.useQuery(undefined, {
enabled: visible,
refetchInterval: 60_000,
});
const completed =
(tried & 1) | (remoteEnabled ? 2 : 0) | (automations?.length ? 4 : 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export function useSidebarCloudPullRequests(
const query = cloudTrpc.integration.github.getByBranches.useQuery(
{ organizationId: organizationId ?? "", refs: stableRefs },
{
enabled: organizationId !== null,
enabled: organizationId !== null && stableRefs.length > 0,
refetchInterval: CLOUD_PULL_REQUESTS_REFETCH_INTERVAL_MS,
staleTime: 10_000,
// Keep chips up while the row set changes, but never carry another
Expand Down

This file was deleted.

Loading
Loading