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

Organization onboarding #5201

Merged
merged 93 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
f37c948
Add initial Onboarding functionality
colby-swandale Sep 25, 2024
80d844f
add onboarding controllers
colby-swandale Oct 17, 2024
f835d0d
better test & confirm the organization hsa been created
colby-swandale Oct 23, 2024
48802be
Add user login helper
colby-swandale Oct 24, 2024
aa198b4
Allow running system tests in devcontainers
colby-swandale Oct 31, 2024
8cea832
Add Name Type enum to OrganizationOnboarding
colby-swandale Nov 4, 2024
855ea95
Add OrganizationOnboardingInvite to capture users invited to join an …
colby-swandale Nov 4, 2024
c0fbcc7
Organization onboarding Name form, allow deleteing onboarding
martinemde Nov 3, 2024
6e672e9
Improve gem ownership validation
colby-swandale Nov 6, 2024
c7f6118
Set default name type value
colby-swandale Nov 6, 2024
5f1f42f
Onboarding Gems, Members, and Finalize design (mobile only)
martinemde Nov 5, 2024
4d7fa31
Change order of membership roles to be ordered
martinemde Nov 6, 2024
46b5544
wip on tests
martinemde Nov 7, 2024
31e3752
rely on fields_for
martinemde Nov 7, 2024
f6e7cb5
add necessary fields
martinemde Nov 7, 2024
4ba1c73
Allow null role in onboarding invites
martinemde Nov 8, 2024
4ca4188
Working user onboarding controller
martinemde Nov 8, 2024
8252cdc
Start fixing system test for onboarding
martinemde Nov 8, 2024
abf5433
improve onboarding views
martinemde Nov 6, 2024
b2dc5b6
Fix syntax error
martinemde Nov 8, 2024
fcdbfdf
simplify tests and user onboarding
martinemde Nov 8, 2024
9e4b583
little updates
martinemde Nov 8, 2024
8341887
Fix gems controller for submitting no gems
martinemde Nov 8, 2024
a300865
Add the promo for organizations to the dashboard
martinemde Nov 8, 2024
163e68c
resolve breaking tests & add new system tests
colby-swandale Nov 8, 2024
535f4af
Move onboarding underneath the organization namespace
colby-swandale Nov 9, 2024
0a23a51
Fix promo link
martinemde Nov 9, 2024
51fb44e
Fix team model tests
martinemde Nov 9, 2024
86b4ce1
Change pathname helpers for onboarding to singular
martinemde Nov 9, 2024
8406cc8
Fix foreign_key in User.has_many :organization_onboardings
martinemde Nov 9, 2024
c1bc1a4
Fix route plural controller name
martinemde Nov 10, 2024
87fbee5
Fix controller nesting problem
martinemde Nov 11, 2024
c050bb1
restore TestHelper capyabra configuration
colby-swandale Nov 11, 2024
c291844
temporary not found for organizations show page
martinemde Nov 11, 2024
f7c163f
Fixup failing Factories Tests
colby-swandale Nov 11, 2024
f373838
Fix name error from old Avo 2 code
colby-swandale Nov 11, 2024
40eedc5
Remvoe test that is no longer valid
colby-swandale Nov 11, 2024
db8dce8
Remove debug gem
colby-swandale Nov 11, 2024
44c65f9
Add test coverage for organziation/onboarding controller
martinemde Nov 11, 2024
dbda465
Handle failed onboarding
martinemde Nov 11, 2024
f5359fd
Adding missing pundit policies for OrganizationOnboarding, Team & Tea…
colby-swandale Nov 11, 2024
53a0ac3
Rewrite test for onboarding with user name type
colby-swandale Nov 11, 2024
69c0483
Remove rubygem memorization
colby-swandale Nov 11, 2024
a898572
Fix routing tests
colby-swandale Nov 11, 2024
9bd0ee5
fix linting
colby-swandale Nov 11, 2024
cf65a5c
Add admin pundit policies for onboarding, team & team member
colby-swandale Nov 11, 2024
5ed61e5
Test confirm action more thoroughly
martinemde Nov 11, 2024
86a290c
Add helper method for add_breadcrumbs
martinemde Nov 11, 2024
8745422
Remove Ownership record of onbarded gems and users
colby-swandale Nov 11, 2024
68058b7
Fill in missing coverage in policy
martinemde Nov 11, 2024
8f277ba
One more policy coverage
martinemde Nov 11, 2024
caf2407
Remove unused teams for now
martinemde Nov 11, 2024
f3b6626
Protect from invalid input in invites
martinemde Nov 11, 2024
9bf8ec6
Update locale files
colby-swandale Nov 11, 2024
7c0b335
OrganizationOnboarding#available_rubygems test coverage
martinemde Nov 11, 2024
704c321
Remove teams migrations
martinemde Nov 11, 2024
d140ce8
During launch we will only allow gem name onboarding
martinemde Nov 11, 2024
caf707f
Use a helper to avoid loading approved_invites twice
martinemde Nov 11, 2024
d953966
username onboarding type is invalid for now
martinemde Nov 12, 2024
fea19c6
really fix onboarding factory
martinemde Nov 12, 2024
b4d7c05
remove new design notice from onboarding
martinemde Nov 12, 2024
a2c964e
Remove team and team member models
colby-swandale Nov 12, 2024
5b07de5
Ammend gem push permissions to include users who are part of an organ…
colby-swandale Nov 12, 2024
22f2988
Only hide username option
martinemde Nov 13, 2024
b5366bf
Remove accidental page addition
martinemde Nov 13, 2024
793a4d2
Remove onboarded_by field
colby-swandale Nov 13, 2024
f5c8811
Update Organization Onboard avo resources with fields and actions
colby-swandale Nov 13, 2024
e01aae0
Update Owner to be top level role
colby-swandale Nov 13, 2024
dad5a4b
Update params name
colby-swandale Nov 13, 2024
4285cd6
Remove leftover teams
colby-swandale Nov 13, 2024
4c905d0
Merge null migration into previous migration
colby-swandale Nov 13, 2024
71c12ce
Fixup Onboard Organization Action
colby-swandale Nov 13, 2024
550a25c
Fixup locales
colby-swandale Nov 13, 2024
c0d4530
Raise exception when onboarding fails
colby-swandale Nov 13, 2024
cd0a681
Fixup rubocop errors
colby-swandale Nov 13, 2024
b1b2cd7
Set the correct access permissions order
colby-swandale Nov 13, 2024
13acb03
Tweak integration tests
colby-swandale Nov 13, 2024
9cfae25
define OrganizationOnboardingInvite policy object
colby-swandale Nov 13, 2024
6483fd8
update locales
colby-swandale Nov 13, 2024
b9c5bca
Fixup breaking access tests
colby-swandale Nov 13, 2024
beb4ce2
touchup database migrations
colby-swandale Nov 13, 2024
d2232aa
Update Organization policies with correct access levels
colby-swandale Nov 13, 2024
8d7177a
Add org role checking to pundit policy
martinemde Nov 13, 2024
33691a0
Organization policies and better policy errors
martinemde Nov 13, 2024
19b3763
Passing org and membership policies
martinemde Nov 14, 2024
732acb3
Set maintainer role for trait
colby-swandale Nov 14, 2024
baed0a2
Rename :handle in organizations route to default :id
martinemde Nov 14, 2024
10857b7
trim created_by_gem_ownerships validations to pass CodeCov
colby-swandale Nov 14, 2024
29ec7b1
add orange-050
martinemde Nov 13, 2024
75d5246
remove routes that will be added later
martinemde Nov 14, 2024
b17a9e8
Add missing tests
colby-swandale Nov 14, 2024
b3c7f03
Update breaking test
colby-swandale Nov 14, 2024
5e639e8
Fixup nil in test
colby-swandale Nov 14, 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
1 change: 1 addition & 0 deletions app/assets/images/icons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions app/avo/actions/onboard_organization.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class Avo::Actions::OnboardOrganization < Avo::Actions::ApplicationAction
self.name = "Onboard Organization"
self.visible = lambda {
current_user.team_member?("rubygems-org") && view == :show
}

self.message = lambda {
"Are you sure you would like to onboard this organization?"
}

self.confirm_button_label = "Onboard"

def handle(query:, **_)
query.each(&:onboard!)
end
end
27 changes: 27 additions & 0 deletions app/avo/resources/organization_onboarding.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class Avo::Resources::OrganizationOnboarding < Avo::BaseResource
self.title = :organization_name
self.includes = [:invites]

def actions
action Avo::Actions::OnboardOrganization
end

def fields
field :id, as: :id
field :status, as: :select, enum: OrganizationOnboarding.statuses
field :organization_name, as: :text
field :organization_handle, as: :text
field :created_by, as: :belongs_to
field :error, as: :text

field :onboarded_at, as: :date_time
field :created_at, as: :date_time
field :updated_at, as: :date_time

tabs style: :pills do
field :users, as: :has_many, through: :invites
field :invites, as: :has_many, use_resource: Avo::Resources::OrganizationOnboardingInvite
field :organization, as: :has_one
end
end
end
11 changes: 11 additions & 0 deletions app/avo/resources/organization_onboarding_invite.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Avo::Resources::OrganizationOnboardingInvite < Avo::BaseResource
self.title = :id
self.includes = [:organization_onboarding]

def fields
field :id, as: :id
field :organization_onboarding, as: :belongs_to
field :user, as: :belongs_to
field :role, as: :select, enum: OrganizationOnboardingInvite.roles
end
end
1 change: 1 addition & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def breadcrumbs
def add_breadcrumb(name, link = nil)
breadcrumbs << [name, link]
end
helper_method :add_breadcrumb

protected

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# This controller has been generated to enable Rails' resource routes.
# More information on https://docs.avohq.io/2.0/controllers.html
class Avo::OrganizationOnboardingInvitesController < Avo::ResourcesController
end
4 changes: 4 additions & 0 deletions app/controllers/avo/organization_onboardings_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# This controller has been generated to enable Rails' resource routes.
# More information on https://docs.avohq.io/2.0/controllers.html
class Avo::OrganizationOnboardingsController < Avo::ResourcesController
end
28 changes: 28 additions & 0 deletions app/controllers/organizations/onboarding/base_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class Organizations::Onboarding::BaseController < ApplicationController
before_action :redirect_to_signin, unless: :signed_in?
before_action :redirect_to_new_mfa, if: :mfa_required_not_yet_enabled?
before_action :find_or_initialize_onboarding
before_action :set_breadcrumbs

def find_or_initialize_onboarding
@organization_onboarding = OrganizationOnboarding.find_or_initialize_by(created_by: Current.user, status: :pending)
end

def set_breadcrumbs
add_breadcrumb t("breadcrumbs.dashboard"), dashboard_path
add_breadcrumb "Create Org"
end

def available_rubygems
@available_rubygems ||= @organization_onboarding.available_rubygems.to_a.tap do |gems|
namesake_rubygem = @organization_onboarding.namesake_rubygem
gems.unshift gems.delete(namesake_rubygem) if namesake_rubygem
end
end
helper_method :available_rubygems

def approved_invites
@approved_invites ||= @organization_onboarding.approved_invites
end
helper_method :approved_invites
end
16 changes: 16 additions & 0 deletions app/controllers/organizations/onboarding/confirm_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class Organizations::Onboarding::ConfirmController < Organizations::Onboarding::BaseController
layout "onboarding"

def edit
end

def update
@organization_onboarding.onboard!

flash[:notice] = I18n.t("organization_onboardings.confirm.success")
redirect_to organization_path(@organization_onboarding.organization)
rescue ActiveRecord::ActiveRecordError
flash.now[:error] = "Onboarding error: #{@organization_onboarding.error}"
render :edit, status: :unprocessable_entity
end
end
20 changes: 20 additions & 0 deletions app/controllers/organizations/onboarding/gems_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class Organizations::Onboarding::GemsController < Organizations::Onboarding::BaseController
layout "onboarding"

def edit
end

def update
if @organization_onboarding.update(onboarding_gems_params)
redirect_to organization_onboarding_users_path
else
render :edit, status: :unprocessable_entity
end
end

private

def onboarding_gems_params
params.permit(organization_onboarding: { rubygems: [] }).fetch(:organization_onboarding, {})
end
end
20 changes: 20 additions & 0 deletions app/controllers/organizations/onboarding/name_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class Organizations::Onboarding::NameController < Organizations::Onboarding::BaseController
layout "onboarding"

def new
end

def create
if @organization_onboarding.update(onboarding_name_params)
redirect_to organization_onboarding_gems_path
else
render :new, status: :unprocessable_entity
end
end

private

def onboarding_name_params
params.require(:organization_onboarding).permit(:organization_name, :organization_handle)
end
end
27 changes: 27 additions & 0 deletions app/controllers/organizations/onboarding/users_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class Organizations::Onboarding::UsersController < Organizations::Onboarding::BaseController
layout "onboarding"

def edit
end

def update
if @organization_onboarding.update(onboarding_user_params)
redirect_to organization_onboarding_confirm_path
else
render :edit, status: :unprocessable_entity

Check warning on line 11 in app/controllers/organizations/onboarding/users_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/organizations/onboarding/users_controller.rb#L11

Added line #L11 was not covered by tests
end
end

private

def role_options
@role_options ||= OrganizationOnboardingInvite.roles.map do |k, _|
[Membership.human_attribute_name("role.#{k}"), k]
end
end
helper_method :role_options

def onboarding_user_params
params.require(:organization_onboarding).permit(invites_attributes: %i[id role])
end
end
10 changes: 10 additions & 0 deletions app/controllers/organizations/onboarding_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class Organizations::OnboardingController < ApplicationController
before_action :redirect_to_signin, unless: :signed_in?
before_action :redirect_to_new_mfa, if: :mfa_required_not_yet_enabled?

def destroy
OrganizationOnboarding.destroy_by(created_by: Current.user, status: %i[pending failed])

redirect_to dashboard_path
end
end
10 changes: 10 additions & 0 deletions app/controllers/organizations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class OrganizationsController < ApplicationController
def show
render plain: flash[:notice] # HACK: for tests until this view is ready
end

private

def organization_params
end
end
6 changes: 6 additions & 0 deletions app/controllers/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ def webauthn_authenticate
end
end

def development_log_in_as
user = User.find(params[:user_id])
sign_in(user)
redirect_back_or_to dashboard_path
end

private

def mark_verified
Expand Down
2 changes: 2 additions & 0 deletions app/javascript/controllers/application.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Application } from "@hotwired/stimulus"
import Clipboard from '@stimulus-components/clipboard'
import CheckboxSelectAll from '@stimulus-components/checkbox-select-all'

const application = Application.start()

// Add vendored controllers
application.register('clipboard', Clipboard)
application.register('checkbox-select-all', CheckboxSelectAll)

// Configure Stimulus development experience
application.debug = false
Expand Down
24 changes: 24 additions & 0 deletions app/javascript/controllers/counter_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = ["counter", "checked"]

connect() {
this.update()
}

checkedTargetConnected(el) {
el.addEventListener("change", this.update.bind(this))
el.addEventListener("input", this.update.bind(this)) // input emitted by checkbox-select-all controller
}

checkedTargetdisconnected(el) {
el.removeEventListener("change", this.update.bind(this))
el.removeEventListener("input", this.update.bind(this))
}

update() {
const count = this.checkedTargets.filter(el => el.checked).length
this.counterTarget.textContent = count
}
}
74 changes: 74 additions & 0 deletions app/javascript/controllers/onboarding_name_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = [
"radio",
"gemname",
"username",
"reveal",
"displayname",
"submit",
]

connect() {
this.submitTarget.disabled = true
this.radioTargets.forEach((radio) => {
if (radio.checked) {
this[radio.value+"name"]()
}
})
}

gemnameField() {
return this.gemnameTarget.querySelector("select")
}

usernameField() {
return this.usernameTarget.querySelector("input")
}

gemname() {
this.usernameTarget.classList.add("hidden")

this.gemnameField().disabled = false
this.gemnameTarget.classList.remove("hidden")
this.revealTarget.classList.remove("hidden")

const inputElement = this.gemnameField()
inputElement.focus()
this.updateDisplaynameWith(inputElement.value)
if (inputElement.value === "") {
this.submitTarget.disabled = true
}
this.validate()
}

username() {
this.gemnameTarget.classList.add("hidden")
this.gemnameField().disabled = true

this.usernameTarget.classList.remove("hidden")
this.revealTarget.classList.remove("hidden")

this.updateDisplaynameWith(this.usernameField().value)
this.validate()
}

validate() {
if (this.element.checkValidity()) {
this.submitTarget.disabled = false
} else {
this.submitTarget.disabled = true
}
}

updateDisplayname(e) {
this.updateDisplaynameWith(e.currentTarget.value)
this.validate()
}

updateDisplaynameWith(value) {
// Replace dashes and underscores with spaces. Capitalize the first letter of each word.
this.displaynameTarget.value = value.replace(/[-_]/g, " ").replace(/\b\w/g, (char) => char.toUpperCase())
}
}
25 changes: 25 additions & 0 deletions app/javascript/controllers/radio_reveal_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = [
"radio",
"item",
]

connect() {
this.update()
}

update() {
this.itemTargets.forEach(item => {
item.classList.add("hidden")
})

this.radioTargets.forEach(radio => {
if (radio.checked) {
const item = this.itemTargets.find(item => item.dataset.name == radio.value)
item.classList.remove("hidden")
}
})
}
}
4 changes: 4 additions & 0 deletions app/javascript/controllers/scroll_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ export default class extends Controller {
scrollTargetConnected() {
this.scrollTarget.scrollIntoView({ behavior: "smooth" })
}

scroll(e) {
e.currentTarget.scrollIntoView({ behavior: "smooth" })
}
}
4 changes: 3 additions & 1 deletion app/models/membership.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ class Membership < ApplicationRecord
scope :unconfirmed, -> { where(confirmed_at: nil) }
scope :confirmed, -> { where.not(confirmed_at: nil) }

enum :role, { owner: Access::OWNER, maintainer: Access::MAINTAINER, admin: Access::ADMIN }, validate: true, default: :maintainer
enum :role, { owner: Access::OWNER, admin: Access::ADMIN, maintainer: Access::MAINTAINER }, validate: true, default: :maintainer

scope :with_minimum_role, ->(role) { where(role: Access.flag_for_role(role)...) }

def confirmed?
!confirmed_at.nil?
Expand Down
Loading
Loading