Commit 4b097cb8 authored by Shinya Maeda's avatar Shinya Maeda

Merge branch '212493-preview-seat-link-payload' into 'master'

Add Preview Seat Link payload on "Metrics and Profiling" Settings page

Closes #212493

See merge request gitlab-org/gitlab!28582
parents 38b03a7f 27e0147f
import PayloadPreviewer from '~/pages/admin/application_settings/payload_previewer';
export default () => {
new PayloadPreviewer(
document.querySelector('.js-usage-ping-payload-trigger'),
document.querySelector('.js-usage-ping-payload'),
).init();
};
import UsagePingPayload from './../usage_ping_payload'; import setup from 'ee_else_ce/admin/application_settings/setup_metrics_and_profiling';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', setup);
new UsagePingPayload(
document.querySelector('.js-usage-ping-payload-trigger'),
document.querySelector('.js-usage-ping-payload'),
).init();
});
...@@ -2,7 +2,7 @@ import axios from '../../../lib/utils/axios_utils'; ...@@ -2,7 +2,7 @@ import axios from '../../../lib/utils/axios_utils';
import { __ } from '../../../locale'; import { __ } from '../../../locale';
import flash from '../../../flash'; import flash from '../../../flash';
export default class UsagePingPayload { export default class PayloadPreviewer {
constructor(trigger, container) { constructor(trigger, container) {
this.trigger = trigger; this.trigger = trigger;
this.container = container; this.container = container;
...@@ -38,7 +38,7 @@ export default class UsagePingPayload { ...@@ -38,7 +38,7 @@ export default class UsagePingPayload {
}) })
.catch(() => { .catch(() => {
this.spinner.classList.remove('d-inline-flex'); this.spinner.classList.remove('d-inline-flex');
flash(__('Error fetching usage ping data.')); flash(__('Error fetching payload data.'));
}); });
} }
......
...@@ -308,6 +308,11 @@ Sg0KU1hNMGExaE9SVGR2V2pKQlBUMWNiaUo5DQo=', ...@@ -308,6 +308,11 @@ Sg0KU1hNMGExaE9SVGR2V2pKQlBUMWNiaUo5DQo=',
</details> </details>
You can view the exact JSON payload in the administration panel. To view the payload:
1. Navigate to **Admin Area > Settings > Metrics and profiling** and expand **Seat Links**.
1. Click **Preview payload**.
#### Disable Seat Link #### Disable Seat Link
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/212375) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.10. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/212375) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.10.
......
import PayloadPreviewer from '~/pages/admin/application_settings/payload_previewer';
import baseSetup from '~/admin/application_settings/setup_metrics_and_profiling';
export default () => {
baseSetup();
new PayloadPreviewer(
document.querySelector('.js-seat-link-payload-trigger'),
document.querySelector('.js-seat-link-payload'),
).init();
};
...@@ -66,6 +66,19 @@ module EE ...@@ -66,6 +66,19 @@ module EE
'From GitLab 13.0 on, this will be the only place for Geo settings and <strong>Admin Area > Settings > Geo</strong> will be removed.'.html_safe 'From GitLab 13.0 on, this will be the only place for Geo settings and <strong>Admin Area > Settings > Geo</strong> will be removed.'.html_safe
end end
def seat_link_payload
data = ::Gitlab::SeatLinkData.new
respond_to do |format|
format.html do
seat_link_json = JSON.pretty_generate(data)
render html: ::Gitlab::Highlight.highlight('payload.json', seat_link_json, language: 'json')
end
format.json { render json: data.to_json }
end
end
private private
override :valid_setting_panels override :valid_setting_panels
......
# frozen_string_literal: true
module Gitlab
class SeatLinkData
attr_reader :date, :key, :max_users, :active_users
delegate :to_json, to: :data
# All fields can be passed to initializer to override defaults. In some cases, the defaults
# 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
# the job was enqueued are necessary.
def initialize(date: default_date, key: default_key, max_users: nil, active_users: nil)
@date = date
@key = key
@max_users = max_users || default_max_count(@date)
@active_users = active_users || default_active_count(@date)
end
private
def data
{
date: date.to_s,
license_key: key,
max_historical_user_count: max_users,
active_users: active_users
}
end
def default_date
Time.now.utc.yesterday.to_date
end
def default_key
::License.current.data
end
def default_max_count(date)
HistoricalData.max_historical_user_count(
from: ::License.current.starts_at,
to: date
)
end
def default_active_count(date)
HistoricalData.at(date)&.active_user_count
end
end
end
...@@ -16,6 +16,10 @@ ...@@ -16,6 +16,10 @@
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: link_path } - link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: link_path }
%p.mb-2= s_('%{link_start}Learn more%{link_end} about what information is shared with GitLab Inc.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe } %p.mb-2= s_('%{link_start}Learn more%{link_end} about what information is shared with GitLab Inc.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
%button.btn.js-seat-link-payload-trigger{ type: 'button' }
.spinner.js-spinner.d-none
.js-text.d-inline= _('Preview payload')
%pre.usage-data.js-seat-link-payload.js-syntax-highlight.code.highlight.mt-2.d-none{ data: { endpoint: seat_link_payload_admin_application_settings_path(format: :html) } }
- else - else
= _('Seat Link is disabled, and cannot be configured through this form.') = _('Seat Link is disabled, and cannot be configured through this form.')
- link_path = help_page_path('subscriptions/index', anchor: 'disable-seat-link') - link_path = help_page_path('subscriptions/index', anchor: 'disable-seat-link')
......
...@@ -29,12 +29,12 @@ class SyncSeatLinkRequestWorker ...@@ -29,12 +29,12 @@ class SyncSeatLinkRequestWorker
private private
def request_body(date, license_key, max_historical_user_count, active_users) def request_body(date, license_key, max_historical_user_count, active_users)
{ Gitlab::SeatLinkData.new(
date: date, date: date,
license_key: license_key, key: license_key,
max_historical_user_count: max_historical_user_count, max_users: max_historical_user_count,
active_users: active_users active_users: active_users
}.to_json ).to_json
end end
def request_headers def request_headers
......
...@@ -16,32 +16,25 @@ class SyncSeatLinkWorker # rubocop:disable Scalability/IdempotentWorker ...@@ -16,32 +16,25 @@ class SyncSeatLinkWorker # rubocop:disable Scalability/IdempotentWorker
return unless should_sync_seats? return unless should_sync_seats?
SyncSeatLinkRequestWorker.perform_async( SyncSeatLinkRequestWorker.perform_async(
report_date.to_s, seat_link_data.date.to_s,
License.current.data, seat_link_data.key,
max_historical_user_count, seat_link_data.max_users,
HistoricalData.at(report_date)&.active_user_count seat_link_data.active_users
) )
end end
private private
def seat_link_data
@seat_link_data ||= Gitlab::SeatLinkData.new
end
# Only sync paid licenses from start date until 14 days after expiration # Only sync paid licenses from start date until 14 days after expiration
# when seat link feature is enabled. # when seat link feature is enabled.
def should_sync_seats? def should_sync_seats?
Gitlab::CurrentSettings.seat_link_enabled? && Gitlab::CurrentSettings.seat_link_enabled? &&
License.current && License.current &&
!License.current.trial? && !License.current.trial? &&
report_date.between?(License.current.starts_at, License.current.expires_at + 14.days) seat_link_data.date.between?(License.current.starts_at, License.current.expires_at + 14.days)
end
def max_historical_user_count
HistoricalData.max_historical_user_count(
from: License.current.starts_at,
to: report_date
)
end
def report_date
@report_date ||= Time.now.utc.yesterday.to_date
end end
end end
---
title: Allow Admins to preview the payload for Seat Link requests
merge_request: 28582
author:
type: added
...@@ -28,6 +28,7 @@ namespace :admin do ...@@ -28,6 +28,7 @@ namespace :admin do
# using `only: []` to keep duplicate routes from being created # using `only: []` to keep duplicate routes from being created
resource :application_settings, only: [] do resource :application_settings, only: [] do
get :seat_link_payload
match :templates, via: [:get, :patch] match :templates, via: [:get, :patch]
get :geo, to: "application_settings#geo_redirection" get :geo, to: "application_settings#geo_redirection"
end end
......
...@@ -234,4 +234,57 @@ describe Admin::ApplicationSettingsController do ...@@ -234,4 +234,57 @@ describe Admin::ApplicationSettingsController do
end end
end end
end end
describe 'GET #seat_link_payload' do
context 'when a non-admin user attempts a request' do
before do
sign_in(create(:user))
end
it 'returns a 404 response' do
get :seat_link_payload, format: :html
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when an admin user attempts a request' do
let_it_be(:yesterday) { Time.now.utc.yesterday.to_date }
let_it_be(:max_count) { 15 }
let_it_be(:current_count) { 10 }
around do |example|
Timecop.freeze { example.run }
end
before_all do
HistoricalData.create!(date: yesterday - 1.day, active_user_count: max_count)
HistoricalData.create!(date: yesterday, active_user_count: current_count)
end
before do
sign_in(admin)
end
it 'returns HTML data', :aggregate_failures do
get :seat_link_payload, format: :html
expect(response).to have_gitlab_http_status(:ok)
body = response.body
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=\"s2\">\"#{yesterday}\"</span>")
expect(body).to include("<span class=\"mi\">#{max_count}</span>")
expect(body).to include("<span class=\"mi\">#{current_count}</span>")
end
it 'returns JSON data', :aggregate_failures do
get :seat_link_payload, format: :json
expect(response).to have_gitlab_http_status(:ok)
expect(response.body).to eq(Gitlab::SeatLinkData.new.to_json)
end
end
end
end end
...@@ -241,6 +241,28 @@ describe 'Admin updates EE-only settings' do ...@@ -241,6 +241,28 @@ describe 'Admin updates EE-only settings' do
end end
end end
context 'Metrics and profiling page' do
before do
visit metrics_and_profiling_admin_application_settings_path
end
it 'loads seat link payload on click', :js do
page.within('#js-seat-link-settings') do
expected_payload_content = /(?=.*"date")(?=.*"license_key")(?=.*"max_historical_user_count")(?=.*"active_users")/m
expect(page).not_to have_content expected_payload_content
click_button('Preview payload')
wait_for_requests
expect(page).to have_selector '.js-seat-link-payload'
expect(page).to have_button 'Hide payload'
expect(page).to have_content expected_payload_content
end
end
end
def current_settings def current_settings
ApplicationSetting.current_without_cache ApplicationSetting.current_without_cache
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::SeatLinkData do
subject do
described_class.new(
date: date,
key: key,
max_users: max_users,
active_users: active_users
)
end
let_it_be(:date) { '2020-03-22'.to_date }
let_it_be(:key) { 'key' }
let_it_be(:max_users) { 11 }
let_it_be(:active_users) { 5 }
describe '#initialize' do
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_date - 1.month }
let_it_be(:current_license) { create_current_license(starts_at: license_start_date)}
let_it_be(:max_before_today) { 15 }
let_it_be(:yesterday_active_count) { 12 }
let_it_be(:today_active_count) { 20 }
before_all do
HistoricalData.create!(date: license_start_date, active_user_count: 10)
HistoricalData.create!(date: license_start_date + 1.day, active_user_count: max_before_today)
HistoricalData.create!(date: utc_date - 1.day, active_user_count: yesterday_active_count)
HistoricalData.create!(date: utc_date, active_user_count: today_active_count)
end
around do |example|
Timecop.travel(utc_time) { example.run }
end
context 'when passing no params' do
subject { described_class.new }
it 'returns object with default attributes set' do
expect(subject).to have_attributes(
date: eq(utc_date - 1.day),
key: eq(current_license.data),
max_users: eq(max_before_today),
active_users: eq(yesterday_active_count)
)
end
end
context 'when passing params' do
it 'returns object with given attributes set' do
expect(subject).to have_attributes(
date: eq(date),
key: eq(key),
max_users: eq(max_users),
active_users: eq(active_users)
)
end
context 'when passing date param only' do
subject { described_class.new(date: utc_date) }
it 'returns object with attributes set using given date' do
expect(subject).to have_attributes(
date: eq(utc_date),
key: eq(current_license.data),
max_users: eq(today_active_count),
active_users: eq(today_active_count)
)
end
end
end
end
describe '.to_json' do
it { is_expected.to delegate_method(:to_json).to(:data) }
it 'returns payload data as a JSON string' do
expect(subject.to_json).to eq(
{
date: date.to_s,
license_key: key,
max_historical_user_count: max_users,
active_users: active_users
}.to_json
)
end
end
end
...@@ -35,6 +35,13 @@ module EE ...@@ -35,6 +35,13 @@ module EE
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
::Gitlab::CurrentSettings.update!(check_namespace_plan: true) ::Gitlab::CurrentSettings.update!(check_namespace_plan: true)
end end
def create_current_license(options = {})
License.current.destroy!
gl_license = create(:gitlab_license, options)
create(:license, data: gl_license.export)
end
end end
end end
end end
...@@ -4,13 +4,6 @@ require 'spec_helper' ...@@ -4,13 +4,6 @@ require 'spec_helper'
describe SyncSeatLinkWorker, type: :worker do describe SyncSeatLinkWorker, type: :worker do
describe '#perform' do describe '#perform' do
def create_current_license(options = {})
License.current.destroy!
gl_license = create(:gitlab_license, options)
create(:license, data: gl_license.export)
end
context 'when current, paid license is active' do context 'when current, paid license is active' do
let(:utc_time) { Time.utc(2020, 3, 12, 12, 00) } let(:utc_time) { Time.utc(2020, 3, 12, 12, 00) }
......
...@@ -8222,6 +8222,9 @@ msgstr "" ...@@ -8222,6 +8222,9 @@ msgstr ""
msgid "Error fetching network graph." msgid "Error fetching network graph."
msgstr "" msgstr ""
msgid "Error fetching payload data."
msgstr ""
msgid "Error fetching projects" msgid "Error fetching projects"
msgstr "" msgstr ""
...@@ -8231,9 +8234,6 @@ msgstr "" ...@@ -8231,9 +8234,6 @@ msgstr ""
msgid "Error fetching the dependency list. Please check your network connection and try again." msgid "Error fetching the dependency list. Please check your network connection and try again."
msgstr "" msgstr ""
msgid "Error fetching usage ping data."
msgstr ""
msgid "Error loading branch data. Please try again." msgid "Error loading branch data. Please try again."
msgstr "" msgstr ""
......
...@@ -348,12 +348,19 @@ describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_not_moc ...@@ -348,12 +348,19 @@ describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_not_moc
it 'loads usage ping payload on click', :js do it 'loads usage ping payload on click', :js do
allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false) allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false)
expect(page).to have_button 'Preview payload' page.within('#js-usage-settings') do
expected_payload_content = /(?=.*"uuid")(?=.*"hostname")/m
find('.js-usage-ping-payload-trigger').click expect(page).not_to have_content expected_payload_content
expect(page).to have_selector '.js-usage-ping-payload' click_button('Preview payload')
expect(page).to have_button 'Hide payload'
wait_for_requests
expect(page).to have_selector '.js-usage-ping-payload'
expect(page).to have_button 'Hide payload'
expect(page).to have_content expected_payload_content
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