Skip to content
Snippets Groups Projects
Commit 53769346 authored by Corinna Gogolok's avatar Corinna Gogolok :m:
Browse files

Add logic to reset submit license usage data banner data

This change introduces a new callout feature name called
`submit_license_usage_data_banner`. It will be used in an upcoming
banner to ask an instance admin to submit seat utilization data via
email. The banner can be dismissed after exporting the license usage
data. But it will be reset each month relative to the license's start
date. The new worker `ResetSubmitLicenseUsageDataBannerWorker` will run
every day at midnight for a possible reset.

Changelog: added
EE: true
parent 37d9bb8f
No related branches found
No related tags found
1 merge request!89142Add logic to reset submit license usage data banner data
......@@ -51,12 +51,15 @@ class Callout < ApplicationRecord
attention_requests_side_nav: 48,
minute_limit_banner: 49,
preview_user_over_limit_free_plan_alert: 50, # EE-only
user_reached_limit_free_plan_alert: 51 # EE-only
user_reached_limit_free_plan_alert: 51, # EE-only
submit_license_usage_data_banner: 52 # EE-only
}
validates :feature_name,
presence: true,
uniqueness: { scope: :user_id },
inclusion: { in: Users::Callout.feature_names.keys }
scope :with_feature_name, -> (feature_name) { where(feature_name: feature_name) }
end
end
......@@ -775,6 +775,9 @@
Settings.cron_jobs['ci_runners_stale_group_runners_prune_worker_cron'] ||= Settingslogic.new({})
Settings.cron_jobs['ci_runners_stale_group_runners_prune_worker_cron']['cron'] ||= '30 * * * *'
Settings.cron_jobs['ci_runners_stale_group_runners_prune_worker_cron']['job_class'] = 'Ci::Runners::StaleGroupRunnersPruneCronWorker'
Settings.cron_jobs['licenses_reset_submit_license_usage_data_banner'] ||= Settingslogic.new({})
Settings.cron_jobs['licenses_reset_submit_license_usage_data_banner']['cron'] ||= "0 0 * * *"
Settings.cron_jobs['licenses_reset_submit_license_usage_data_banner']['job_class'] = 'Licenses::ResetSubmitLicenseUsageDataBannerWorker'
end
#
......
......@@ -19594,6 +19594,7 @@ Name of the feature that the callout is for.
| <a id="usercalloutfeaturenameenumstorage_enforcement_banner_fourth_enforcement_threshold"></a>`STORAGE_ENFORCEMENT_BANNER_FOURTH_ENFORCEMENT_THRESHOLD` | Callout feature name for storage_enforcement_banner_fourth_enforcement_threshold. |
| <a id="usercalloutfeaturenameenumstorage_enforcement_banner_second_enforcement_threshold"></a>`STORAGE_ENFORCEMENT_BANNER_SECOND_ENFORCEMENT_THRESHOLD` | Callout feature name for storage_enforcement_banner_second_enforcement_threshold. |
| <a id="usercalloutfeaturenameenumstorage_enforcement_banner_third_enforcement_threshold"></a>`STORAGE_ENFORCEMENT_BANNER_THIRD_ENFORCEMENT_THRESHOLD` | Callout feature name for storage_enforcement_banner_third_enforcement_threshold. |
| <a id="usercalloutfeaturenameenumsubmit_license_usage_data_banner"></a>`SUBMIT_LICENSE_USAGE_DATA_BANNER` | Callout feature name for submit_license_usage_data_banner. |
| <a id="usercalloutfeaturenameenumsuggest_pipeline"></a>`SUGGEST_PIPELINE` | Callout feature name for suggest_pipeline. |
| <a id="usercalloutfeaturenameenumsuggest_popover_dismissed"></a>`SUGGEST_POPOVER_DISMISSED` | Callout feature name for suggest_popover_dismissed. |
| <a id="usercalloutfeaturenameenumtabs_position_highlight"></a>`TABS_POSITION_HIGHLIGHT` | Callout feature name for tabs_position_highlight. |
......@@ -390,6 +390,15 @@
:weight: 1
:idempotent:
:tags: []
- :name: cronjob:licenses_reset_submit_license_usage_data_banner
:worker_name: Licenses::ResetSubmitLicenseUsageDataBannerWorker
:feature_category: :provision
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:tags: []
- :name: cronjob:namespaces_free_user_cap_remediation
:worker_name: Namespaces::FreeUserCap::RemediationWorker
:feature_category: :experimentation_conversion
......
# frozen_string_literal: true
module Licenses
class ResetSubmitLicenseUsageDataBannerWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
data_consistency :sticky
# This worker does not perform work scoped to a context
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :provision
# Keep retries within the same day (retries are within ~17 hours)
sidekiq_options retry: 13
def perform
return if License.current.nil?
Gitlab::Licenses::SubmitLicenseUsageDataBanner.new.reset
end
end
end
# frozen_string_literal: true
module Gitlab
module Licenses
class SubmitLicenseUsageDataBanner
SUBMIT_LICENSE_USAGE_DATA_BANNER = 'submit_license_usage_data_banner'
def reset
return unless has_non_trial_offline_cloud_license?
return unless same_day_or_end_of_month?
Gitlab::CurrentSettings.update(license_usage_data_exported: false)
Users::Callout.with_feature_name(SUBMIT_LICENSE_USAGE_DATA_BANNER).delete_all
end
private
def has_non_trial_offline_cloud_license?
return false if Gitlab::CurrentSettings.should_check_namespace_plan?
return false unless Feature.enabled?(:automated_email_provision)
return false unless ::License.current&.offline_cloud_license?
return false if ::License.current.trial?
true
end
def same_day_or_end_of_month?
current_date = Date.current
start_date = ::License.current.starts_at
current_date.day == start_date.day ||
(current_date == current_date.end_of_month && start_date.day > current_date.day)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Licenses::SubmitLicenseUsageDataBanner do
let_it_be(:feature_name) { described_class::SUBMIT_LICENSE_USAGE_DATA_BANNER }
describe '#reset' do
using RSpec::Parameterized::TableSyntax
subject(:reset_data) { described_class.new.reset }
let(:check_namespace_plan) { false }
let(:cloud_licensing_enabled) { true }
let(:offline_cloud_licensing_enabled) { true }
let(:trial) { false }
let(:starts_at) { Date.current }
def callout_count
Users::Callout.where(feature_name: feature_name).count
end
shared_examples 'skips resetting the submit license usage data' do
it 'does not reset the submit license usage data' do
reset_data
expect(Gitlab::CurrentSettings.reload.license_usage_data_exported).to eq(true)
expect(callout_count).to eq(1)
end
end
shared_examples 'resets the submit license usage data' do
it 'resets the submit license usage data' do
reset_data
expect(Gitlab::CurrentSettings.reload.license_usage_data_exported).to eq(false)
expect(callout_count).to eq(0)
end
end
before do
Gitlab::CurrentSettings.update!(license_usage_data_exported: true)
create_current_license(
{
cloud_licensing_enabled: cloud_licensing_enabled,
offline_cloud_licensing_enabled: offline_cloud_licensing_enabled,
restrictions: { trial: trial },
starts_at: starts_at
}
)
create(:callout, feature_name: feature_name, dismissed_at: Time.current)
allow(Gitlab::CurrentSettings).to receive(:should_check_namespace_plan?).and_return(check_namespace_plan)
end
context 'when check namespace plan setting is enabled' do
let(:check_namespace_plan) { true }
include_examples 'skips resetting the submit license usage data'
end
context 'when feature flag :automated_email_provision is disabled' do
before do
stub_feature_flags(automated_email_provision: false)
end
include_examples 'skips resetting the submit license usage data'
end
context 'when there is only a future dated license' do
let(:starts_at) { Date.tomorrow }
before do
::License.current.destroy!
end
include_examples 'skips resetting the submit license usage data'
end
context 'when current license is an online cloud license' do
let(:offline_cloud_licensing_enabled) { false }
include_examples 'skips resetting the submit license usage data'
end
context 'when current license is a legacy license' do
let(:cloud_licensing_enabled) { false }
let(:offline_cloud_licensing_enabled) { false }
include_examples 'skips resetting the submit license usage data'
end
context 'when current license is for a trial' do
let(:trial) { true }
include_examples 'skips resetting the submit license usage data'
end
context 'when license start day matches today\'s day' do
include_examples 'resets the submit license usage data'
end
context 'when license start day does not match today\'s day' do
context 'and today is the end of the month' do
context 'and the start date\'s day is smaller than today\'s day' do
let(:starts_at) { Date.new(2022, 1, 27) }
around do |example|
travel_to(Date.new(2022, 2, 28)) { example.run }
end
include_examples 'skips resetting the submit license usage data'
end
context 'and the start date\'s day is bigger than today\'s day' do
where(
current_date: [
Date.new(2022, 2, 28),
Date.new(2024, 2, 29),
Date.new(2022, 4, 30)
]
)
with_them do
let(:starts_at) { Date.new(2022, 1, 31) }
around do |example|
travel_to(current_date) { example.run }
end
include_examples 'resets the submit license usage data'
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Licenses::ResetSubmitLicenseUsageDataBannerWorker, type: :worker do
describe '#perform' do
subject(:reset_license_usage_data_exported) { described_class.new.perform }
before do
allow(Gitlab::CurrentSettings).to receive(:should_check_namespace_plan?).and_return(false)
end
context 'when current license is nil' do
before do
License.current.destroy!
end
it 'does not reset the submit license usage data' do
expect(Gitlab::Licenses::SubmitLicenseUsageDataBanner).not_to receive(:new)
reset_license_usage_data_exported
end
end
it 'resets the submit license usage data' do
create_current_license({ cloud_licensing_enabled: true, offline_cloud_licensing_enabled: true })
expect_next_instance_of(Gitlab::Licenses::SubmitLicenseUsageDataBanner) do |banner|
expect(banner).to receive(:reset).and_call_original
end
reset_license_usage_data_exported
end
end
end
......@@ -11,4 +11,16 @@
it { is_expected.to validate_presence_of(:feature_name) }
it { is_expected.to validate_uniqueness_of(:feature_name).scoped_to(:user_id).ignoring_case_sensitivity }
end
describe 'scopes' do
describe '.with_feature_name' do
let_it_be(:feature_name) { described_class.feature_names.keys.last }
let_it_be(:user_callouts_for_feature_name) { create_list(:callout, 2, feature_name: feature_name) }
let_it_be(:another_user_callout) { create(:callout, feature_name: described_class.feature_names.each_key.first) }
it 'returns user callouts for the given feature name only' do
expect(described_class.with_feature_name(feature_name)).to eq(user_callouts_for_feature_name)
end
end
end
end
......@@ -319,6 +319,7 @@
'JiraConnect::SyncMergeRequestWorker' => 3,
'JiraConnect::SyncProjectWorker' => 3,
'LdapGroupSyncWorker' => 3,
'Licenses::ResetSubmitLicenseUsageDataBannerWorker' => 13,
'MailScheduler::IssueDueWorker' => 3,
'MailScheduler::NotificationServiceWorker' => 3,
'MembersDestroyer::UnassignIssuablesWorker' => 3,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment