killbill-aviate 2.4.0 → 2.4.1.pre.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/config/aviate_manifest.js +0 -1
- data/app/assets/images/aviate/.keep +0 -0
- data/app/assets/javascripts/aviate/kiddo/charts/line_chart.js +29 -9
- data/app/controllers/aviate/usage_controller.rb +23 -3
- data/app/views/aviate/usage/show.html.erb +6 -1
- data/lib/aviate/client.rb +76 -8
- data/lib/aviate/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 668e8f43e5ab2c7c9c617b13e6e741b355613950f64e850682174001f93ac03c
|
|
4
|
+
data.tar.gz: b8eaa6d33bd8029125ca1ecb6e78d6c29902730c15a75ecf74498814b2f78ef3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 57a7d1d3458d82f0891c67a3a243bc9a45b679a67e53c17703059d3a2e49454f5dd77a12fc4211f63a6492d94f2080eb5fd92e79e229b2facf68fabb83f6b29a
|
|
7
|
+
data.tar.gz: d4ecb9bad5c487cab51f82912a6daaabd424ad996d2a4d6fdd52ed2857371523c32ffa7dd9247a7676902056826a1d6ad71c016afa33362d0840b50100531e49
|
|
File without changes
|
|
@@ -47,15 +47,24 @@
|
|
|
47
47
|
return result.concat(element.values);
|
|
48
48
|
}, []);
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
var
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
self.
|
|
50
|
+
// X domain: union of billing period bounds and actual data so that data
|
|
51
|
+
// points recorded just before the period start (e.g. on the charge date)
|
|
52
|
+
// remain visible.
|
|
53
|
+
var period_start = json.period_start ? helper.parseDate(json.period_start) : null;
|
|
54
|
+
var period_end = json.period_end ? helper.parseDate(json.period_end) : null;
|
|
55
|
+
var x_values = allValues.map(function (d) { return d.x; });
|
|
56
|
+
if (period_start) x_values.push(period_start);
|
|
57
|
+
if (period_end) x_values.push(period_end);
|
|
58
|
+
self.x.domain(d3.extent(x_values));
|
|
59
|
+
|
|
60
|
+
// Y domain: always start at 0 and use nice() for clean tick values.
|
|
61
|
+
var y_max = d3.max(allValues, function (d) { return d.y; }) || 1;
|
|
62
|
+
self.y.domain([0, y_max]).nice();
|
|
63
|
+
|
|
64
|
+
// Cap y-axis tick count to the domain max so integer formatting never
|
|
65
|
+
// produces duplicate labels (e.g. [0,3] with 10 ticks → "0,0,1,1,2,2,3").
|
|
66
|
+
var y_domain_max = self.y.domain()[1];
|
|
67
|
+
axes.y.ticks(Math.min(y_domain_max, 10));
|
|
59
68
|
|
|
60
69
|
// Render axes
|
|
61
70
|
axes.render(svg);
|
|
@@ -112,6 +121,17 @@
|
|
|
112
121
|
yOffset += 22;
|
|
113
122
|
});
|
|
114
123
|
|
|
124
|
+
// Extend each series to the right edge of the x domain so the cumulative
|
|
125
|
+
// step line is always visible — d3.curveStepAfter needs at least two points
|
|
126
|
+
// to produce a visible line segment; a single point produces only a move.
|
|
127
|
+
var x_domain_end = self.x.domain()[1];
|
|
128
|
+
datasets.forEach(function (dataset) {
|
|
129
|
+
var vals = dataset.values;
|
|
130
|
+
if (vals.length > 0 && vals[vals.length - 1].x < x_domain_end) {
|
|
131
|
+
vals.push({ x: x_domain_end, y: vals[vals.length - 1].y });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
115
135
|
// Render data lines (stepped)
|
|
116
136
|
datasets.forEach(function (dataset) {
|
|
117
137
|
var data = dataset.values,
|
|
@@ -49,7 +49,7 @@ module Aviate
|
|
|
49
49
|
@wallet_balance = fetch_wallet_balance(@account_id, cached_options)
|
|
50
50
|
|
|
51
51
|
# Build chart JSON for the view
|
|
52
|
-
@chart_json = build_chart_json(@chart_data)
|
|
52
|
+
@chart_json = build_chart_json(@chart_data, start_date, end_date)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
private
|
|
@@ -89,6 +89,23 @@ module Aviate
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def fetch_unit_types(subscription, start_date, cached_options)
|
|
92
|
+
if Killbill::Aviate::AviateClient.aviate_catalog?(subscription.account_id, cached_options)
|
|
93
|
+
fetch_billing_meter_codes(subscription, cached_options)
|
|
94
|
+
else
|
|
95
|
+
fetch_catalog_phase_unit_types(subscription, start_date, cached_options)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Aviate catalogs record usage against a billing meter code, resolved via the Aviate plugin's
|
|
100
|
+
# own (JWT-authenticated) Catalog API, rather than a standard catalog unit type.
|
|
101
|
+
def fetch_billing_meter_codes(subscription, cached_options)
|
|
102
|
+
Killbill::Aviate::AviateClient.billing_meter_codes_for_plan(subscription.plan_name, subscription.account_id, cached_options)
|
|
103
|
+
rescue StandardError => e
|
|
104
|
+
Rails.logger.warn("Failed to fetch Aviate billing meter codes: #{e.message}")
|
|
105
|
+
[]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def fetch_catalog_phase_unit_types(subscription, start_date, cached_options)
|
|
92
109
|
# NOTE: KillBillClient::Model::Catalog.get_catalog_phase parses the response as a
|
|
93
110
|
# Catalog object by default, but the API actually returns a Phase-shaped payload
|
|
94
111
|
# (it has no `usages` accessor otherwise) - request it as a Phase explicitly.
|
|
@@ -173,14 +190,17 @@ module Aviate
|
|
|
173
190
|
chart_data
|
|
174
191
|
end
|
|
175
192
|
|
|
176
|
-
def build_chart_json(chart_data)
|
|
193
|
+
def build_chart_json(chart_data, period_start = nil, period_end = nil)
|
|
177
194
|
return nil if chart_data.empty?
|
|
178
195
|
|
|
179
196
|
series = chart_data.map do |unit_type, values|
|
|
180
197
|
{ 'name' => unit_type, 'values' => values }
|
|
181
198
|
end
|
|
182
199
|
|
|
183
|
-
{ 'type' => 'TIMELINE', 'data' => series }
|
|
200
|
+
json = { 'type' => 'TIMELINE', 'data' => series }
|
|
201
|
+
json['period_start'] = period_start.to_s if period_start
|
|
202
|
+
json['period_end'] = period_end.to_s if period_end
|
|
203
|
+
json.to_json
|
|
184
204
|
end
|
|
185
205
|
|
|
186
206
|
def fetch_accrued_cost(account_id, cached_options)
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
<div class="kaui-container usage-show">
|
|
2
|
-
<%= render "kaui/components/breadcrumb/breadcrumb"
|
|
2
|
+
<%= render "kaui/components/breadcrumb/breadcrumb", breadcrumbs: [
|
|
3
|
+
{ label: 'Accounts', href: '/accounts' },
|
|
4
|
+
{ label: @account.name.presence || "Account #{@account_id}", href: "/accounts/#{@account_id}" },
|
|
5
|
+
{ label: 'Usage', href: "/aviate/accounts/#{@account_id}/usage" },
|
|
6
|
+
{ label: 'Usage Detail' }
|
|
7
|
+
] %>
|
|
3
8
|
<div class="d-flex" style="gap: 4rem;">
|
|
4
9
|
<%= render :template => 'kaui/layouts/kaui_account_sidebar' %>
|
|
5
10
|
<div class="usage-content" style="max-width: 67.5rem; width: 100%;">
|
data/lib/aviate/client.rb
CHANGED
|
@@ -5,6 +5,8 @@ module Killbill
|
|
|
5
5
|
class AviateClient < KillBillClient::Model::Resource
|
|
6
6
|
KILLBILL_AVIATE_PREFIX = '/plugins/aviate-plugin/v1'
|
|
7
7
|
|
|
8
|
+
CATALOG_STATE_AVIATE = 'CATALOG_STATE_AVIATE'
|
|
9
|
+
|
|
8
10
|
class << self
|
|
9
11
|
def aviate_plugin_available?(options = nil)
|
|
10
12
|
path = "#{KILLBILL_AVIATE_PREFIX}/health/data"
|
|
@@ -17,6 +19,51 @@ module Killbill
|
|
|
17
19
|
[false, e.message.to_s]
|
|
18
20
|
end
|
|
19
21
|
|
|
22
|
+
# Returns true if the account/subscription is billed via an Aviate catalog (as opposed to a
|
|
23
|
+
# standard XML one). Any error (plugin not installed, missing/invalid JWT, network issue, etc.)
|
|
24
|
+
# is treated as "not Aviate" so callers can safely fall back to the standard catalog behavior.
|
|
25
|
+
#
|
|
26
|
+
# JWT authentication is handled by build_request_options, which sets the Authorization header
|
|
27
|
+
# from options[:jwt_token] when present (populated from the session cookie by EngineController).
|
|
28
|
+
def aviate_catalog?(account_id, options = nil)
|
|
29
|
+
path = "#{KILLBILL_AVIATE_PREFIX}/catalog/info"
|
|
30
|
+
request_options = build_request_options(options)
|
|
31
|
+
|
|
32
|
+
response = KillBillClient::API.get path, catalog_params(account_id), request_options
|
|
33
|
+
JSON.parse(response.body)['state'] == CATALOG_STATE_AVIATE
|
|
34
|
+
rescue StandardError
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns the list of billing meter codes configured on the given plan's usage phases.
|
|
39
|
+
# Aviate catalogs record usage against a billing meter code rather than a standard
|
|
40
|
+
# catalog unit type, so this must be used instead of the plain KillBillClient catalog/phase
|
|
41
|
+
# lookup whenever `aviate_catalog?` is true.
|
|
42
|
+
# JWT authentication is handled by build_request_options (see aviate_catalog? above).
|
|
43
|
+
def billing_meter_codes_for_plan(plan_name, account_id, options = nil)
|
|
44
|
+
return [] if plan_name.blank?
|
|
45
|
+
|
|
46
|
+
path = "#{KILLBILL_AVIATE_PREFIX}/catalog/#{plan_name}/plan"
|
|
47
|
+
request_options = build_request_options(options)
|
|
48
|
+
|
|
49
|
+
response = KillBillClient::API.get path, catalog_params(account_id), request_options
|
|
50
|
+
plan = JSON.parse(response.body)
|
|
51
|
+
|
|
52
|
+
codes = []
|
|
53
|
+
Array(plan['phases']).each do |phase|
|
|
54
|
+
Array(phase['usages']).each do |usage|
|
|
55
|
+
Array(usage['tiers']).each do |tier|
|
|
56
|
+
Array(tier['blocks']).each do |block|
|
|
57
|
+
codes << block['billingMeterCode'] if block['billingMeterCode'].present?
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
codes.uniq
|
|
63
|
+
rescue StandardError
|
|
64
|
+
[]
|
|
65
|
+
end
|
|
66
|
+
|
|
20
67
|
def aviate_plugin_installed(options = nil)
|
|
21
68
|
nodes_info = KillBillClient::Model::NodesInfo.nodes_info(options) || []
|
|
22
69
|
plugins_info = nodes_info.empty? ? [] : (nodes_info.first.plugins_info || [])
|
|
@@ -117,18 +164,39 @@ module Killbill
|
|
|
117
164
|
|
|
118
165
|
private
|
|
119
166
|
|
|
167
|
+
def catalog_params(account_id)
|
|
168
|
+
account_id.present? ? { accountId: account_id } : {}
|
|
169
|
+
end
|
|
170
|
+
|
|
120
171
|
def parse_csv_response(body)
|
|
121
172
|
return [] if body.nil? || body.strip.empty?
|
|
122
173
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
174
|
+
# The host_samples endpoint returns a JSON array. Each element has a
|
|
175
|
+
# "samples" field with usage data serialized as flat comma-separated
|
|
176
|
+
# timestamp/value pairs: "ts1,val1,ts2,val2,..." (observed format).
|
|
177
|
+
# Newlines, if ever present, are normalized to commas before parsing.
|
|
178
|
+
parsed = JSON.parse(body)
|
|
179
|
+
return [] unless parsed.is_a?(Array)
|
|
180
|
+
|
|
181
|
+
result = []
|
|
182
|
+
parsed.each do |entry|
|
|
183
|
+
samples_str = entry['samples']
|
|
184
|
+
next if samples_str.nil? || samples_str.strip.empty?
|
|
185
|
+
|
|
186
|
+
tokens = samples_str.tr("\n", ',').split(',').map(&:strip).reject(&:empty?)
|
|
187
|
+
tokens.each_slice(2) do |ts_str, val_str|
|
|
188
|
+
next if ts_str.nil? || val_str.nil?
|
|
189
|
+
|
|
190
|
+
ts = ts_str.to_i
|
|
191
|
+
next if ts.zero?
|
|
192
|
+
|
|
193
|
+
date = Time.at(ts).utc.strftime('%Y-%m-%dT%H:%M:%S')
|
|
194
|
+
result << { date: date, value: val_str.to_f }
|
|
195
|
+
end
|
|
130
196
|
end
|
|
131
|
-
|
|
197
|
+
|
|
198
|
+
result
|
|
199
|
+
rescue StandardError
|
|
132
200
|
[]
|
|
133
201
|
end
|
|
134
202
|
|
data/lib/aviate/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: killbill-aviate
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.4.
|
|
4
|
+
version: 2.4.1.pre.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kill Bill core team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: killbill-assets-ui
|
|
@@ -76,6 +76,7 @@ files:
|
|
|
76
76
|
- README.md
|
|
77
77
|
- Rakefile
|
|
78
78
|
- app/assets/config/aviate_manifest.js
|
|
79
|
+
- app/assets/images/aviate/.keep
|
|
79
80
|
- app/assets/javascripts/aviate/aviate.js
|
|
80
81
|
- app/assets/javascripts/aviate/kiddo/axes.js
|
|
81
82
|
- app/assets/javascripts/aviate/kiddo/charts/line_chart.js
|
|
@@ -124,9 +125,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
124
125
|
version: '0'
|
|
125
126
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
127
|
requirements:
|
|
127
|
-
- - "
|
|
128
|
+
- - ">"
|
|
128
129
|
- !ruby/object:Gem::Version
|
|
129
|
-
version:
|
|
130
|
+
version: 1.3.1
|
|
130
131
|
requirements: []
|
|
131
132
|
rubygems_version: 3.4.10
|
|
132
133
|
signing_key:
|