Skip to content
This repository has been archived by the owner on Jun 24, 2024. It is now read-only.

Import resiliency (3/3) #1385

Merged
merged 35 commits into from
Jul 12, 2018
Merged

Import resiliency (3/3) #1385

merged 35 commits into from
Jul 12, 2018

Conversation

BenEmdon
Copy link
Contributor

@BenEmdon BenEmdon commented Jul 10, 2018

What

This PR creates a new user flow that utilizes the new import resiliency changes made in #1368 and #1378 for the purpose of solving #1253.

This particular PR proposes a couple of significant additions:

  • Registers CreateGitHubRepositoryJob's and PorterStatusJob's queues with sidekiq
  • Configures ActionCable with our Redis setup
  • Added a status column to AssignmentInvitation
  • Introduces a new import_resiliency feature 🚩
  • The following routes on AssignmentInvitationController:
    • GET #progress - JSON api that returns the status of an invitation
    • POST #create_repo - if the invitation is accepted, kicks off a CreateGitHubRepositoryJob
    • GET #setupv2 - renders a live updating view of the repo creation progress

All of the new repo creation flow is hiding behind the import_resiliency feature 🚩

ActionCable

All the server work for ActionCable is done. The client side work will require some extra thought so I have left that work for a follow up PR since this is already an 🐘. Instead of ActionCable we are using short polling and hitting a GET #progress for now.

What if it errors?

Right now if it errors the UI will reflect the error state but I am yet to add retry button. There is a route ready for the retry button but I have a question about unique/locking background jobs.

What does it look like?

jul-10-2018 11-18-07
jul-10-2018 11-18-41

@BenEmdon BenEmdon requested a review from d12 July 10, 2018 21:39
if import_resiliency_enabled?
job_started = false
if current_invitation.accepted? || current_invitation.errored?
AssignmentRepo::CreateGitHubRepositoryJob.perform_later(current_assignment, current_user)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we ensure that the job is a unique job before we kick it off? Since jobs retry with exponential backoff we could end up in a situation where another job is kicked off while another is about to be retired.

Copy link
Contributor

Choose a reason for hiding this comment

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

If possible, yes.

Copy link
Contributor Author

@BenEmdon BenEmdon Jul 11, 2018

Choose a reason for hiding this comment

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

By default Sidekiq doesn't have this feature. It does in enterprise, but we aren't on that licensed plan.

Another option would be to use mhenrixon/sidekiq-unique-jobs. This would require us to move away from ActiveJob and to using straight Sidekiq.

Possibly worth investigating in a follow up PR.

RepositoryCreationStatusChannel.channel(user_id: user.id),
text: Creator::REPOSITORY_CREATION_COMPLETE,
status: assignment.invitation&.status
)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we be recording stats about successful repo creation on this vs the old way? if so using GitHubClassroom.statsd?

Copy link
Contributor

Choose a reason for hiding this comment

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

Definitely, we should be recording metrics wherever we think might help us be confident the new porter logic is better and more successful than the old one. I said this in an earlier comment, but we'll also want to have granular metrics so we can distinguish imports made using old logic and new logic.

- [deadline, 3]
- [boom, 3]
- [create_repository, 3]
- [porter_status, 3]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Worth mentioning that this many custom queues could be inefficient (#1381 )

Copy link
Contributor

@d12 d12 left a comment

Choose a reason for hiding this comment

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

I left some comments, mostly everything looks 👍, I think the biggest thing I was unsure about was this new create action. I left some inline comments, let me know what you think.

$.ajax({type: "GET", url: path}).done(function(data) {

display_progress(data);
setTimeout(check_progress, 3000);
Copy link
Contributor

Choose a reason for hiding this comment

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

The JS here looks good 👍

The only thing I'd suggest is abstracting the two uses of 3000 out to a constant at the top of the file so that if we want to adjust the polling rate in the future, we don't need to go through the whole file looking for where it is.

const path = progress_path();

$.ajax({type: "GET", url: path}).done(function(data) {

Copy link
Contributor

Choose a reason for hiding this comment

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

style nit, do we need a newline here?


start_job = function() {
const path = job_path();

Copy link
Contributor

Choose a reason for hiding this comment

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

Same style nit here

result = current_invitation.redeem_for(current_user, import_resiliency: import_resiliency_enabled?)
case result.status
when :success
GitHubClassroom.statsd.increment("exercise_invitation.accept")
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of re-using the old metric key here, what do you think about using a new one? v2_excercise_invitation.accept, for example.

This'll give us a little bit more granularity in our metrics when trying to see how the new flow is performing.

GitHubClassroom.statsd.increment("exercise_invitation.accept")
redirect_to setupv2_assignment_invitation_path
when :error
GitHubClassroom.statsd.increment("exercise_invitation.fail")
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment about metric granularity here

if import_resiliency_enabled?
job_started = false
if current_invitation.accepted? || current_invitation.errored?
AssignmentRepo::CreateGitHubRepositoryJob.perform_later(current_assignment, current_user)
Copy link
Contributor

Choose a reason for hiding this comment

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

If possible, yes.

@@ -19,32 +19,25 @@ class CreateGitHubRepositoryJob < ApplicationJob
def perform(assignment, user)
start = Time.zone.now

assignment.invitation&.creating_repo!
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is this method defined? I wasn't able to find it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah it's an enum helper, nevermind!

RepositoryCreationStatusChannel.channel(user_id: user.id),
text: Creator::REPOSITORY_CREATION_COMPLETE,
status: assignment.invitation&.status
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Definitely, we should be recording metrics wherever we think might help us be confident the new porter logic is better and more successful than the old one. I said this in an earlier comment, but we'll also want to have granular metrics so we can distinguish imports made using old logic and new logic.

#
# rubocop:disable MethodLength
# rubocop:disable AbcSize
def redeem_for(invitee, import_resiliency: false)
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of using a kwarg here, can we just directly check the feature flag?

Copy link
Contributor Author

@BenEmdon BenEmdon Jul 11, 2018

Choose a reason for hiding this comment

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

I can't use the feature dependency here because we are outside of a controller (feature 🚩s are defined on ApplicationController).

@@ -0,0 +1,5 @@
class AddStatusToAssignmentInvitation < ActiveRecord::Migration[5.1]
def change
add_column :assignment_invitations, :status, :integer
Copy link
Contributor

Choose a reason for hiding this comment

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

The migration will need to be split into it's own PR, would you mind doing that and pinging me on it? The diff should be this file + the schema diff and nothing else.

@d12
Copy link
Contributor

d12 commented Jul 11, 2018

Also one other thing I'd suggest, this PR is pretty big and it's hard to review and be sure it's 100% correct. If it's possible in the future, I'd recommend breaking PRs into more atomic, smaller changes 😅

.and_return(result)

patch :accept, params: { id: invitation.key }
end

Choose a reason for hiding this comment

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

Layout/BlockAlignment: end at 143, 4 is not aligned with it "sends an event to statsd" do at 135, 6.

@BenEmdon
Copy link
Contributor Author

Update

Things to do in followup PRs

  • Fix concurrency issue with CreateGitHubRepositoryJob
  • Add support for group assignments
  • Support ActionCable instead of short polling

Can I get some final 👀 on this? @d12

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants