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
8 changes: 8 additions & 0 deletions apps/dashboard/src/components/details/detail-activity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
githubViewerQueryOptions,
} from "#/lib/github.query";
import type { GitHubActor } from "#/lib/github.types";
import { removePullFromOpenViews } from "#/lib/github-query-updates";
import { checkPermissionWarning } from "#/lib/warning-store";

export function DetailActivityHeader({
Expand Down Expand Up @@ -180,6 +181,13 @@ export function DetailCommentBox({
},
});
if (result.ok) {
if (newState === "closed") {
removePullFromOpenViews(queryClient, scope, {
owner,
repo,
pullNumber: issueNumber,
});
}
void queryClient.invalidateQueries({
queryKey: githubQueryKeys.all,
});
Expand Down
22 changes: 21 additions & 1 deletion apps/dashboard/src/components/inbox/inbox-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import type {
NotificationParticipant,
NotificationsResult,
} from "#/lib/github.types";
import { githubRevalidationSignalKeys } from "#/lib/github-revalidation";
import { useGitHubSignalStream } from "#/lib/use-github-signal-stream";
import { useHasMounted } from "#/lib/use-has-mounted";

const routeApi = getRouteApi("/_protected/inbox");
Expand Down Expand Up @@ -120,11 +122,25 @@ export function InboxPage() {
const [selectedId, setSelectedId] = useState<string | null>(null);
const [filter, setFilter] = useState<InboxFilter>("unread");

const queryInput = { all: filter === "all" };
const queryInput = useMemo(() => ({ all: filter === "all" }), [filter]);
const queryKey = useMemo(
() => githubQueryKeys.notifications.list(scope, queryInput),
[scope, queryInput],
);
const webhookRefreshTargets = useMemo(
() => [
{
queryKey,
signalKeys: [githubRevalidationSignalKeys.notifications],
},
],
[queryKey],
);
const query = useQuery({
...githubNotificationsQueryOptions(scope, queryInput),
enabled: hasMounted,
});
useGitHubSignalStream(webhookRefreshTargets);

const queryClient = useQueryClient();
const notifications = query.data?.notifications ?? [];
Expand Down Expand Up @@ -208,6 +224,7 @@ const InboxSidebar = memo(function InboxSidebar({
const prev = queryClient.getQueryData<NotificationsResult>(queryKey);
if (prev) {
queryClient.setQueryData<NotificationsResult>(queryKey, {
...prev,
notifications: prev.notifications.map((n) => ({
...n,
unread: false,
Expand All @@ -234,6 +251,7 @@ const InboxSidebar = memo(function InboxSidebar({
const prev = queryClient.getQueryData<NotificationsResult>(queryKey);
if (prev) {
queryClient.setQueryData<NotificationsResult>(queryKey, {
...prev,
notifications: prev.notifications.filter((n) => n.unread),
});
}
Expand Down Expand Up @@ -436,6 +454,7 @@ const InboxRow = memo(function InboxRow({
const prev = queryClient.getQueryData<NotificationsResult>(queryKey);
if (prev) {
queryClient.setQueryData<NotificationsResult>(queryKey, {
...prev,
notifications: prev.notifications.filter(
(n) => n.id !== notification.id,
),
Expand All @@ -455,6 +474,7 @@ const InboxRow = memo(function InboxRow({
const prev = queryClient.getQueryData<NotificationsResult>(queryKey);
if (prev) {
queryClient.setQueryData<NotificationsResult>(queryKey, {
...prev,
notifications: prev.notifications.map((n) =>
n.id === notification.id ? { ...n, unread: false } : n,
),
Expand Down
86 changes: 68 additions & 18 deletions apps/dashboard/src/components/pulls/detail/pull-detail-activity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ import {
lazy,
Suspense,
useCallback,
useEffect,
useMemo,
useRef,
useState,
Expand Down Expand Up @@ -113,10 +112,13 @@ import type {
PullWorkflowApproval,
TimelineEvent,
} from "#/lib/github.types";
import { removePullFromOpenViews } from "#/lib/github-query-updates";
import { githubRevalidationSignalKeys } from "#/lib/github-revalidation";
import {
mergeIssueStateIntoCloseEvent,
parseCloseReason,
} from "#/lib/timeline-close-reason";
import { useGitHubSignalStream } from "#/lib/use-github-signal-stream";
import { usePrefersNoHover } from "#/lib/use-prefers-no-hover";
import { checkPermissionWarning } from "#/lib/warning-store";

Expand Down Expand Up @@ -334,16 +336,53 @@ function MergeStatusSection({
prTitle: string;
firstCommitMessage?: string;
}) {
const input = useMemo(
() => ({ owner, repo, pullNumber }),
[owner, repo, pullNumber],
);
const statusQueryKey = useMemo(
() => githubQueryKeys.pulls.status(scope, input),
[scope, input],
);
const statusQuery = useQuery({
...githubPullStatusQueryOptions(scope, { owner, repo, pullNumber }),
...githubPullStatusQueryOptions(scope, input),
});
const workflowStatusRefreshTargets = useMemo(
() => [
{
queryKey: statusQueryKey,
signalKeys: [
githubRevalidationSignalKeys.pullEntity(input),
githubRevalidationSignalKeys.repoProtection({
owner: input.owner,
repo: input.repo,
}),
githubRevalidationSignalKeys.repoStatuses({
owner: input.owner,
repo: input.repo,
}),
...(statusQuery.data?.pendingWorkflowApprovals ?? []).map(
(approval) =>
githubRevalidationSignalKeys.workflowRunEntity({
owner: input.owner,
repo: input.repo,
runId: approval.workflowRunId,
}),
),
],
},
],
[input, statusQuery.data?.pendingWorkflowApprovals, statusQueryKey],
);
useGitHubSignalStream(workflowStatusRefreshTargets);

const status = statusQuery.data ?? null;

if (!status) return <MergeStatusSkeleton />;

return (
<MergeStatusCard
scope={scope}
status={status}
owner={owner}
repo={repo}
Expand Down Expand Up @@ -440,13 +479,15 @@ function MergedBranchBanner({
}

function MergeStatusCard({
scope,
status,
owner,
repo,
pullNumber,
prTitle,
firstCommitMessage,
}: {
scope: GitHubQueryScope;
status: PullStatus;
owner: string;
repo: string;
Expand Down Expand Up @@ -506,6 +547,7 @@ function MergeStatusCard({
{/* Checks section */}
{(checks.total > 0 || pendingWorkflowApprovals.length > 0) && (
<ChecksSection
scope={scope}
checks={checks}
checkRuns={checkRuns}
pendingWorkflowApprovals={pendingWorkflowApprovals}
Expand Down Expand Up @@ -545,6 +587,7 @@ function MergeStatusCard({

{/* Merge action footer */}
<MergeFooter
scope={scope}
isMergeBlocked={isMergeBlocked}
canMerge={canMerge}
bypass={bypass}
Expand Down Expand Up @@ -754,6 +797,7 @@ function ReviewsSection({
// ── Checks section ──────────────────────────────────────────────────

function ChecksSection({
scope,
checks,
checkRuns,
pendingWorkflowApprovals,
Expand All @@ -763,6 +807,7 @@ function ChecksSection({
repo,
pullNumber,
}: {
scope: GitHubQueryScope;
checks: PullStatus["checks"];
checkRuns: PullCheckRun[];
pendingWorkflowApprovals: PullWorkflowApproval[];
Expand All @@ -776,6 +821,10 @@ function ChecksSection({
const [isRerunning, setIsRerunning] = useState(false);
const [isApproving, setIsApproving] = useState(false);
const queryClient = useQueryClient();
const statusQueryKey = useMemo(
() => githubQueryKeys.pulls.status(scope, { owner, repo, pullNumber }),
[scope, owner, repo, pullNumber],
);

const pendingTotal = checks.pending + checks.expected;
const approvalCount = pendingWorkflowApprovals.length;
Expand Down Expand Up @@ -894,33 +943,27 @@ function ChecksSection({
toast.warning(
`Approved ${approved} workflow${approved !== 1 ? "s" : ""}, but ${failed} failed`,
);
} else {
toast.success(
`Approved ${pendingWorkflowApprovals.length} workflow${pendingWorkflowApprovals.length !== 1 ? "s" : ""}`,
);
}
// Keep the button in loading state; the effect below resets it once the
// workflow_run webhook invalidates the cache and the pending list drains.
await queryClient.invalidateQueries({ queryKey: ["github"] });
await queryClient.invalidateQueries({
queryKey: statusQueryKey,
exact: true,
refetchType: "active",
});
} else {
toast.error(result.error);
checkPermissionWarning(result, `${owner}/${repo}`);
setIsApproving(false);
}
} catch {
toast.error("Failed to approve workflows");
} finally {
setIsApproving(false);
}
};

// Reset the approving state when the pending list drains (webhook arrived) or
// after a safety timeout to avoid a permanently-stuck spinner.
useEffect(() => {
if (!isApproving) return;
if (pendingWorkflowApprovals.length === 0) {
setIsApproving(false);
return;
}
const timer = setTimeout(() => setIsApproving(false), 30_000);
return () => clearTimeout(timer);
}, [isApproving, pendingWorkflowApprovals.length]);

return (
<Collapsible open={open} onOpenChange={setOpen}>
<CollapsibleTrigger asChild>
Expand Down Expand Up @@ -1296,6 +1339,7 @@ const MERGE_STRATEGIES = [
];

function MergeFooter({
scope,
isMergeBlocked,
canMerge,
bypass,
Expand All @@ -1305,6 +1349,7 @@ function MergeFooter({
prTitle,
firstCommitMessage,
}: {
scope: GitHubQueryScope;
isMergeBlocked: boolean;
canMerge: boolean;
bypass: ReturnType<typeof useMergeBypass>;
Expand Down Expand Up @@ -1357,6 +1402,11 @@ function MergeFooter({
},
});
if (result.ok) {
removePullFromOpenViews(queryClient, scope, {
owner,
repo,
pullNumber,
});
await queryClient.invalidateQueries({ queryKey: ["github"] });
} else {
toast.error(result.error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ export function PullDetailContent({
queryKey: githubQueryKeys.pulls.status(scope, input),
signalKeys: [
githubRevalidationSignalKeys.pullEntity(input),
githubRevalidationSignalKeys.repoProtection({
owner: input.owner,
repo: input.repo,
}),
githubRevalidationSignalKeys.repoStatuses({
owner: input.owner,
repo: input.repo,
Expand Down
Loading
Loading