Commit 144094bc authored by Andreas Brandl's avatar Andreas Brandl

Merge branch...

Merge branch '294127-follow-up-investigate-whether-we-should-convert-namespace_onboarding_actions-table' into 'master'

Refactor NamespaceOnboardingAction model to OnboardingProgress

See merge request gitlab-org/gitlab!50711
parents 41a7b3e2 4304fcc1
......@@ -28,7 +28,7 @@ class Namespace < ApplicationRecord
has_many :runner_namespaces, inverse_of: :namespace, class_name: 'Ci::RunnerNamespace'
has_many :runners, through: :runner_namespaces, source: :runner, class_name: 'Ci::Runner'
has_many :namespace_onboarding_actions
has_one :onboarding_progress
# This should _not_ be `inverse_of: :namespace`, because that would also set
# `user.namespace` when this user creates a group with themselves as `owner`.
......
# frozen_string_literal: true
class NamespaceOnboardingAction < ApplicationRecord
belongs_to :namespace, optional: false
validates :action, presence: true
ACTIONS = {
subscription_created: 1,
git_write: 2,
merge_request_created: 3,
git_read: 4,
pipeline_created: 5,
user_added: 6
}.freeze
# The monitoring window prevents the recording of a namespace_onboarding_action if a namespace is created before this
# time span. We are not interested in older namspaces, because the purpose of this table is to monitor and act on the
# progress of newly created namespaces or namespaces that already have at least one recorded action.
MONITORING_WINDOW = 90.days
enum action: ACTIONS
class << self
def completed?(namespace, action)
where(namespace: namespace, action: action).exists?
end
def create_action(namespace, action)
return unless namespace.root?
return if namespace.created_at < MONITORING_WINDOW.ago && !namespace.namespace_onboarding_actions.exists?
NamespaceOnboardingAction.safe_find_or_create_by(namespace: namespace, action: action)
end
end
end
# frozen_string_literal: true
class OnboardingProgress < ApplicationRecord
belongs_to :namespace, optional: false
validate :namespace_is_root_namespace
ACTIONS = [
:git_pull,
:git_write,
:merge_request_created,
:pipeline_created,
:user_added,
:trial_started,
:subscription_created,
:required_mr_approvals_enabled,
:code_owners_enabled,
:scoped_label_created,
:security_scan_enabled,
:issue_auto_closed,
:repository_imported,
:repository_mirrored
].freeze
class << self
def onboard(namespace)
return unless root_namespace?(namespace)
safe_find_or_create_by(namespace: namespace)
end
def register(namespace, action)
return unless root_namespace?(namespace) && ACTIONS.include?(action)
action_column = column_name(action)
onboarding_progress = find_by(namespace: namespace, action_column => nil)
onboarding_progress&.update!(action_column => Time.current)
end
def completed?(namespace, action)
return unless root_namespace?(namespace) && ACTIONS.include?(action)
action_column = column_name(action)
where(namespace: namespace).where.not(action_column => nil).exists?
end
private
def column_name(action)
:"#{action}_at"
end
def root_namespace?(namespace)
namespace && namespace.root?
end
end
private
def namespace_is_root_namespace
return unless namespace
errors.add(:namespace, _('must be a root namespace')) if namespace.has_parent?
end
end
......@@ -35,6 +35,7 @@ module Groups
@group.add_owner(current_user)
@group.create_namespace_settings
Service.create_from_active_default_integrations(@group, :group_id)
OnboardingProgress.onboard(@group)
end
end
......
......@@ -13,7 +13,7 @@ module MergeRequests
merge_request.diffs(include_stats: false).write_cache
merge_request.create_cross_references!(current_user)
NamespaceOnboardingAction.create_action(merge_request.target_project.namespace, :merge_request_created)
OnboardingProgressService.new(merge_request.target_project.namespace).execute(action: :merge_request_created)
end
end
end
......
......@@ -8,6 +8,6 @@ class OnboardingProgressService
def execute(action:)
return unless @namespace
NamespaceOnboardingAction.create_action(@namespace, action)
OnboardingProgress.register(@namespace, action)
end
end
......@@ -94,7 +94,7 @@ class PostReceiveService
end
def record_onboarding_progress
NamespaceOnboardingAction.create_action(project.namespace, :git_write)
OnboardingProgressService.new(project.namespace).execute(action: :git_write)
end
end
......
---
title: Change onboarding actions table to use one record per namespace
merge_request: 50711
author:
type: changed
# frozen_string_literal: true
class CreateOnboardingProgress < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
with_lock_retries do
create_table :onboarding_progresses do |t|
t.references :namespace, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade }
t.timestamps_with_timezone null: false
t.datetime_with_timezone :git_pull_at
t.datetime_with_timezone :git_write_at
t.datetime_with_timezone :merge_request_created_at
t.datetime_with_timezone :pipeline_created_at
t.datetime_with_timezone :user_added_at
t.datetime_with_timezone :trial_started_at
t.datetime_with_timezone :subscription_created_at
t.datetime_with_timezone :required_mr_approvals_enabled_at
t.datetime_with_timezone :code_owners_enabled_at
t.datetime_with_timezone :scoped_label_created_at
t.datetime_with_timezone :security_scan_enabled_at
t.datetime_with_timezone :issue_auto_closed_at
t.datetime_with_timezone :repository_imported_at
t.datetime_with_timezone :repository_mirrored_at
end
end
end
def down
with_lock_retries do
drop_table :onboarding_progresses
end
end
end
c2766b50914c6b4d8c96fb958cdfb67f0d29e40df45654c35d62792c272e3d5a
\ No newline at end of file
......@@ -14491,6 +14491,36 @@ CREATE SEQUENCE oauth_openid_requests_id_seq
ALTER SEQUENCE oauth_openid_requests_id_seq OWNED BY oauth_openid_requests.id;
CREATE TABLE onboarding_progresses (
id bigint NOT NULL,
namespace_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
git_pull_at timestamp with time zone,
git_write_at timestamp with time zone,
merge_request_created_at timestamp with time zone,
pipeline_created_at timestamp with time zone,
user_added_at timestamp with time zone,
trial_started_at timestamp with time zone,
subscription_created_at timestamp with time zone,
required_mr_approvals_enabled_at timestamp with time zone,
code_owners_enabled_at timestamp with time zone,
scoped_label_created_at timestamp with time zone,
security_scan_enabled_at timestamp with time zone,
issue_auto_closed_at timestamp with time zone,
repository_imported_at timestamp with time zone,
repository_mirrored_at timestamp with time zone
);
CREATE SEQUENCE onboarding_progresses_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE onboarding_progresses_id_seq OWNED BY onboarding_progresses.id;
CREATE TABLE open_project_tracker_data (
id bigint NOT NULL,
service_id integer NOT NULL,
......@@ -18715,6 +18745,8 @@ ALTER TABLE ONLY oauth_applications ALTER COLUMN id SET DEFAULT nextval('oauth_a
ALTER TABLE ONLY oauth_openid_requests ALTER COLUMN id SET DEFAULT nextval('oauth_openid_requests_id_seq'::regclass);
ALTER TABLE ONLY onboarding_progresses ALTER COLUMN id SET DEFAULT nextval('onboarding_progresses_id_seq'::regclass);
ALTER TABLE ONLY open_project_tracker_data ALTER COLUMN id SET DEFAULT nextval('open_project_tracker_data_id_seq'::regclass);
ALTER TABLE ONLY operations_feature_flag_scopes ALTER COLUMN id SET DEFAULT nextval('operations_feature_flag_scopes_id_seq'::regclass);
......@@ -20052,6 +20084,9 @@ ALTER TABLE ONLY oauth_applications
ALTER TABLE ONLY oauth_openid_requests
ADD CONSTRAINT oauth_openid_requests_pkey PRIMARY KEY (id);
ALTER TABLE ONLY onboarding_progresses
ADD CONSTRAINT onboarding_progresses_pkey PRIMARY KEY (id);
ALTER TABLE ONLY open_project_tracker_data
ADD CONSTRAINT open_project_tracker_data_pkey PRIMARY KEY (id);
......@@ -22254,6 +22289,8 @@ CREATE INDEX index_on_users_lower_username ON users USING btree (lower((username
CREATE INDEX index_on_users_name_lower ON users USING btree (lower((name)::text));
CREATE UNIQUE INDEX index_onboarding_progresses_on_namespace_id ON onboarding_progresses USING btree (namespace_id);
CREATE INDEX index_open_project_tracker_data_on_service_id ON open_project_tracker_data USING btree (service_id);
CREATE INDEX index_operations_feature_flags_issues_on_issue_id ON operations_feature_flags_issues USING btree (issue_id);
......@@ -24465,6 +24502,9 @@ ALTER TABLE ONLY geo_repository_updated_events
ALTER TABLE ONLY boards_epic_board_labels
ADD CONSTRAINT fk_rails_2bedeb8799 FOREIGN KEY (label_id) REFERENCES labels(id) ON DELETE CASCADE;
ALTER TABLE ONLY onboarding_progresses
ADD CONSTRAINT fk_rails_2ccfd420cc FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY protected_branch_unprotect_access_levels
ADD CONSTRAINT fk_rails_2d2aba21ef FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
......
......@@ -24,7 +24,7 @@ module Subscriptions
response = client.create_subscription(create_subscription_params, billing_email, token)
NamespaceOnboardingAction.create_action(@group, :subscription_created) if response[:success]
OnboardingProgressService.new(@group).execute(action: :subscription_created) if response[:success]
response
end
......
......@@ -66,10 +66,12 @@ RSpec.describe Subscriptions::CreateService do
expect(subject.execute).to eq(success: false, data: { errors: 'failed to create subscription' })
end
it 'does not create a namespace onboarding action' do
it 'does not register a namespace onboarding progress action' do
OnboardingProgress.onboard(group)
subject.execute
expect(NamespaceOnboardingAction.completed?(group, :subscription_created)).to eq(false)
expect(OnboardingProgress.completed?(group, :subscription_created)).to eq(false)
end
end
......@@ -102,10 +104,12 @@ RSpec.describe Subscriptions::CreateService do
subject.execute
end
it 'creates a namespace onboarding action' do
it 'registers a namespace onboarding progress action' do
OnboardingProgress.onboard(group)
subject.execute
expect(NamespaceOnboardingAction.completed?(group, :subscription_created)).to eq(true)
expect(OnboardingProgress.completed?(group, :subscription_created)).to eq(true)
end
end
end
......
......@@ -52,7 +52,7 @@ RSpec.describe Repositories::GitHttpController do
}.from(0).to(1)
end
it 'records a namespace onboarding progress action' do
it 'records an onboarding progress action' do
expect_next_instance_of(OnboardingProgressService) do |service|
expect(service).to receive(:execute).with(action: :git_read)
end
......
# frozen_string_literal: true
FactoryBot.define do
factory :namespace_onboarding_action do
factory :onboarding_progress do
namespace
action { :subscription_created }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe NamespaceOnboardingAction do
let(:namespace) { create(:namespace) }
describe 'associations' do
it { is_expected.to belong_to(:namespace).required }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:action) }
end
describe '.completed?' do
let(:action) { :subscription_created }
subject { described_class.completed?(namespace, action) }
context 'action created for the namespace' do
before do
create(:namespace_onboarding_action, namespace: namespace, action: action)
end
it { is_expected.to eq(true) }
end
context 'action created for another namespace' do
before do
create(:namespace_onboarding_action, namespace: build(:namespace), action: action)
end
it { is_expected.to eq(false) }
end
end
describe '.create_action' do
let(:action) { :subscription_created }
subject(:create_action) { described_class.create_action(namespace, action) }
it 'creates the action for the namespace just once' do
expect { create_action }.to change { count_namespace_actions }.by(1)
expect { create_action }.to change { count_namespace_actions }.by(0)
end
context 'when the namespace is created outside the monitoring window' do
let(:namespace) { create(:namespace, created_at: (NamespaceOnboardingAction::MONITORING_WINDOW + 1.day).ago) }
it 'does not create an action for the namespace' do
expect { create_action }.not_to change { count_namespace_actions }
end
context 'when an action has already been recorded for the namespace' do
before do
described_class.create!(namespace: namespace, action: :git_write)
end
it 'creates an action for the namespace' do
expect { create_action }.to change { count_namespace_actions }.by(1)
end
end
end
context 'when the namespace is not a root' do
let(:namespace) { create(:namespace, parent: build(:namespace)) }
it 'does not create an action for the namespace' do
expect { create_action }.not_to change { count_namespace_actions }
end
end
def count_namespace_actions
described_class.where(namespace: namespace, action: action).count
end
end
end
......@@ -19,8 +19,8 @@ RSpec.describe Namespace do
it { is_expected.to have_one :aggregation_schedule }
it { is_expected.to have_one :namespace_settings }
it { is_expected.to have_many :custom_emoji }
it { is_expected.to have_many :namespace_onboarding_actions }
it { is_expected.to have_one :package_setting_relation }
it { is_expected.to have_one :onboarding_progress }
end
describe 'validations' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe OnboardingProgress do
let(:namespace) { create(:namespace) }
let(:action) { :subscription_created }
describe 'associations' do
it { is_expected.to belong_to(:namespace).required }
end
describe 'validations' do
describe 'namespace_is_root_namespace' do
subject(:onboarding_progress) { build(:onboarding_progress, namespace: namespace)}
context 'when associated namespace is root' do
it { is_expected.to be_valid }
end
context 'when associated namespace is not root' do
let(:namespace) { build(:group, :nested) }
it 'is invalid' do
expect(onboarding_progress).to be_invalid
expect(onboarding_progress.errors[:namespace]).to include('must be a root namespace')
end
end
end
end
describe '.onboard' do
subject(:onboard) { described_class.onboard(namespace) }
it 'adds a record for the namespace' do
expect { onboard }.to change(described_class, :count).from(0).to(1)
end
context 'when not given a namespace' do
let(:namespace) { nil }
it 'does not add a record for the namespace' do
expect { onboard }.not_to change(described_class, :count).from(0)
end
end
context 'when not given a root namespace' do
let(:namespace) { create(:namespace, parent: build(:namespace)) }
it 'does not add a record for the namespace' do
expect { onboard }.not_to change(described_class, :count).from(0)
end
end
end
describe '.register' do
subject(:register_action) { described_class.register(namespace, action) }
context 'when the namespace was onboarded' do
before do
described_class.onboard(namespace)
end
it 'registers the action for the namespace' do
expect { register_action }.to change { described_class.completed?(namespace, action) }.from(false).to(true)
end
context 'when the action does not exist' do
let(:action) { :foo }
it 'does not register the action for the namespace' do
expect { register_action }.not_to change { described_class.completed?(namespace, action) }.from(nil)
end
end
end
context 'when the namespace was not onboarded' do
it 'does not register the action for the namespace' do
expect { register_action }.not_to change { described_class.completed?(namespace, action) }.from(false)
end
end
end
describe '.completed?' do
subject { described_class.completed?(namespace, action) }
context 'when the namespace has not yet been onboarded' do
it { is_expected.to eq(false) }
end
context 'when the namespace has been onboarded but not registered the action yet' do
before do
described_class.onboard(namespace)
end
it { is_expected.to eq(false) }
context 'when the action has been registered' do
before do
described_class.register(namespace, action)
end
it { is_expected.to eq(true) }
end
end
end
end
......@@ -63,6 +63,10 @@ RSpec.describe Groups::CreateService, '#execute' do
end
it { is_expected.to be_persisted }
it 'adds an onboarding progress record' do
expect { subject }.to change(OnboardingProgress, :count).from(0).to(1)
end
end
context 'when user can not create a group' do
......@@ -84,6 +88,10 @@ RSpec.describe Groups::CreateService, '#execute' do
end
it { is_expected.to be_persisted }
it 'does not add an onboarding progress record' do
expect { subject }.not_to change(OnboardingProgress, :count).from(0)
end
end
context 'as guest' do
......
......@@ -13,14 +13,20 @@ RSpec.describe Members::CreateService, :clean_gitlab_redis_shared_state, :sideki
subject(:execute_service) { described_class.new(user, params).execute(source) }
before do
source.is_a?(Project) ? source.add_maintainer(user) : source.add_owner(user)
if source.is_a?(Project)
source.add_maintainer(user)
OnboardingProgress.onboard(source.namespace)
else
source.add_owner(user)
OnboardingProgress.onboard(source)
end
end
context 'when passing valid parameters' do
it 'adds a user to members' do
expect(execute_service[:status]).to eq(:success)
expect(source.users).to include member
expect(NamespaceOnboardingAction.completed?(source.namespace, :user_added)).to be(true)
expect(OnboardingProgress.completed?(source.namespace, :user_added)).to be(true)
end
context 'when executing on a group' do
......@@ -29,7 +35,7 @@ RSpec.describe Members::CreateService, :clean_gitlab_redis_shared_state, :sideki
it 'adds a user to members' do
expect(execute_service[:status]).to eq(:success)
expect(source.users).to include member
expect(NamespaceOnboardingAction.completed?(source, :user_added)).to be(true)
expect(OnboardingProgress.completed?(source, :user_added)).to be(true)
end
end
end
......@@ -41,7 +47,7 @@ RSpec.describe Members::CreateService, :clean_gitlab_redis_shared_state, :sideki
expect(execute_service[:status]).to eq(:error)
expect(execute_service[:message]).to be_present
expect(source.users).not_to include member
expect(NamespaceOnboardingAction.completed?(source.namespace, :user_added)).to be(false)
expect(OnboardingProgress.completed?(source.namespace, :user_added)).to be(false)
end
end
......@@ -52,7 +58,7 @@ RSpec.describe Members::CreateService, :clean_gitlab_redis_shared_state, :sideki
expect(execute_service[:status]).to eq(:error)
expect(execute_service[:message]).to be_present
expect(source.users).not_to include member
expect(NamespaceOnboardingAction.completed?(source.namespace, :user_added)).to be(false)
expect(OnboardingProgress.completed?(source.namespace, :user_added)).to be(false)
end
end
......@@ -63,7 +69,7 @@ RSpec.describe Members::CreateService, :clean_gitlab_redis_shared_state, :sideki
expect(execute_service[:status]).to eq(:error)
expect(execute_service[:message]).to include("#{member.username}: Access level is not included in the list")
expect(source.users).not_to include member
expect(NamespaceOnboardingAction.completed?(source.namespace, :user_added)).to be(false)
expect(OnboardingProgress.completed?(source.namespace, :user_added)).to be(false)
end
end
......@@ -73,7 +79,7 @@ RSpec.describe Members::CreateService, :clean_gitlab_redis_shared_state, :sideki
it 'does not add a member' do
expect(execute_service[:status]).to eq(:error)
expect(execute_service[:message]).to eq('Invite email has already been taken')
expect(NamespaceOnboardingAction.completed?(source.namespace, :user_added)).to be(false)
expect(OnboardingProgress.completed?(source.namespace, :user_added)).to be(false)
end
end
end
......@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe MergeRequests::AfterCreateService do
include AfterNextHelpers
let_it_be(:merge_request) { create(:merge_request) }
subject(:after_create_service) do
......@@ -64,11 +66,15 @@ RSpec.describe MergeRequests::AfterCreateService do
execute_service
end
it 'records a namespace onboarding progress action' do
expect(NamespaceOnboardingAction).to receive(:create_action)
.with(merge_request.target_project.namespace, :merge_request_created).and_call_original
it 'registers an onboarding progress action' do
OnboardingProgress.onboard(merge_request.target_project.namespace)
expect_next(OnboardingProgressService, merge_request.target_project.namespace)
.to receive(:execute).with(action: :merge_request_created).and_call_original
execute_service
expect { execute_service }.to change(NamespaceOnboardingAction, :count).by(1)
expect(OnboardingProgress.completed?(merge_request.target_project.namespace, :merge_request_created)).to be(true)
end
end
end
......@@ -11,32 +11,38 @@ RSpec.describe OnboardingProgressService do
subject(:execute_service) { described_class.new(namespace).execute(action: :subscription_created) }
context 'when the namespace is a root' do
it 'records a namespace onboarding progress action fot the given namespace' do
expect(NamespaceOnboardingAction).to receive(:create_action)
.with(namespace, :subscription_created).and_call_original
before do
OnboardingProgress.onboard(namespace)
end
it 'registers a namespace onboarding progress action for the given namespace' do
execute_service
expect { execute_service }.to change(NamespaceOnboardingAction, :count).by(1)
expect(OnboardingProgress.completed?(namespace, :subscription_created)).to eq(true)
end
end
context 'when the namespace is not the root' do
let(:root_namespace) { build(:namespace) }
it 'records a namespace onboarding progress action for the root namespace' do
expect(NamespaceOnboardingAction).to receive(:create_action)
.with(root_namespace, :subscription_created).and_call_original
before do
OnboardingProgress.onboard(root_namespace)
end
it 'registers a namespace onboarding progress action for the root namespace' do
execute_service
expect { execute_service }.to change(NamespaceOnboardingAction, :count).by(1)
expect(OnboardingProgress.completed?(root_namespace, :subscription_created)).to eq(true)
end
end
context 'when no namespace is passed' do
let(:namespace) { nil }
it 'does not record a namespace onboarding progress action' do
expect(NamespaceOnboardingAction).not_to receive(:create_action)
it 'does not register a namespace onboarding progress action' do
execute_service
expect(OnboardingProgress.completed?(root_namespace, :subscription_created)).to be(nil)
end
end
end
......
......@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe PostReceiveService do
include Gitlab::Routing
include AfterNextHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, :wiki_repo, namespace: user.namespace) }
......@@ -46,8 +47,8 @@ RSpec.describe PostReceiveService do
expect(subject).to be_empty
end
it 'does not record a namespace onboarding progress action' do
expect(NamespaceOnboardingAction).not_to receive(:create_action)
it 'does not record an onboarding progress action' do
expect_next(OnboardingProgressService).not_to receive(:execute)
subject
end
......@@ -87,9 +88,9 @@ RSpec.describe PostReceiveService do
expect(response.reference_counter_decreased).to be(true)
end
it 'records a namespace onboarding progress action' do
expect(NamespaceOnboardingAction).to receive(:create_action)
.with(project.namespace, :git_write)
it 'records an onboarding progress action' do
expect_next(OnboardingProgressService, project.namespace)
.to receive(:execute).with(action: :git_write)
subject
end
......
......@@ -7,20 +7,26 @@ RSpec.describe Namespaces::OnboardingPipelineCreatedWorker, '#perform' do
let_it_be(:ci_pipeline) { create(:ci_pipeline) }
it 'records the event' do
before do
OnboardingProgress.onboard(ci_pipeline.project.namespace)
end
it 'registers an onboarding progress action' do
expect_next(OnboardingProgressService, ci_pipeline.project.namespace)
.to receive(:execute).with(action: :pipeline_created).and_call_original
expect do
subject.perform(ci_pipeline.project.namespace_id)
end.to change(NamespaceOnboardingAction, :count).by(1)
expect(OnboardingProgress.completed?(ci_pipeline.project.namespace, :pipeline_created)).to eq(true)
end
context "when a namespace doesn't exist" do
it "does nothing" do
it 'does not register an onboarding progress action' do
expect_next(OnboardingProgressService, ci_pipeline.project.namespace).not_to receive(:execute)
expect { subject.perform(nil) }.not_to change(NamespaceOnboardingAction, :count)
subject.perform(nil)
expect(OnboardingProgress.completed?(ci_pipeline.project.namespace, :pipeline_created)).to eq(false)
end
end
end
......@@ -7,10 +7,16 @@ RSpec.describe Namespaces::OnboardingUserAddedWorker, '#perform' do
let_it_be(:group) { create(:group) }
it 'records the event' do
before do
OnboardingProgress.onboard(group)
end
it 'registers an onboarding progress action' do
expect_next(OnboardingProgressService, group)
.to receive(:execute).with(action: :user_added).and_call_original
expect { subject.perform(group.id) }.to change(NamespaceOnboardingAction, :count).by(1)
subject.perform(group.id)
expect(OnboardingProgress.completed?(group, :user_added)).to be(true)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment