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,6 +18,29 @@ RSpec.describe SyncSeatLinkRequestWorker, type: :worker do ...@@ -18,6 +18,29 @@ 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: {
timestamp: '2019-12-31T23:20:12Z',
date: '2019-12-31',
license_key: '123',
max_historical_user_count: 5,
active_users: 4
}.to_json
)
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', date: '2020-01-01',
license_key: '123', license_key: '123',
max_historical_user_count: 5, max_historical_user_count: 5,
...@@ -25,6 +48,7 @@ RSpec.describe SyncSeatLinkRequestWorker, type: :worker do ...@@ -25,6 +48,7 @@ RSpec.describe SyncSeatLinkRequestWorker, type: :worker do
}.to_json }.to_json
) )
end 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
......
...@@ -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,6 +90,9 @@ RSpec.describe SyncSeatLinkWorker, type: :worker do ...@@ -88,6 +90,9 @@ RSpec.describe SyncSeatLinkWorker, type: :worker do
end end
end end
context 'license checks' do
let_it_be(:historical_data) { create(:historical_data) }
context 'when license is missing' do context 'when license is missing' do
before do before do
License.current.destroy! License.current.destroy!
...@@ -117,14 +122,14 @@ RSpec.describe SyncSeatLinkWorker, type: :worker do ...@@ -117,14 +122,14 @@ RSpec.describe SyncSeatLinkWorker, type: :worker do
create_current_license(expires_at: expiration_date) create_current_license(expires_at: expiration_date)
end end
context 'the license expired over 15 days ago' do context 'the license expired over 14 days ago' do
let(:expiration_date) { Time.now.utc.to_date - 16.days } let(:expiration_date) { Time.zone.now.utc.to_date - 15.days }
include_examples 'no seat link sync' include_examples 'no seat link sync'
end end
context 'the license expired less than or equal to 15 days ago' do context 'the license expired less than or equal to 14 days ago' do
let(:expiration_date) { Time.now.utc.to_date - 15.days } let(:expiration_date) { Time.zone.now.utc.to_date - 14.days }
it 'executes the SyncSeatLinkRequestWorker' do it 'executes the SyncSeatLinkRequestWorker' do
expect(SyncSeatLinkRequestWorker).to receive(:perform_async).and_return(true) expect(SyncSeatLinkRequestWorker).to receive(:perform_async).and_return(true)
...@@ -133,13 +138,20 @@ RSpec.describe SyncSeatLinkWorker, type: :worker do ...@@ -133,13 +138,20 @@ RSpec.describe SyncSeatLinkWorker, type: :worker do
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