Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add and edit github connection #5263

Merged
merged 38 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7c7c0f5
Adding backend API to fetch connected repos
AdityaHegde Jul 12, 2024
f157c1e
Merge branch 'main' into adityahegde/connect-to-github
AdityaHegde Jul 12, 2024
3caebf5
Cleanup
AdityaHegde Jul 12, 2024
708237b
Basics of github connection UI
AdityaHegde Jul 12, 2024
7028a51
Add connecting to github UI
AdityaHegde Jul 15, 2024
c0a5baf
Merge branch 'main' into adityahegde/connect-to-github-ui
AdityaHegde Jul 16, 2024
8f8879e
Add github url edit button
AdityaHegde Jul 16, 2024
64e2d30
Avoid unnessary calls
AdityaHegde Jul 17, 2024
c57aa21
Merge branch 'main' into adityahegde/connect-to-github-ui
AdityaHegde Jul 17, 2024
ec24980
Adjust github url edit button
AdityaHegde Jul 17, 2024
546266e
PR comments
AdityaHegde Jul 18, 2024
53c1b1a
Fix invalidations
AdityaHegde Jul 19, 2024
fc2bce3
Fix moving to github connection
AdityaHegde Jul 22, 2024
5c3358f
Merge branch 'main' into adityahegde/connect-to-github-ui
AdityaHegde Jul 22, 2024
08b0376
Regenerate from proto
AdityaHegde Jul 22, 2024
21afe75
Refactor to use a single class
AdityaHegde Jul 22, 2024
9012e9f
Merge branch 'main' into adityahegde/connect-to-github-ui
AdityaHegde Jul 22, 2024
1139185
Fix lint
AdityaHegde Jul 23, 2024
58ca0c3
Update error colors in user invite
AdityaHegde Jul 23, 2024
f402534
Fix a page refresh after updating project
AdityaHegde Jul 23, 2024
9a99b5c
Update github repo selector modal
AdityaHegde Jul 24, 2024
b5d6cdc
Push to github for the 1st time
AdityaHegde Jul 25, 2024
61504ea
Happy path for push to github
AdityaHegde Jul 26, 2024
af24d8b
Handle existing content
AdityaHegde Jul 26, 2024
69e8508
Fix lint and always push
AdityaHegde Jul 26, 2024
88d387e
Update based on mocks
AdityaHegde Jul 29, 2024
83d60f8
Update connection to handle updating to new repo
AdityaHegde Jul 29, 2024
079b2b1
Merge branch 'main' into adityahegde/connect-to-github-ui
AdityaHegde Jul 29, 2024
ce868ee
Cleanup
AdityaHegde Jul 29, 2024
f09758d
Fix copy on edit
AdityaHegde Jul 29, 2024
be44162
Ignore .git folder
AdityaHegde Jul 29, 2024
6f8caba
Add telemetry
AdityaHegde Jul 30, 2024
0c17b50
Fix issues around overwrite
AdityaHegde Jul 30, 2024
f31a127
Merge branch 'main' into adityahegde/connect-to-github-ui
AdityaHegde Jul 30, 2024
880f729
PR comments
AdityaHegde Jul 30, 2024
ce5ea9b
Add autoclose on github connection confirmation
AdityaHegde Jul 31, 2024
bfeca3b
PR comments
AdityaHegde Jul 31, 2024
f6c95a7
UI PR comments
AdityaHegde Jul 31, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script lang="ts">
import {
AlertDialog,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@rilldata/web-common/components/alert-dialog";
import { Button } from "@rilldata/web-common/components/button";
import Github from "@rilldata/web-common/components/icons/Github.svelte";

export let open = false;
export let onContinue: () => void;
</script>

<AlertDialog bind:open>
<AlertDialogTrigger asChild>
<div class="hidden"></div>
</AlertDialogTrigger>
<AlertDialogContent>
<div class="flex flex-row gap-x-2">
<Github size="28px" />
<div class="flex flex-col">
<AlertDialogHeader>
<AlertDialogTitle>
Connect this project to a Github repo
</AlertDialogTitle>
<AlertDialogDescription>
Before continuing, you need to make sure your Rill project has been
<!-- TODO: docs link -->
pushed to a repo on Github.
<a href="https://docs.rilldata.com">Learn how</a>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter class="mt-5">
<Button type="secondary" on:click={() => (open = false)}>
Cancel
</Button>
<Button type="primary" on:click={onContinue}>Continue</Button>
</AlertDialogFooter>
</div>
</div>
</AlertDialogContent>
</AlertDialog>
36 changes: 36 additions & 0 deletions web-admin/src/features/projects/github/GithubConnection.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this extra layer of abstraction isn't necessary & adds complexity.

Is there something wrong with using vanilla TanStack Query in the ProjectGitHubConnection.svelte component? That's the pattern we're using most frequently throughout the codebase.

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
createAdminServiceGetGithubUserStatus,
getAdminServiceGetGithubUserStatusQueryKey,
} from "@rilldata/web-admin/client";
import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient";
import { waitUntil } from "@rilldata/web-common/lib/waitUtils";
import { get } from "svelte/store";

export class GithubConnection {
public readonly userStatus = createAdminServiceGetGithubUserStatus();

private connecting: boolean;

public constructor(private readonly onReconnect: () => void) {}

public async check() {
this.connecting = false;
await waitUntil(() => !get(this.userStatus).isLoading);
const userStatus = get(this.userStatus).data;
if (userStatus.hasAccess) {
return this.onReconnect();
}

this.connecting = true;
window.open(userStatus.grantAccessUrl, "_blank");
}

public async focused() {
if (!this.connecting) return;
await queryClient.refetchQueries(
getAdminServiceGetGithubUserStatusQueryKey(),
);
if (this.connecting) this.onReconnect();
this.connecting = false;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { getRepoNameFromGithubUrl } from "./github-utils";
import { getRepoNameFromGithubUrl } from "web-admin/src/features/projects/github/github-utils";
ericpgreen2 marked this conversation as resolved.
Show resolved Hide resolved

export let githubUrl: string;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<script lang="ts">
import {
createAdminServiceGetProject,
createAdminServiceUpdateProject,
getAdminServiceGetGithubUserStatusQueryKey,
} from "@rilldata/web-admin/client";
import { GithubRepoUpdater } from "@rilldata/web-admin/features/projects/github/GithubRepoUpdater";
import {
AlertDialog,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@rilldata/web-common/components/alert-dialog";
import { Button } from "@rilldata/web-common/components/button";
import Github from "@rilldata/web-common/components/icons/Github.svelte";
import Select from "@rilldata/web-common/components/forms/Select.svelte";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little out-of-scope of this PR, but it'd be nice to deprecate this in favor of ShadCN's Select component

import Spinner from "@rilldata/web-common/features/entity-management/Spinner.svelte";
import { EntityStatus } from "@rilldata/web-common/features/entity-management/types";
import { eventBus } from "@rilldata/web-common/lib/event-bus/event-bus";
import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient";
import { invalidateRuntimeQueries } from "@rilldata/web-common/runtime-client/invalidation";
import type { AxiosError } from "axios";

export let open = false;
export let project: string;
export let organization: string;

let githubUrl = "";
const githubRepoUpdater = new GithubRepoUpdater();
const githubRepos = githubRepoUpdater.userRepos;
const status = githubRepoUpdater.status;
const projectQuery = createAdminServiceGetProject(organization, project);

$: repoSelections =
$githubRepos.data?.repos?.map((r) => ({
value: r.url,
label: `${r.owner}/${r.name}`,
})) ?? [];

const updateProject = createAdminServiceUpdateProject();
async function updateGithubUrl() {
await $updateProject.mutateAsync({
name: project,
organizationName: organization,
data: {
githubUrl,
},
});
eventBus.emit("notification", {
message: `Set github repo to ${githubUrl}`,
type: "success",
});
void queryClient.refetchQueries(
getAdminServiceGetGithubUserStatusQueryKey(),
);
void invalidateRuntimeQueries(
queryClient,
$projectQuery.data.prodDeployment.runtimeInstanceId,
);
open = false;
}

function handleVisibilityChange() {
if (document.visibilityState !== "visible") return;
void githubRepoUpdater.focused();
}

$: error = ($status.error ?? $updateProject.error) as unknown as AxiosError;
</script>

<svelte:window on:visibilitychange={handleVisibilityChange} />

<AlertDialog bind:open>
<AlertDialogTrigger asChild>
<div class="hidden"></div>
</AlertDialogTrigger>
<AlertDialogContent>
<div class="flex flex-row gap-x-2">
<Github size="28px" />
<div class="flex flex-col">
<AlertDialogHeader>
<AlertDialogTitle>Select Github repository</AlertDialogTitle>
<AlertDialogDescription class="flex flex-col gap-y-1">
<span>
Which Github repo would you like to connect to this Rill project?
</span>
{#if $status.isLoading}
<div class="flex flex-row items-center ml-5 h-8">
<Spinner status={EntityStatus.Running} />
</div>
{:else}
<Select
id="repo-selector"
bind:value={githubUrl}
label=""
options={repoSelections}
/>
{/if}
<span class="font-semibold">
Note: Contents of this repo will replace your current Rill
project.
</span>
{#if error}
<div class="text-red-500 text-sm py-px">
{error.response?.data?.message ?? error.message}
</div>
{/if}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter class="mt-5">
<Button
outline={false}
type="link"
on:click={() => githubRepoUpdater.check()}
>
Choose other repos
</Button>
<Button type="secondary" on:click={() => (open = false)}>
Cancel
</Button>
<Button
type="primary"
on:click={() => updateGithubUrl()}
loading={$updateProject.isLoading}
>
Continue
</Button>
</AlertDialogFooter>
</div>
</div>
</AlertDialogContent>
</AlertDialog>
65 changes: 65 additions & 0 deletions web-admin/src/features/projects/github/GithubRepoUpdater.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, is there a reason to be adding this abstraction on top of vanilla TanStack Query? To update the GitHub repo seems like a vanilla mutation and shouldn't require too much complexity. I wonder if the bugginess I shared via Jam is a derivative of this complexity.

If we're to go down this route, I'd expect a "GithubRepoUpdater" to include a method to actually update the Github repo, but that looks to be happening outside of this abstraction in the GithubRepoSelectionDialog.svelte component.

Copy link
Collaborator Author

@AdityaHegde AdityaHegde Jul 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This endpoint can be slow if there are a lot of repos and orgs connected. I want to avoid refetching it on window focus all the time, but only when connect to github or choose other repos is clicked.

Both this and GithubConnection have a check to only refetch if it was explicitly asked for (basically the connecting check).

Naming this GithubRepoUpdater is just a remnant of some old code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. The vanilla TanStack approach would be to use $githubQuery.refetch() in the component itself. Might be simpler?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are refetching the query right? The idea is to hide as much code outside of components as possible. We still need the check if we popped a page to select github repos or not.

This will also hopefully allow us to abstract such code out. But it will have to be follow ups

Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
adminServiceListGithubUserRepos,
createAdminServiceGetGithubUserStatus,
createAdminServiceListGithubUserRepos,
getAdminServiceListGithubUserReposQueryKey,
type RpcStatus,
} from "@rilldata/web-admin/client";
import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient";
import { waitUntil } from "@rilldata/web-common/lib/waitUtils";
import { derived, get } from "svelte/store";

export class GithubRepoUpdater {
public readonly userStatus = createAdminServiceGetGithubUserStatus();
public readonly userRepos = derived([this.userStatus], ([userStatus], set) =>
createAdminServiceListGithubUserRepos({
query: {
enabled: !!userStatus.data?.hasAccess,
queryClient,
},
}).subscribe(set),
) as ReturnType<
typeof createAdminServiceListGithubUserRepos<
Awaited<ReturnType<typeof adminServiceListGithubUserRepos>>,
RpcStatus
>
>;

public readonly status = derived(
[this.userStatus, this.userRepos],
([userStatus, userRepos]) => {
if (userStatus.isFetching || userRepos.isFetching) {
return {
isLoading: true,
error: undefined,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be consistent with terminology. If reading isFetching, then we should return isFetching. If reading isLoading, then we should return isLoading.


return {
isLoading: false,
error: userStatus.error ?? userRepos.error,
};
},
);

private connecting: boolean;

public constructor() {}

public async check() {
this.connecting = false;
await waitUntil(() => !get(this.userStatus).isLoading);
const userStatus = get(this.userStatus).data;

this.connecting = true;
window.open(userStatus.grantAccessUrl, "_blank");
}

public async focused() {
if (!this.connecting) return;
await queryClient.refetchQueries(
getAdminServiceListGithubUserReposQueryKey(),
);
this.connecting = false;
}
}
Loading
Loading