batches: create GitHub App UX flow#52979
Conversation
3a0de1d to
bb1df55
Compare
…ovided, clean up text
commit c3b078c Author: BolajiOlajide <cooproton@gmail.com> Date: Sat Jun 3 22:29:17 2023 +0100 update batches redirect url commit 30f8985 Author: BolajiOlajide <cooproton@gmail.com> Date: Sat Jun 3 22:27:12 2023 +0100 bazel it commit e4e734c Author: BolajiOlajide <cooproton@gmail.com> Date: Sat Jun 3 21:57:39 2023 +0100 add tests for middleware commit 80855ed Author: BolajiOlajide <cooproton@gmail.com> Date: Sat Jun 3 16:18:46 2023 +0100 add tests commit 5b3c0f8 Author: BolajiOlajide <cooproton@gmail.com> Date: Tue May 30 17:54:42 2023 +0100 handle redirect for batches app
f7c74ed to
ec71e29
Compare
4c480a6 to
28a3f70
Compare
| if (!response.ok) { | ||
| if (response.body instanceof ReadableStream) { | ||
| const error = await response.text() | ||
| throw new Error(error) | ||
| } | ||
| } | ||
| const state = (await response.json()) as StateResponse |
There was a problem hiding this comment.
| if (state.webhookUUID?.length) { | ||
| webhookURL = new URL(`/.api/webhooks/${state.webhookUUID}`, originURL).href | ||
| } | ||
| if (!state.state?.length) { | ||
| throw new Error('Response from server missing state parameter') | ||
| } |
There was a problem hiding this comment.
Covering our grounds here rather than blindly assuming what we got back from the server was a StateResponse.
| try { | ||
| new URL(trimmedURL) | ||
| } catch { | ||
| return setUrlError('URL is not valid.') | ||
| } |
There was a problem hiding this comment.
This ensures the GitHub URL an admin input is actually a valid URL. Before if you made a typo it would just... completely blow up 😂
Screen.Recording.2023-06-07.at.10.29.11.AM.mov
There was a problem hiding this comment.
Tests from Bolaji's PR, thank you Bolaji!! 🙌 The only part that's new since my PR is the addition of several test cases for GenerateRedirectURL to ensure a redirect URL is still returned with an error message encoded as a URL parameter now.
| { | ||
| name: "invalid domain", | ||
| domain: &invalidDomain, | ||
| expectedURL: "/site-admin/github-apps?success=false&error=invalid+domain%3A+invalid", | ||
| }, | ||
| { | ||
| name: "repos creation error", | ||
| domain: &reposDomain, | ||
| creationErr: creationErr, | ||
| expectedURL: "/site-admin/github-apps?success=false&error=uh+oh%21", | ||
| }, | ||
| { | ||
| name: "batches creation error", | ||
| domain: &batchesDomain, | ||
| creationErr: creationErr, | ||
| expectedURL: "/site-admin/batch-changes?success=false&error=uh+oh%21", | ||
| }, |
There was a problem hiding this comment.
The new test cases.
| /> | ||
| {error && <ErrorAlert className="mt-4 mb-0 text-left" error={error} />} | ||
| {!success && setupError && <GitHubAppFailureAlert error={setupError} />} | ||
| <ConnectionContainer> | ||
| {error && <ErrorAlert error={error} />} |
There was a problem hiding this comment.
The existing error here from the connection was actually redundant with the one on line 84/90.
| <Button | ||
| variant="primary" | ||
| onClick={createState} | ||
| disabled={name.trim().length < 3 || url.trim().length < 10} | ||
| > | ||
| <Button variant="primary" onClick={createState} disabled={!!nameError || !!urlError}> |
There was a problem hiding this comment.
The condition to disable the button here was arbitrary and didn't provide any feedback to the user as to why it was disabled. I've replaced it with actual per-input validation using <Input status="error" error={...} /> and updated this condition to disable the button corresponding with either of those input errors being present.
|
Codenotify: Notifying subscribers in CODENOTIFY files for diff fc8071b...262bc44.
|
|
Okay build is finally all green again!! 😌 |
st0nebreaker
left a comment
There was a problem hiding this comment.
Looks really good!
I tested through the happy path and all is gucci. For the sad paths you featured in your description, is there a clear way to trigger those or did you have to manually change parts of the code/setup to get that? I don't need to test the failure path flow, just curious, and the added tests/flows you tested give me confidence.
| try { | ||
| new URL(trimmedURL) | ||
| } catch { | ||
| return setUrlError('URL is not valid.') | ||
| } |
| switch *parsedDomain { | ||
| case types.ReposGitHubAppDomain: | ||
| if creationErr != nil { | ||
| return fmt.Sprintf("/site-admin/github-apps?success=false&error=%s", url.QueryEscape(creationErr.Error())) | ||
| } | ||
| if installationID == nil || appID == nil { | ||
| return fmt.Sprintf("/site-admin/github-apps?success=false&error=%s", url.QueryEscape("missing installation ID or app ID")) | ||
| } | ||
|
|
||
| return fmt.Sprintf("/site-admin/github-apps/%s?installation_id=%d", MarshalGitHubAppID(int64(*appID)), *installationID) | ||
| case types.BatchesGitHubAppDomain: | ||
| if creationErr != nil { | ||
| return fmt.Sprintf("/site-admin/batch-changes?success=false&error=%s", url.QueryEscape(creationErr.Error())) | ||
| } | ||
|
|
||
| // This shouldn't really happen unless we also had an error, but we handle it just | ||
| // in case | ||
| if appName == nil { | ||
| return "/site-admin/batch-changes?success=true" | ||
| } | ||
| return fmt.Sprintf("/site-admin/batch-changes?success=true&app_name=%s", *appName) | ||
| default: | ||
| return fmt.Sprintf("/site-admin/github-apps?success=false&error=%s", url.QueryEscape(fmt.Sprintf("unsupported github apps domain: %v", parsedDomain))) | ||
| } | ||
| } |
There was a problem hiding this comment.
Love this redirect logic & passing the success & error info through query params!
Yeah I was manually forcing failures to happen by changing the middleware code at various points. It's not too annoying if you're interested in playing around with it. If you find the handler for the r.HandleFunc(prefix+"/setup", ...Anywhere there's an error code path that's normally behind a condition, e.g. if err != nil {
redirectURL := generateRedirectURL(nil, nil, nil, nil, errors.New("Bad request, invalid state"))
http.Redirect(w, req, redirectURL, http.StatusFound)
return
}I'd just comment out the condition to test what it would be like to fail at that point: // if err != nil {
redirectURL := generateRedirectURL(nil, nil, nil, nil, errors.New("Bad request, invalid state"))
http.Redirect(w, req, redirectURL, http.StatusFound)
return
// } |
BolajiOlajide
left a comment
There was a problem hiding this comment.
Code-wise, this looks good to me. Thanks for the detailed explanation.
Just need to figure out why I get a nil response from createCommitFromPatch
pjlast
left a comment
There was a problem hiding this comment.
Amazing PR, thanks for all the tweaks and polish and the super detailed code walk through 🙏
|
|
Closes https://github.com/sourcegraph/sourcegraph/issues/52223, https://github.com/sourcegraph/sourcegraph/issues/52220, https://github.com/sourcegraph/sourcegraph/issues/52475.
Walkthrough
Code walkthrough time, because it's a fairly chunky diff! Let's start at the front of the front and loop our way around.
CreateGitHubAppPage- [code]BatchChangesCreateGitHubAppPage- [code]CreateGitHubAppPagefor specifically the BC App context. It provides a custom URL validation function to ensure the provided URL is from a unique host as well as sets the manifest details, page header, "batches" App domain, and base URL.CommitSigningIntegrationNode- [code]batches/resolvers/code_host.go- [code]idandbase_urlproperties to the GraphQL type. The former is needed for the mutation to delete the App, and the latter is needed to validate that the URL an admin inputs when creating a new App is unique.githubappauth/middleware.go- [code]https://sourcegraph.test:3443/.auth/githubapp/setup...and requiring them to manually get themselves back to Sourcegraph and figure out what to do next. Now if an error occurs, we will attempt to redirect the user back to the appropriate GitHub Apps site admin page and surface the exact error message in the UI. You can see this in action in the 2nd and 3rd failure path demo videos below.GitHubFailureAlert- [code]Demo videos
Success path, creating a GitHub App. I recorded creating for repos here, too, just to confirm nothing broke:
Screen.Recording.2023-06-07.at.12.16.48.AM.mov
Screen.Recording.2023-06-07.at.12.14.54.AM.mov
Failure path, where we failed to create the initial state to use for initiating the GitHub App creation process:
Screen.Recording.2023-06-06.at.11.51.07.PM.mov
Screen.Recording.2023-06-06.at.11.53.45.PM.mov
Failure path, where we got all the way through to validating the setup and recording the new installation, but failed before we could ascertain the domain that the new GitHub App was intended for:
Screen.Recording.2023-06-06.at.11.40.13.PM.mov
Screen.Recording.2023-06-06.at.11.57.54.PM.mov
Failure path, where we got all the way through to validating the setup and recording the new installation, but something went wrong at the very last step:
Screen.Recording.2023-06-06.at.11.44.09.PM.mov
Screen.Recording.2023-06-07.at.12.01.16.AM.mov
Test plan
Extensive manual testing with a variety of forced failure breakpoints. Middleware unit tests updated.