Commit 27e0147f authored by Tyler Amos's avatar Tyler Amos

Users can preview payload from Seat Link toggle

- Adds preview payload button below the Seat Link toggle on the
  Metrics and Profiling admin page.
- Creates SeatLinkData module as a place to store common logic needed
  to construct the payload for Seat Link requests.  Used by
  SyncSeatLinkWorker and Preview payload.
- Moves spec helper method create_current_license to
  EE::LicenseHelpers for reuse.
- Genericize the UsagePingPayload JS class as PayloadPreviewer
  for reuse.
- Add info about preview payload to Seat Link documentation.
parent 7b5ad03d
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', () => {
new UsagePingPayload(
document.querySelector('.js-usage-ping-payload-trigger'),
document.querySelector('.js-usage-ping-payload'),
).init();
});
document.addEventListener('DOMContentLoaded', setup);
......@@ -2,7 +2,7 @@ import axios from '../../../lib/utils/axios_utils';
import { __ } from '../../../locale';
import flash from '../../../flash';
export default class UsagePingPayload {
export default class PayloadPreviewer {
constructor(trigger, container) {
this.trigger = trigger;
this.container = container;
......@@ -38,7 +38,7 @@ export default class UsagePingPayload {
})
.catch(() => {
this.spinner.classList.remove('d-inline-flex');
flash(__('Error fetching usage ping data.'));
flash(__('Error fetching payload data.'));
});
}
......
......@@ -308,6 +308,11 @@ Sg0KU1hNMGExaE9SVGR2V2pKQlBUMWNiaUo5DQo=',
</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
> [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
'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
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
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 @@
- 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 }
%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
= _('Seat Link is disabled, and cannot be configured through this form.')
- link_path = help_page_path('subscriptions/index', anchor: 'disable-seat-link')
......
......@@ -29,12 +29,12 @@ class SyncSeatLinkRequestWorker
private
def request_body(date, license_key, max_historical_user_count, active_users)
{
Gitlab::SeatLinkData.new(
date: date,
license_key: license_key,
max_historical_user_count: max_historical_user_count,
key: license_key,
max_users: max_historical_user_count,
active_users: active_users
}.to_json
).to_json
end
def request_headers
......
......@@ -16,32 +16,25 @@ class SyncSeatLinkWorker # rubocop:disable Scalability/IdempotentWorker
return unless should_sync_seats?
SyncSeatLinkRequestWorker.perform_async(
report_date.to_s,
License.current.data,
max_historical_user_count,
HistoricalData.at(report_date)&.active_user_count
seat_link_data.date.to_s,
seat_link_data.key,
seat_link_data.max_users,
seat_link_data.active_users
)
end
private
def seat_link_data
@seat_link_data ||= Gitlab::SeatLinkData.new
end
# Only sync paid licenses from start date until 14 days after expiration
# when seat link feature is enabled.
def should_sync_seats?
Gitlab::CurrentSettings.seat_link_enabled? &&
License.current &&
!License.current.trial? &&
report_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
seat_link_data.date.between?(License.current.starts_at, License.current.expires_at + 14.days)
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
# using `only: []` to keep duplicate routes from being created
resource :application_settings, only: [] do
get :seat_link_payload
match :templates, via: [:get, :patch]
get :geo, to: "application_settings#geo_redirection"
end
......
......@@ -234,4 +234,57 @@ describe Admin::ApplicationSettingsController do
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
......@@ -241,6 +241,28 @@ describe 'Admin updates EE-only settings' do
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
ApplicationSetting.current_without_cache
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
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
::Gitlab::CurrentSettings.update!(check_namespace_plan: true)
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
......@@ -4,13 +4,6 @@ require 'spec_helper'
describe SyncSeatLinkWorker, type: :worker 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
let(:utc_time) { Time.utc(2020, 3, 12, 12, 00) }
......
......@@ -8222,6 +8222,9 @@ msgstr ""
msgid "Error fetching network graph."
msgstr ""
msgid "Error fetching payload data."
msgstr ""
msgid "Error fetching projects"
msgstr ""
......@@ -8231,9 +8234,6 @@ msgstr ""
msgid "Error fetching the dependency list. Please check your network connection and try again."
msgstr ""
msgid "Error fetching usage ping data."
msgstr ""
msgid "Error loading branch data. Please try again."
msgstr ""
......
......@@ -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
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'
expect(page).to have_button 'Hide payload'
click_button('Preview 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
......
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