Commit 3e3e0a16 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch '271343-send-latest-historical-data' into 'master'

Send latest historical data as seat link data

See merge request gitlab-org/gitlab!47614
parents 477fe927 b85d82fe
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
module Gitlab module Gitlab
class SeatLinkData class SeatLinkData
attr_reader :date, :key, :max_users, :active_users include Gitlab::Utils::StrongMemoize
attr_reader :timestamp, :key, :max_users, :active_users
delegate :to_json, to: :data delegate :to_json, to: :data
...@@ -10,41 +12,54 @@ module Gitlab ...@@ -10,41 +12,54 @@ module Gitlab
# are preferable, like for SyncSeatLinkWorker, to determine seat link data, and in others, # are preferable, like for SyncSeatLinkWorker, to determine seat link data, and in others,
# like for SyncSeatLinkRequestWorker, the params are passed because the values from when # like for SyncSeatLinkRequestWorker, the params are passed because the values from when
# the job was enqueued are necessary. # the job was enqueued are necessary.
def initialize(date: default_date, key: default_key, max_users: nil, active_users: nil) def initialize(timestamp: nil, key: default_key, max_users: nil, active_users: nil)
@date = date @timestamp = timestamp || historical_data&.recorded_at
@key = key @key = key
@max_users = max_users || default_max_count(@date) @max_users = max_users || default_max_count
@active_users = active_users || default_active_count(@date) @active_users = active_users || default_active_count
end
# Returns true if historical data exists between license start and the given date.
def historical_data_exists?
historical_data.present?
end end
private private
def data def data
{ {
date: date.to_s, timestamp: timestamp&.iso8601,
date: timestamp&.to_date&.to_s,
license_key: key, license_key: key,
max_historical_user_count: max_users, max_historical_user_count: max_users,
active_users: active_users active_users: active_users
} }
end end
def default_date
Time.current.utc.yesterday.to_date
end
def default_key def default_key
::License.current.data ::License.current.data
end end
def default_max_count(date) def license_starts_at
::License.current.starts_at.beginning_of_day
end
def default_max_count
HistoricalData.max_historical_user_count( HistoricalData.max_historical_user_count(
from: ::License.current.starts_at.beginning_of_day, from: license_starts_at,
to: date.end_of_day to: timestamp
) )
end end
def default_active_count(date) def historical_data
HistoricalData.at(date)&.active_user_count strong_memoize(:historical_data) do
to_timestamp = timestamp || Time.current
HistoricalData.during(license_starts_at..to_timestamp).order(:recorded_at).last
end
end
def default_active_count
historical_data&.active_user_count
end end
end end
end end
...@@ -16,11 +16,6 @@ class HistoricalData < ApplicationRecord ...@@ -16,11 +16,6 @@ class HistoricalData < ApplicationRecord
) )
end end
# HistoricalData.at(Date.new(2014, 1, 1)).active_user_count
def at(date)
find_by(recorded_at: date.all_day)
end
def max_historical_user_count(license: nil, from: nil, to: nil) def max_historical_user_count(license: nil, from: nil, to: nil)
license ||= License.current license ||= License.current
expires_at = license&.expires_at || Time.current expires_at = license&.expires_at || Time.current
......
...@@ -15,12 +15,12 @@ class SyncSeatLinkRequestWorker ...@@ -15,12 +15,12 @@ class SyncSeatLinkRequestWorker
RequestError = Class.new(StandardError) RequestError = Class.new(StandardError)
def perform(date, license_key, max_historical_user_count, active_users) def perform(timestamp, license_key, max_historical_user_count, active_users)
response = Gitlab::HTTP.post( response = Gitlab::HTTP.post(
URI_PATH, URI_PATH,
base_uri: EE::SUBSCRIPTIONS_URL, base_uri: EE::SUBSCRIPTIONS_URL,
headers: request_headers, headers: request_headers,
body: request_body(date, license_key, max_historical_user_count, active_users) body: request_body(timestamp, license_key, max_historical_user_count, active_users)
) )
raise RequestError, request_error_message(response) unless response.success? raise RequestError, request_error_message(response) unless response.success?
...@@ -28,9 +28,9 @@ class SyncSeatLinkRequestWorker ...@@ -28,9 +28,9 @@ class SyncSeatLinkRequestWorker
private private
def request_body(date, license_key, max_historical_user_count, active_users) def request_body(timestamp, license_key, max_historical_user_count, active_users)
Gitlab::SeatLinkData.new( Gitlab::SeatLinkData.new(
date: date, timestamp: Time.zone.parse(timestamp),
key: license_key, key: license_key,
max_users: max_historical_user_count, max_users: max_historical_user_count,
active_users: active_users active_users: active_users
......
...@@ -16,7 +16,7 @@ class SyncSeatLinkWorker # rubocop:disable Scalability/IdempotentWorker ...@@ -16,7 +16,7 @@ class SyncSeatLinkWorker # rubocop:disable Scalability/IdempotentWorker
return unless should_sync_seats? return unless should_sync_seats?
SyncSeatLinkRequestWorker.perform_async( SyncSeatLinkRequestWorker.perform_async(
seat_link_data.date.to_s, seat_link_data.timestamp.iso8601,
seat_link_data.key, seat_link_data.key,
seat_link_data.max_users, seat_link_data.max_users,
seat_link_data.active_users seat_link_data.active_users
...@@ -36,6 +36,7 @@ class SyncSeatLinkWorker # rubocop:disable Scalability/IdempotentWorker ...@@ -36,6 +36,7 @@ class SyncSeatLinkWorker # rubocop:disable Scalability/IdempotentWorker
License.current && License.current &&
!License.current.trial? && !License.current.trial? &&
License.current.expires_at && # Skip sync if license has no expiration License.current.expires_at && # Skip sync if license has no expiration
seat_link_data.date.between?(License.current.starts_at, License.current.expires_at + 14.days) seat_link_data.historical_data_exists? && # Skip sync if there is no historical data
seat_link_data.timestamp.between?(License.current.starts_at.beginning_of_day, License.current.expires_at.end_of_day + 14.days)
end end
end end
---
title: Modify seat link API to send latest historical data instead of previous day's
data
merge_request: 47614
author:
type: changed
...@@ -312,6 +312,7 @@ RSpec.describe Admin::ApplicationSettingsController do ...@@ -312,6 +312,7 @@ RSpec.describe Admin::ApplicationSettingsController do
expect(body).to start_with('<span id="LC1" class="line" lang="json">') expect(body).to start_with('<span id="LC1" class="line" lang="json">')
expect(body).to include('<span class="nl">"license_key"</span>') expect(body).to include('<span class="nl">"license_key"</span>')
expect(body).to include("<span class=\"s2\">\"#{yesterday.to_date}\"</span>") expect(body).to include("<span class=\"s2\">\"#{yesterday.to_date}\"</span>")
expect(body).to include("<span class=\"s2\">\"#{yesterday.iso8601}\"</span>")
expect(body).to include("<span class=\"mi\">#{max_count}</span>") expect(body).to include("<span class=\"mi\">#{max_count}</span>")
expect(body).to include("<span class=\"mi\">#{current_count}</span>") expect(body).to include("<span class=\"mi\">#{current_count}</span>")
end end
......
...@@ -257,7 +257,7 @@ RSpec.describe 'Admin updates EE-only settings' do ...@@ -257,7 +257,7 @@ RSpec.describe 'Admin updates EE-only settings' do
it 'loads seat link payload on click', :js do it 'loads seat link payload on click', :js do
page.within('#js-seat-link-settings') do page.within('#js-seat-link-settings') do
expected_payload_content = /(?=.*"date")(?=.*"license_key")(?=.*"max_historical_user_count")(?=.*"active_users")/m expected_payload_content = /(?=.*"date")(?=.*"timestamp")(?=.*"license_key")(?=.*"max_historical_user_count")(?=.*"active_users")/m
expect(page).not_to have_content expected_payload_content expect(page).not_to have_content expected_payload_content
......
...@@ -5,22 +5,21 @@ require 'spec_helper' ...@@ -5,22 +5,21 @@ require 'spec_helper'
RSpec.describe Gitlab::SeatLinkData do RSpec.describe Gitlab::SeatLinkData do
subject do subject do
described_class.new( described_class.new(
date: date, timestamp: timestamp,
key: key, key: key,
max_users: max_users, max_users: max_users,
active_users: active_users active_users: active_users
) )
end end
let_it_be(:date) { '2020-03-22'.to_date } let_it_be(:timestamp) { Time.iso8601('2020-03-22T06:09:18Z') }
let_it_be(:key) { 'key' } let_it_be(:key) { 'key' }
let_it_be(:max_users) { 11 } let_it_be(:max_users) { 11 }
let_it_be(:active_users) { 5 } let_it_be(:active_users) { 5 }
describe '#initialize' do describe '#initialize' do
let_it_be(:utc_time) { Time.utc(2020, 3, 12, 12, 00) } let_it_be(:utc_time) { Time.utc(2020, 3, 12, 12, 00) }
let_it_be(:utc_date) { utc_time.to_date } let_it_be(:license_start_date) { utc_time.to_date - 1.month }
let_it_be(:license_start_date) { utc_date - 1.month }
let_it_be(:current_license) { create_current_license(starts_at: license_start_date)} let_it_be(:current_license) { create_current_license(starts_at: license_start_date)}
let_it_be(:max_before_today) { 15 } let_it_be(:max_before_today) { 15 }
...@@ -30,8 +29,8 @@ RSpec.describe Gitlab::SeatLinkData do ...@@ -30,8 +29,8 @@ RSpec.describe Gitlab::SeatLinkData do
before_all do before_all do
HistoricalData.create!(recorded_at: license_start_date, active_user_count: 10) HistoricalData.create!(recorded_at: license_start_date, active_user_count: 10)
HistoricalData.create!(recorded_at: license_start_date + 1.day, active_user_count: max_before_today) HistoricalData.create!(recorded_at: license_start_date + 1.day, active_user_count: max_before_today)
HistoricalData.create!(recorded_at: utc_date - 1.day, active_user_count: yesterday_active_count) HistoricalData.create!(recorded_at: utc_time - 1.day, active_user_count: yesterday_active_count)
HistoricalData.create!(recorded_at: utc_date, active_user_count: today_active_count) HistoricalData.create!(recorded_at: utc_time, active_user_count: today_active_count)
end end
around do |example| around do |example|
...@@ -43,10 +42,10 @@ RSpec.describe Gitlab::SeatLinkData do ...@@ -43,10 +42,10 @@ RSpec.describe Gitlab::SeatLinkData do
it 'returns object with default attributes set' do it 'returns object with default attributes set' do
expect(subject).to have_attributes( expect(subject).to have_attributes(
date: eq(utc_date - 1.day), timestamp: eq(utc_time),
key: eq(current_license.data), key: eq(current_license.data),
max_users: eq(max_before_today), max_users: eq(today_active_count),
active_users: eq(yesterday_active_count) active_users: eq(today_active_count)
) )
end end
end end
...@@ -54,7 +53,7 @@ RSpec.describe Gitlab::SeatLinkData do ...@@ -54,7 +53,7 @@ RSpec.describe Gitlab::SeatLinkData do
context 'when passing params' do context 'when passing params' do
it 'returns object with given attributes set' do it 'returns object with given attributes set' do
expect(subject).to have_attributes( expect(subject).to have_attributes(
date: eq(date), timestamp: eq(timestamp),
key: eq(key), key: eq(key),
max_users: eq(max_users), max_users: eq(max_users),
active_users: eq(active_users) active_users: eq(active_users)
...@@ -62,14 +61,14 @@ RSpec.describe Gitlab::SeatLinkData do ...@@ -62,14 +61,14 @@ RSpec.describe Gitlab::SeatLinkData do
end end
context 'when passing date param only' do context 'when passing date param only' do
subject { described_class.new(date: utc_date) } subject { described_class.new(timestamp: utc_time - 1.day) }
it 'returns object with attributes set using given date' do it 'returns object with attributes set using given date' do
expect(subject).to have_attributes( expect(subject).to have_attributes(
date: eq(utc_date), timestamp: eq(utc_time - 1.day),
key: eq(current_license.data), key: eq(current_license.data),
max_users: eq(today_active_count), max_users: eq(max_before_today),
active_users: eq(today_active_count) active_users: eq(yesterday_active_count)
) )
end end
end end
...@@ -82,7 +81,8 @@ RSpec.describe Gitlab::SeatLinkData do ...@@ -82,7 +81,8 @@ RSpec.describe Gitlab::SeatLinkData do
it 'returns payload data as a JSON string' do it 'returns payload data as a JSON string' do
expect(subject.to_json).to eq( expect(subject.to_json).to eq(
{ {
date: date.to_s, timestamp: timestamp.iso8601,
date: timestamp.to_date.iso8601,
license_key: key, license_key: key,
max_historical_user_count: max_users, max_historical_user_count: max_users,
active_users: active_users active_users: active_users
...@@ -90,4 +90,25 @@ RSpec.describe Gitlab::SeatLinkData do ...@@ -90,4 +90,25 @@ RSpec.describe Gitlab::SeatLinkData do
) )
end end
end end
describe '#historical_data_exists?' do
let_it_be(:license) { create_current_license(starts_at: Date.current - 7.days) }
it 'returns false if no historical data exists' do
expect(described_class.new.historical_data_exists?).to be(false)
end
it 'returns false if no historical data exists within [license start date, seat_link_data.date]' do
create(:historical_data, recorded_at: Time.current - 8.days)
create(:historical_data, recorded_at: Time.current)
expect(described_class.new(timestamp: Time.current - 1.day).historical_data_exists?).to be(false)
end
it 'returns true if historical data exists within [license start date, seat_link_data.date]' do
create(:historical_data, recorded_at: Time.current - 2.days)
expect(described_class.new(timestamp: Time.current - 1.day).historical_data_exists?).to be(true)
end
end
end end
...@@ -21,12 +21,6 @@ RSpec.describe HistoricalData do ...@@ -21,12 +21,6 @@ RSpec.describe HistoricalData do
end end
end end
describe ".at" do
it "returns the historical data at the specified date" do
expect(described_class.at(Date.new(2014, 8, 1)).active_user_count).to eq(800)
end
end
describe ".track!" do describe ".track!" do
before do before do
allow(User).to receive(:billable).and_return([1, 2, 3, 4, 5]) allow(User).to receive(:billable).and_return([1, 2, 3, 4, 5])
......
...@@ -5,7 +5,7 @@ require 'spec_helper' ...@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe SyncSeatLinkRequestWorker, type: :worker do RSpec.describe SyncSeatLinkRequestWorker, type: :worker do
describe '#perform' do describe '#perform' do
subject do subject do
described_class.new.perform('2020-01-01', '123', 5, 4) described_class.new.perform('2020-01-01T01:20:12+02:00', '123', 5, 4)
end end
let(:seat_link_url) { [EE::SUBSCRIPTIONS_URL, '/api/v1/seat_links'].join } let(:seat_link_url) { [EE::SUBSCRIPTIONS_URL, '/api/v1/seat_links'].join }
...@@ -18,7 +18,8 @@ RSpec.describe SyncSeatLinkRequestWorker, type: :worker do ...@@ -18,7 +18,8 @@ RSpec.describe SyncSeatLinkRequestWorker, type: :worker do
expect(WebMock).to have_requested(:post, seat_link_url).with( expect(WebMock).to have_requested(:post, seat_link_url).with(
headers: { 'Content-Type' => 'application/json' }, headers: { 'Content-Type' => 'application/json' },
body: { body: {
date: '2020-01-01', timestamp: '2019-12-31T23:20:12Z',
date: '2019-12-31',
license_key: '123', license_key: '123',
max_historical_user_count: 5, max_historical_user_count: 5,
active_users: 4 active_users: 4
...@@ -26,6 +27,29 @@ RSpec.describe SyncSeatLinkRequestWorker, type: :worker do ...@@ -26,6 +27,29 @@ RSpec.describe SyncSeatLinkRequestWorker, type: :worker do
) )
end end
context 'with old date format string' do
subject do
described_class.new.perform('2020-01-01', '123', 5, 4)
end
it 'makes an HTTP POST request with passed params' do
stub_request(:post, seat_link_url).to_return(status: 200)
subject
expect(WebMock).to have_requested(:post, seat_link_url).with(
headers: { 'Content-Type' => 'application/json' },
body: {
timestamp: '2020-01-01T00:00:00Z',
date: '2020-01-01',
license_key: '123',
max_historical_user_count: 5,
active_users: 4
}.to_json
)
end
end
shared_examples 'unsuccessful request' do shared_examples 'unsuccessful request' do
context 'when the request is not successful' do context 'when the request is not successful' do
before do before do
......
...@@ -11,13 +11,13 @@ RSpec.describe SyncSeatLinkWorker, type: :worker do ...@@ -11,13 +11,13 @@ RSpec.describe SyncSeatLinkWorker, type: :worker do
# Setting the date as 12th March 2020 12:00 UTC for tests and creating new license # Setting the date as 12th March 2020 12:00 UTC for tests and creating new license
create_current_license(starts_at: '2020-02-12'.to_date) create_current_license(starts_at: '2020-02-12'.to_date)
HistoricalData.create!(recorded_at: '2020-02-11'.to_date, active_user_count: 100) HistoricalData.create!(recorded_at: '2020-02-11T00:00:00Z', active_user_count: 100)
HistoricalData.create!(recorded_at: '2020-02-12'.to_date, active_user_count: 10) HistoricalData.create!(recorded_at: '2020-02-12T00:00:00Z', active_user_count: 10)
HistoricalData.create!(recorded_at: '2020-02-13'.to_date, active_user_count: 15) HistoricalData.create!(recorded_at: '2020-02-13T00:00:00Z', active_user_count: 15)
HistoricalData.create!(recorded_at: '2020-03-11'.to_date, active_user_count: 10) HistoricalData.create!(recorded_at: '2020-03-11T00:00:00Z', active_user_count: 10)
HistoricalData.create!(recorded_at: '2020-03-12'.to_date, active_user_count: 20) HistoricalData.create!(recorded_at: '2020-03-12T00:00:00Z', active_user_count: 12)
HistoricalData.create!(recorded_at: '2020-03-15'.to_date, active_user_count: 25) HistoricalData.create!(recorded_at: '2020-03-15T00:00:00Z', active_user_count: 25)
allow(SyncSeatLinkRequestWorker).to receive(:perform_async).and_return(true) allow(SyncSeatLinkRequestWorker).to receive(:perform_async).and_return(true)
end end
...@@ -27,10 +27,10 @@ RSpec.describe SyncSeatLinkWorker, type: :worker do ...@@ -27,10 +27,10 @@ RSpec.describe SyncSeatLinkWorker, type: :worker do
expect(SyncSeatLinkRequestWorker).to have_received(:perform_async) expect(SyncSeatLinkRequestWorker).to have_received(:perform_async)
.with( .with(
'2020-03-11', '2020-03-12T00:00:00Z',
License.current.data, License.current.data,
15, 15,
10 12
) )
end end
end end
...@@ -46,12 +46,13 @@ RSpec.describe SyncSeatLinkWorker, type: :worker do ...@@ -46,12 +46,13 @@ RSpec.describe SyncSeatLinkWorker, type: :worker do
subject.perform subject.perform
# Time.iso8601('2020-03-12T13:00:00+13:00') == Time.iso8601('2020-03-12T00:00:00Z')
expect(SyncSeatLinkRequestWorker).to have_received(:perform_async) expect(SyncSeatLinkRequestWorker).to have_received(:perform_async)
.with( .with(
'2020-03-11', '2020-03-12T13:00:00+13:00',
License.current.data, License.current.data,
15, 15,
10 12
) )
end end
end end
...@@ -67,12 +68,13 @@ RSpec.describe SyncSeatLinkWorker, type: :worker do ...@@ -67,12 +68,13 @@ RSpec.describe SyncSeatLinkWorker, type: :worker do
subject.perform subject.perform
# Time.iso8601('2020-03-11T18:00:00-06:00') == Time.iso8601('2020-03-12T00:00:00Z')
expect(SyncSeatLinkRequestWorker).to have_received(:perform_async) expect(SyncSeatLinkRequestWorker).to have_received(:perform_async)
.with( .with(
'2020-03-11', '2020-03-11T18:00:00-06:00',
License.current.data, License.current.data,
15, 15,
10 12
) )
end end
end end
...@@ -88,58 +90,68 @@ RSpec.describe SyncSeatLinkWorker, type: :worker do ...@@ -88,58 +90,68 @@ RSpec.describe SyncSeatLinkWorker, type: :worker do
end end
end end
context 'when license is missing' do context 'license checks' do
before do let_it_be(:historical_data) { create(:historical_data) }
License.current.destroy!
end
include_examples 'no seat link sync' context 'when license is missing' do
end before do
License.current.destroy!
end
context 'when using a trial license' do include_examples 'no seat link sync'
before do
create(:license, trial: true)
end end
include_examples 'no seat link sync' context 'when using a trial license' do
end before do
create(:license, trial: true)
end
context 'when the license has no expiration date' do include_examples 'no seat link sync'
before do
create_current_license(expires_at: nil, block_changes_at: nil)
end end
include_examples 'no seat link sync' context 'when the license has no expiration date' do
end before do
create_current_license(expires_at: nil, block_changes_at: nil)
end
context 'when using an expired license' do include_examples 'no seat link sync'
before do
create_current_license(expires_at: expiration_date)
end end
context 'the license expired over 15 days ago' do context 'when using an expired license' do
let(:expiration_date) { Time.now.utc.to_date - 16.days } before do
create_current_license(expires_at: expiration_date)
end
include_examples 'no seat link sync' context 'the license expired over 14 days ago' do
end let(:expiration_date) { Time.zone.now.utc.to_date - 15.days }
context 'the license expired less than or equal to 15 days ago' do include_examples 'no seat link sync'
let(:expiration_date) { Time.now.utc.to_date - 15.days } end
it 'executes the SyncSeatLinkRequestWorker' do context 'the license expired less than or equal to 14 days ago' do
expect(SyncSeatLinkRequestWorker).to receive(:perform_async).and_return(true) let(:expiration_date) { Time.zone.now.utc.to_date - 14.days }
subject.perform it 'executes the SyncSeatLinkRequestWorker' do
expect(SyncSeatLinkRequestWorker).to receive(:perform_async).and_return(true)
subject.perform
end
end end
end end
end end
context 'when seat link has been disabled' do context 'when seat link has been disabled' do
before do before do
create(:historical_data)
allow(Gitlab::CurrentSettings).to receive(:seat_link_enabled?).and_return(false) allow(Gitlab::CurrentSettings).to receive(:seat_link_enabled?).and_return(false)
end end
include_examples 'no seat link sync' include_examples 'no seat link sync'
end end
context 'when no historical data exists' do
include_examples 'no seat link sync'
end
end end
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