Skip to content
Open
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
47 changes: 33 additions & 14 deletions web-admin/src/features/edit-session/PublishPopover.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@
import {
createRuntimeServiceGitMergeToBranchMutation,
createRuntimeServiceGitPushMutation,
createRuntimeServiceGitStatus,
getRuntimeServiceGitStatusQueryKey,
} from "@rilldata/web-common/runtime-client";
import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2";
import { Rocket } from "lucide-svelte";
import { buildPostMergeUrl } from "./post-merge-url";
import { goto } from "$app/navigation";
import {
fetchDeploymentGithubStatusChanges,
getDeploymentGithubStatus,
} from "@rilldata/web-admin/features/edit-session/selectors.ts";

export let organization: string;
export let project: string;
Expand All @@ -36,29 +39,29 @@
const client = useRuntimeClient();
const gitPushMutation = createRuntimeServiceGitPushMutation(client);
const gitMergeMutation = createRuntimeServiceGitMergeToBranchMutation(client);
const gitStatusQuery = createRuntimeServiceGitStatus(client, {});
const gitStatusQuery = getDeploymentGithubStatus(client, primaryBranch);
// Query GetProject without a branch param so `data.deployment` reflects
// the project's primary (prod) deployment — the same source of truth the
// project layout uses. ListDeployments is too loose: it includes orphan
// prod records whose project pointer has been cleared.
const projectQuery = createAdminServiceGetProject(organization, project);
const redeployProjectMutation = createAdminServiceRedeployProject();

$: currentBranch = $gitStatusQuery.data?.branch ?? "";
$: hasLocalChanges = $gitStatusQuery.data?.localChanges ?? false;
$: ({
isPending,
data: {
hasLocalChanges,
hasChangesOnCurrent,
alreadyOnPrimary,
disabledPerGitStatus,
},
} = $gitStatusQuery);

$: projectLoaded = $projectQuery.data !== undefined;
$: prodDeployment = $projectQuery.data?.deployment;
$: prodDeploymentActive =
!!prodDeployment && isActiveDeployment(prodDeployment);
$: alreadyOnPrimary =
!!primaryBranch && !!currentBranch && currentBranch === primaryBranch;
// TODO: this should also check currentBranch vs primaryBranch once that API is available.
$: disabled =
!primaryBranch ||
!currentBranch ||
!projectLoaded ||
alreadyOnPrimary ||
isPublishing;
$: disabled = !projectLoaded || disabledPerGitStatus || isPublishing;

// Prefetch prod's project parser commit SHA so the deploying page can
// wait for prod to advance past it before redirecting to the dashboard,
Expand Down Expand Up @@ -94,6 +97,22 @@
{},
);

// Refetch local changes status, we predict this based on file watcher response.
// But we dont check if changes flipped to with changes to without changes.
hasLocalChanges = await fetchDeploymentGithubStatusChanges(
client,
queryClient,
primaryBranch,
);
if (!hasLocalChanges && !hasChangesOnCurrent) {
eventBus.emit("notification", {
type: "default",
message: "No changes detected",
});
isPublishing = false;
return;
}

try {
if (hasLocalChanges) {
await $gitPushMutation.mutateAsync({
Expand Down Expand Up @@ -193,7 +212,7 @@
<span class="text-xs">
{#if alreadyOnPrimary}
Already on production
{:else if !primaryBranch || !currentBranch || !projectLoaded}
{:else if isPending || !projectLoaded}
Loading project...
{:else if !hasLocalChanges}
No changes to publish
Expand Down
101 changes: 101 additions & 0 deletions web-admin/src/features/edit-session/selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { RuntimeClient } from "@rilldata/web-common/runtime-client/v2";
import { derived } from "svelte/store";
import {
createRuntimeServiceGitStatus,
getRuntimeServiceGitStatusQueryKey,
runtimeServiceGitStatus,
type V1GitStatusResponse,
} from "@rilldata/web-common/runtime-client";
import type { QueryClient } from "@tanstack/query-core";

export function getDeploymentGithubStatus(
runtimeClient: RuntimeClient,
primaryBranch: string | undefined,
) {
return derived(
[
createRuntimeServiceGitStatus(runtimeClient, {}),
createRuntimeServiceGitStatus(runtimeClient, {
remoteBranch: primaryBranch,
}),
],
([currentBranchGitStatusResp, primaryBranchGitStatusResp]) => {
const isPending =
currentBranchGitStatusResp.isPending ||
primaryBranchGitStatusResp.isPending;
const error =
currentBranchGitStatusResp.error || primaryBranchGitStatusResp.error;
if (isPending || error) {
return {
isPending,
error,
data: {
hasLocalChanges: false,
hasChangesOnCurrent: false,
alreadyOnPrimary: false,
disabledPerGitStatus: true,
},
};
}

const currentBranch = currentBranchGitStatusResp.data?.branch ?? "";
const hasChangesAgainstCurrent = Boolean(
currentBranchGitStatusResp.data?.localCommits ||
currentBranchGitStatusResp.data?.localChanges,
);
const hasChangesOnCurrent = Boolean(
primaryBranchGitStatusResp.data?.localCommits ||
primaryBranchGitStatusResp.data?.localChanges,
);
const hasLocalChanges = hasChangesAgainstCurrent || hasChangesOnCurrent;

const alreadyOnPrimary =
!!primaryBranch && !!currentBranch && currentBranch === primaryBranch;

const disabledPerGitStatus =
!primaryBranch ||
!currentBranch ||
alreadyOnPrimary ||
!hasLocalChanges;

return {
isPending: false,
error: undefined,
data: {
hasLocalChanges,
hasChangesOnCurrent,
alreadyOnPrimary,
disabledPerGitStatus,
},
};
},
);
}

export async function fetchDeploymentGithubStatusChanges(
runtimeClient: RuntimeClient,
queryClient: QueryClient,
primaryBranch: string | undefined,
) {
const currentBranchGitStatusResp = await queryClient.fetchQuery({
queryKey: getRuntimeServiceGitStatusQueryKey(runtimeClient.instanceId, {}),
queryFn: () => runtimeServiceGitStatus(runtimeClient, {}),
});
const hasChangesAgainstCurrent = Boolean(
currentBranchGitStatusResp.localCommits ||
currentBranchGitStatusResp.localChanges,
);

const primaryBranchGitStatusResp =
queryClient.getQueryData<V1GitStatusResponse>(
getRuntimeServiceGitStatusQueryKey(runtimeClient.instanceId, {
remoteBranch: primaryBranch,
}),
);
const hasChangesAgainstPrimary = Boolean(
primaryBranchGitStatusResp?.localCommits ||
primaryBranchGitStatusResp?.localChanges,
);

return hasChangesAgainstCurrent || hasChangesAgainstPrimary;
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ function fakeQueryClient() {
invalidateQueries: vi.fn(),
refetchQueries: vi.fn(),
resetQueries: vi.fn(),
getQueryData: vi.fn(),
} as unknown as QueryClient & {
invalidateQueries: ReturnType<typeof vi.fn>;
refetchQueries: ReturnType<typeof vi.fn>;
resetQueries: ReturnType<typeof vi.fn>;
getQueryData: ReturnType<typeof vi.fn>;
};
}

Expand Down Expand Up @@ -229,10 +231,10 @@ describe("handleFileEvent", () => {
);

const gitStatusKey = getRuntimeServiceGitStatusQueryKey(INSTANCE_ID, {});
const gitHit = qc.invalidateQueries.mock.calls.some(
([arg]) =>
Array.isArray(arg.queryKey) &&
JSON.stringify(arg.queryKey) === JSON.stringify(gitStatusKey),
const gitHit = qc.getQueryData.mock.calls.some(
([queryKey]) =>
Array.isArray(queryKey) &&
JSON.stringify(queryKey) === JSON.stringify(gitStatusKey),
);
expect(gitHit).toBe(true);
});
Expand Down
19 changes: 16 additions & 3 deletions web-common/src/runtime-client/invalidation/file-invalidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
getRuntimeServiceIssueDevJWTQueryKey,
getRuntimeServiceListFilesQueryKey,
V1FileEvent,
type V1GitStatusResponse,
type V1WatchFilesResponse,
} from "@rilldata/web-common/runtime-client";
import type { RuntimeClient } from "@rilldata/web-common/runtime-client/v2";
Expand Down Expand Up @@ -80,9 +81,7 @@ export async function handleFileEvent(
}

// Keep the cloud editor's commit button in sync with the working tree.
void queryClient.invalidateQueries({
queryKey: getRuntimeServiceGitStatusQueryKey(instanceId, {}),
});
resetGitStatusQuery(queryClient, instanceId);
}

// Throttle: when many files arrive at once (e.g. initial sync), one refetch
Expand All @@ -95,3 +94,17 @@ export async function handleFileEvent(
);
}
}

function resetGitStatusQuery(queryClient: QueryClient, instanceId: string) {
const queryKey = getRuntimeServiceGitStatusQueryKey(instanceId, {});

const existingQueryData =
queryClient.getQueryData<V1GitStatusResponse>(queryKey);
// Skip updating the cache if there is no query data.
if (!existingQueryData) return;

queryClient.setQueryData(queryKey, {
...existingQueryData,
localChanges: true, // Force localChanges=true
});
}
Loading