diff --git a/Gemfile b/Gemfile
index 6e24fc4..e2679fa 100644
--- a/Gemfile
+++ b/Gemfile
@@ -26,6 +26,7 @@ gem "polaris_view_components"
gem "chartkick"
gem "groupdate"
gem "convenient_grouper"
+gem "prophet-rb"
# Importing
gem "csvreader"
diff --git a/Gemfile.lock b/Gemfile.lock
index b940609..9ce5fab 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -108,6 +108,7 @@ GEM
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
chartkick (5.0.4)
+ cmdstan (0.2.2)
coderay (1.1.3)
concurrent-ruby (1.2.2)
connection_pool (2.4.1)
@@ -190,6 +191,7 @@ GEM
racc (~> 1.4)
nokogiri (1.15.4-x86_64-linux)
racc (~> 1.4)
+ numo-narray (0.9.2.1)
orm_adapter (0.5.0)
parallel (1.23.0)
parser (3.2.2.3)
@@ -199,6 +201,10 @@ GEM
polaris_view_components (1.1.0)
rails (>= 5.0.0)
view_component (>= 3.0.0, < 4.0.0)
+ prophet-rb (0.5.0)
+ cmdstan (>= 0.2)
+ numo-narray (>= 0.9.1.7)
+ rover-df
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
@@ -256,6 +262,8 @@ GEM
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.2.6)
+ rover-df (0.3.4)
+ numo-narray (>= 0.9.1.9)
rubocop (1.56.1)
base64 (~> 0.1.1)
json (~> 2.3)
@@ -379,6 +387,7 @@ DEPENDENCIES
letter_opener
pg (~> 1.1)
polaris_view_components
+ prophet-rb
pry-rails
puma (~> 6.0)
rack-timeout
diff --git a/app/controllers/imports/globes_controller.rb b/app/controllers/imports/globes_controller.rb
index 07354a4..c6acd40 100644
--- a/app/controllers/imports/globes_controller.rb
+++ b/app/controllers/imports/globes_controller.rb
@@ -2,9 +2,19 @@ class Imports::GlobesController < ApplicationController
before_action :authenticate_user!
def show
- payments = current_user.payments.pluck(:shop_country, :charge_type).last(30)
- globe_data = payments.map { |c| {countryCode: c[0], reverse: c[1] == "refund"} }
+ @import = current_user.imports.find(params[:import_id])
render json: globe_data
end
+
+ private
+
+ def globe_data
+ payments = @import.payments.pluck(:shop_country, :charge_type).last(80)
+ globe_data = []
+ payments.each do |c|
+ globe_data << {countryCode: c[0], reverse: c[1] == "refund"}
+ end
+ globe_data
+ end
end
diff --git a/app/controllers/imports_controller.rb b/app/controllers/imports_controller.rb
index 93264a7..f9184fd 100644
--- a/app/controllers/imports_controller.rb
+++ b/app/controllers/imports_controller.rb
@@ -15,9 +15,12 @@ def new
end
def create
- @import = current_user.imports.new(source: Import.sources[:csv_file], **import_params)
- @import.user.count_usage_charges_as_recurring = import_params.dig(:user_attributes, :count_usage_charges_as_recurring)
+ @import = current_user.imports.new(
+ source: Import.sources[:csv_file],
+ **import_params.except(:user_attributes)
+ )
if @import.save
+ save_user_attributes
redirect_to @import, notice: "Import successfully created."
else
flash.now[:alert] = "Import failed to create."
@@ -40,7 +43,13 @@ def import_params
params.require(:import).permit(
:import_type,
:payouts_file,
- user_attributes: [:count_usage_charges_as_recurring]
+ user_attributes: [:id, :count_usage_charges_as_recurring]
+ )
+ end
+
+ def save_user_attributes
+ current_user.update(
+ count_usage_charges_as_recurring: import_params.dig(:user_attributes, :count_usage_charges_as_recurring)
)
end
diff --git a/app/controllers/partner_api_credentials_controller.rb b/app/controllers/partner_api_credentials_controller.rb
index 142f123..829033e 100644
--- a/app/controllers/partner_api_credentials_controller.rb
+++ b/app/controllers/partner_api_credentials_controller.rb
@@ -15,9 +15,11 @@ def edit
end
def create
- @partner_api_credential = current_user.build_partner_api_credential(partner_api_credential_params)
-
+ @partner_api_credential = current_user.build_partner_api_credential(
+ partner_api_credential_params.except(:user_attributes)
+ )
if @partner_api_credential.save
+ save_user_attributes
redirect_to edit_partner_api_credential_path(@partner_api_credential), notice: "Partner api credential was successfully created."
else
render :new, status: :unprocessable_entity, notice: "Partner api credential was not created."
@@ -51,12 +53,17 @@ def add_status_messsage_error
@partner_api_credential.errors.add(:base, @partner_api_credential.status_message)
end
- # Only allow a list of trusted parameters through.
+ def save_user_attributes
+ current_user.update(
+ count_usage_charges_as_recurring: partner_api_credential_params.dig(:user_attributes, :count_usage_charges_as_recurring)
+ )
+ end
+
def partner_api_credential_params
params.require(:partner_api_credential).permit(
:access_token,
:organization_id,
- user_attributes: [:count_usage_charges_as_recurring]
+ user_attributes: [:id, :count_usage_charges_as_recurring]
)
end
end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
new file mode 100644
index 0000000..c6f4d79
--- /dev/null
+++ b/app/controllers/users_controller.rb
@@ -0,0 +1,17 @@
+class UsersController < ApplicationController
+ before_action :authenticate_user!
+
+ def update
+ if current_user.update(user_params)
+ redirect_to request.referrer, status: :see_other
+ else
+ redirect_to request.referrer, alert: "Error saving user!", status: :see_other
+ end
+ end
+
+ private
+
+ def user_params
+ params.require(:user).permit(:show_forecasts)
+ end
+end
diff --git a/app/helpers/metrics_helper.rb b/app/helpers/metrics_helper.rb
index d49f402..f9065d3 100644
--- a/app/helpers/metrics_helper.rb
+++ b/app/helpers/metrics_helper.rb
@@ -80,6 +80,7 @@ def metrics_chart_options
download: true,
library: {
pointSize: 6,
+ isStacked: false,
backgroundColor: "transparent",
animation: {
startup: true,
@@ -87,7 +88,7 @@ def metrics_chart_options
easing: "inAndOut"
},
lineWidth: 3,
- colors: ["#5912D5", "rgba(89, 18, 213, 0.2)"],
+ colors: ["#5912D5", "#5912D5"],
explorer: {
keepInBounds: true,
axis: "horizontal",
@@ -101,10 +102,24 @@ def metrics_chart_options
color: "#F1F2F4"
}
},
+ timeline: {
+ tooltipDateFormat: "MMM d, yyyy"
+ },
+ hAxis: {
+ format: "MMM d, y"
+ },
chartArea: {
width: "100%",
height: "80%",
left: "5%"
+ },
+ focusTarget: "category",
+ series: {
+ "1": {
+ visibleInLegend: false,
+ lineWidth: 0,
+ areaOpacity: 0
+ }
}
}
}
diff --git a/app/javascript/controllers/form_controller.js b/app/javascript/controllers/form_controller.js
index 5b370bd..d5b3224 100644
--- a/app/javascript/controllers/form_controller.js
+++ b/app/javascript/controllers/form_controller.js
@@ -4,7 +4,7 @@ import DirtyForm from '../libraries/dirty-form'
export default class extends Controller {
static targets = ['submitButton']
static values = {
- showSaveBar: { type: Boolean, default: true },
+ showSaveBar: { type: Boolean, default: false },
submitButtonTarget: String
}
diff --git a/app/javascript/controllers/globe_controller.js b/app/javascript/controllers/globe_controller.js
index 14f7404..04df72c 100644
--- a/app/javascript/controllers/globe_controller.js
+++ b/app/javascript/controllers/globe_controller.js
@@ -32,7 +32,7 @@ const ARC_ALTITUDES = [0.5, 0.55, 0.55, 0.6, 0.6];
const ARC_DASH_LENGTHS = [1.5, 1.8, 1.9, 1.9, 2, 2.2];
const ARC_SPEEDS = [0.6, 0.65, 0.75, 0.95, 1];
-const ARCS_AT_ONCE = 5;
+const ARCS_AT_ONCE = 8;
const ARC_EMIT_DELAY = 300;
export default class extends Controller {
@@ -295,7 +295,7 @@ export default class extends Controller {
controls.rotateSpeed = 0.2;
controls.zoomSpeed = 0.2;
controls.autoRotate = true;
- controls.autoRotateSpeed = 0.35;
+ controls.autoRotateSpeed = 0.25;
}
// SETUP CAMERA
diff --git a/app/jobs/calculate_metrics_job.rb b/app/jobs/calculate_metrics_job.rb
index 7021547..75486d3 100644
--- a/app/jobs/calculate_metrics_job.rb
+++ b/app/jobs/calculate_metrics_job.rb
@@ -5,7 +5,7 @@ class CalculateMetricsJob < ApplicationJob
def perform(import:)
import.calculate
rescue => e
- import&.failed!
+ import&.fail
raise e
end
end
diff --git a/app/jobs/import_payments_job.rb b/app/jobs/import_payments_job.rb
index 499685f..b862e3c 100644
--- a/app/jobs/import_payments_job.rb
+++ b/app/jobs/import_payments_job.rb
@@ -5,7 +5,7 @@ class ImportPaymentsJob < ApplicationJob
def perform(import:)
import.import
rescue => e
- import&.failed!
+ import&.fail
raise e
end
end
diff --git a/app/models/import.rb b/app/models/import.rb
index 9936bf4..b03fe3a 100644
--- a/app/models/import.rb
+++ b/app/models/import.rb
@@ -43,7 +43,7 @@ def schedule
def import
importing!
- Import::Payments.new(import: self).import!
+ Import::PaymentsProcessor.new(import: self).import!
imported
end
@@ -53,14 +53,42 @@ def imported
def calculate
calculating!
- Import::Metrics.new(import: self).calculate!
+ Import::MetricsProcessor.new(import: self).calculate!
completed!
end
+ def fail
+ failed!
+ payments.delete_all
+ metrics.delete_all
+ end
+
def source_adaptor
csv_file_source? ? Import::Adaptor::CsvFile : Import::Adaptor::ShopifyPaymentsApi
end
+ def import_payments_after_date
+ max_allowed_ago = source_adaptor.const_get(:MAX_HISTORY).ago
+ if user.newest_metric_date.to_i < max_allowed_ago.to_i
+ max_allowed_ago
+ else
+ user.newest_metric_date
+ end
+ end
+
+ def import_metrics_after_date
+ if user.newest_metric_date.present?
+ user.newest_metric_date + 1.day
+ else
+ user.payments.minimum("payment_date")
+ end
+ end
+
+ def import_metrics_before_date
+ # Don't include the latest day, because it may not be complete
+ user.payments.maximum(:payment_date) - 1.day
+ end
+
private
def broadcast_details_update
diff --git a/app/models/import/adaptor/csv_file.rb b/app/models/import/adaptor/csv_file.rb
index fc024ac..9b36a6f 100644
--- a/app/models/import/adaptor/csv_file.rb
+++ b/app/models/import/adaptor/csv_file.rb
@@ -2,10 +2,10 @@
require "csvreader"
class Import::Adaptor::CsvFile
- BATCH_SIZE = 500
+ BATCH_SIZE = 1000
+ MAX_HISTORY = 4.years
CSV_READER_OPTIONS = {
- converters: :all,
header_converters: :symbol,
encoding: "UTF-8"
}.freeze
@@ -57,11 +57,12 @@ class Import::Adaptor::CsvFile
]
}.freeze
- def initialize(import:, created_at_min:)
+ def initialize(import:, import_payments_after_date:)
@import = import
- @created_at_min = created_at_min
+ @import_payments_after_date = import_payments_after_date
@temp_files = {}
+ @prepared_csv_file = prepared_csv_file
end
def fetch_payments
@@ -75,9 +76,9 @@ def batch_size
private
def stream_payments(main_enum)
- CsvHashReader.foreach(prepared_csv_file, **CSV_READER_OPTIONS) do |csv_row|
+ CsvHashReader.foreach(@prepared_csv_file, **CSV_READER_OPTIONS) do |csv_row|
parsed_row = parse(csv_row)
- break if parsed_row[:payment_date] <= @created_at_min
+ break if parsed_row[:payment_date] <= @import_payments_after_date
main_enum.yield parsed_row
end
@@ -122,6 +123,8 @@ def shop_country(csv_row)
end
def prepared_csv_file
+ return @prepared_csv_file if @prepared_csv_file
+
file = @import.payouts_file
if zipped?(file.content_type)
extracted_zip_file(ActiveStorage::Blob.service.path_for(file.key))
diff --git a/app/models/import/adaptor/shopify_payments_api.rb b/app/models/import/adaptor/shopify_payments_api.rb
index 468758b..9b05dc3 100644
--- a/app/models/import/adaptor/shopify_payments_api.rb
+++ b/app/models/import/adaptor/shopify_payments_api.rb
@@ -5,8 +5,9 @@
class Import::Adaptor::ShopifyPaymentsApi
include ShopifyPartnerAPI
- THROTTLE_MIN_TIME_PER_CALL = 0.3
BATCH_SIZE = 100
+ MAX_HISTORY = 7.days
+ THROTTLE_MIN_TIME_PER_CALL = 0.3.seconds
API_REVENUE_TYPES = {
"recurring_revenue" => [
@@ -36,11 +37,11 @@ class Import::Adaptor::ShopifyPaymentsApi
]
}.freeze
- def initialize(import:, created_at_min:)
+ def initialize(import:, import_payments_after_date:)
@import = import
- @created_at_min = created_at_min.strftime("%Y-%m-%dT%H:%M:%S.%L%z")
+ @import_payments_after_date = import_payments_after_date.strftime("%Y-%m-%dT%H:%M:%S.%L%z")
- @context = @import.partner_api_credential.context
+ @context = import.partner_api_credential.context
@cursor = ""
@throttle_start_time = Time.zone.now
end
@@ -61,7 +62,7 @@ def stream_payments(main_enum)
while has_next_page
@throttle_start_time = throttle(@throttle_start_time)
- results = fetch_from_api(@cursor, @created_at_min)
+ results = fetch_from_api(@cursor)
break if results.data.nil?
transactions = results.data.transactions.edges
@@ -74,10 +75,10 @@ def stream_payments(main_enum)
end
end
- def fetch_from_api
+ def fetch_from_api(cursor)
results = ShopifyPartnerAPI.client.query(
Graphql::TransactionsQuery,
- variables: {createdAtMin: @created_at_min, cursor: @cursor, first: batch_size},
+ variables: {cursor: cursor, createdAtMin: @import_payments_after_date, first: batch_size},
context: @context
)
handle_error(results.errors) if results.errors.any?
diff --git a/app/models/import/metrics.rb b/app/models/import/metrics_processor.rb
similarity index 68%
rename from app/models/import/metrics.rb
rename to app/models/import/metrics_processor.rb
index b389e78..0cfd536 100644
--- a/app/models/import/metrics.rb
+++ b/app/models/import/metrics_processor.rb
@@ -1,15 +1,16 @@
-class Import::Metrics
+class Import::MetricsProcessor
def initialize(import:)
@import = import
- @user = @import.user
- @import_from, @import_to = import_dates
+ @user = import.user
+ @import_from = import.import_metrics_after_date
+ @import_to = import.import_metrics_before_date
end
def calculate!
return if @import_from.blank? || @import_to.blank?
calculate_new_metrics
rescue => error
- @import&.failed!
+ @import&.fail
raise error
end
@@ -32,7 +33,7 @@ def calculate_new_metrics
metrics << new_metric_from(calculator: calculator) if calculator.has_metrics?
end
end
- Metric.import!(metrics.flatten.compact, validate: false, no_returning: true)
+ Metric.import!(metrics, validate: false, no_returning: true) if metrics.present?
@import.touch
end
end
@@ -42,8 +43,9 @@ def app_titles_for(date:, charge_type:)
end
def new_metric_from(calculator:)
- @user.metrics.new(
- import: @import,
+ {
+ user_id: @user.id,
+ import_id: @import.id,
metric_date: calculator.date,
charge_type: calculator.charge_type,
app_title: calculator.app_title,
@@ -57,19 +59,6 @@ def new_metric_from(calculator:)
lifetime_value: calculator.lifetime_value,
repeat_customers: calculator.repeat_customers,
repeat_vs_new_customers: calculator.repeat_vs_new_customers
- )
- end
-
- def import_dates
- # TODO: Returns dates for all payments, instead of just this Imports payments, because of partial dates.
- latest_calculated_metric = @user.metrics.order("metric_date").last
- import_from = if latest_calculated_metric.present?
- latest_calculated_metric.metric_date + 1.day
- else
- @user.payments.minimum("payment_date")
- end
- last_imported_payment = @user.payments.maximum(:payment_date)
- import_to = last_imported_payment - 1.day
- [import_from, import_to]
+ }
end
end
diff --git a/app/models/import/payments.rb b/app/models/import/payments_processor.rb
similarity index 55%
rename from app/models/import/payments.rb
rename to app/models/import/payments_processor.rb
index 6ac8d2a..4b8996f 100644
--- a/app/models/import/payments.rb
+++ b/app/models/import/payments_processor.rb
@@ -1,16 +1,16 @@
-class Import::Payments
+class Import::PaymentsProcessor
def initialize(import:)
@import = import
- @user = @import.user
- @created_at_min = @user.calculate_from_date
- @source_adaptor = @import.source_adaptor.new(import: @import, created_at_min: @created_at_min)
+ @user = import.user
+ @import_payments_after_date = import.import_payments_after_date
+ @source_adaptor = import.source_adaptor.new(import: import, import_payments_after_date: @import_payments_after_date)
end
def import!
- @user.clear_old_payments!
+ @user.clear_old_payments!(after: @import_payments_after_date)
import_new_payments
rescue => error
- @import&.failed!
+ @import&.fail
raise error
end
@@ -18,38 +18,39 @@ def import!
def import_new_payments
fetched_payments.each_slice(@source_adaptor.batch_size) do |batch|
- payments = batch.map do |transaction|
- break if transaction[:payment_date] <= @created_at_min
+ payments = []
+ batch.each do |transaction|
+ next if transaction[:payment_date] <= @import_payments_after_date
next if transaction[:charge_type].nil?
next if transaction[:shop].nil?
next if transaction[:revenue].zero?
- new_payment(transaction)
- end.compact
+ payments << new_payment(transaction)
+ end
- Payment.import!(payments, validate: false, no_returning: true)
+ Payment.import!(payments.compact, validate: false, no_returning: true) if payments.present?
@import.touch
end
end
- private
-
def fetched_payments
@source_adaptor.fetch_payments
end
def new_payment(payment)
payment[:charge_type] = adjust_usage_charge_type(payment) if payment[:charge_type] == "usage_revenue"
-
- @user.payments.new(
- import: @import,
+ # Note to self: Do not refactor to payment.new objects
+ # It grows memory like crazy when processing large files
+ {
+ user_id: @user.id,
+ import_id: @import.id,
payment_date: payment[:payment_date],
charge_type: payment[:charge_type],
revenue: payment[:revenue],
app_title: payment[:app_title],
shop: payment[:shop],
shop_country: payment[:shop_country]
- )
+ }
end
def adjust_usage_charge_type(charge_type)
diff --git a/app/models/metric.rb b/app/models/metric.rb
index 97857c6..0ac71ad 100644
--- a/app/models/metric.rb
+++ b/app/models/metric.rb
@@ -1,8 +1,10 @@
+require "prophet-rb"
+
class Metric < ApplicationRecord
belongs_to :user
belongs_to :import
- PERIODS = [7, 28, 29, 30, 31, 90, 180, 365].freeze
+ PERIODS = [1, 7, 28, 29, 30, 31, 90, 180, 365].freeze
PERIODS_AGO = [1, 2, 3, 6, 12].freeze
CHARGE_TYPES = ["recurring_revenue", "onetime_revenue", "affiliate_revenue", "refund"].freeze
@@ -50,6 +52,13 @@ def chart_data(date, period, calculation, column)
metrics.sort_by { |h| h[0].to_datetime }
end
+ def forecast_for_chart_data(chart_data)
+ Prophet.forecast(chart_data, count: 3)
+ rescue ArgumentError
+ # Forecasts require a minimum of 10 data points
+ []
+ end
+
# Build a hash of dates containing date ranges,
# for each period between the first date and the date selected
def group_options(date, first_date, period)
diff --git a/app/models/metric/tile_presenter.rb b/app/models/metric/tile_presenter.rb
index a6aec8d..9f9f3b9 100644
--- a/app/models/metric/tile_presenter.rb
+++ b/app/models/metric/tile_presenter.rb
@@ -42,7 +42,32 @@ def period_ago_change(period_ago)
end
def chart_data
- metrics = @filter.user_metrics_by_app.by_optional_charge_type(@charge_type)
- metrics.chart_data(@filter.date, @filter.period, @calculation, @column)
+ chart_data = basic_chart_data
+
+ if @filter.show_forecasts?
+ forecast_data = forecast_chart_data(chart_data)
+ return chart_data if forecast_data[:data].empty?
+ chart_data << forecast_data
+ end
+ chart_data
+ end
+
+ private
+
+ def basic_chart_data
+ metrics_chart = fetch_metrics_chart
+ [{name: @display, data: metrics_chart}]
+ end
+
+ def forecast_chart_data(chart_data)
+ forecast_data = Metric.forecast_for_chart_data(fetch_metrics_chart)
+ {name: "Forecast", data: forecast_data}
+ end
+
+ def fetch_metrics_chart
+ @fetch_metrics_chart ||= begin
+ metrics = @filter.user_metrics_by_app.by_optional_charge_type(@charge_type)
+ metrics.chart_data(@filter.date, @filter.period, @calculation, @column).to_h
+ end
end
end
diff --git a/app/models/metric/tiles_filter.rb b/app/models/metric/tiles_filter.rb
index db94dff..2a9b2f0 100644
--- a/app/models/metric/tiles_filter.rb
+++ b/app/models/metric/tiles_filter.rb
@@ -10,7 +10,7 @@ def initialize(user:, params:)
attr_reader :user, :date, :charge_type, :chart, :period, :app
- delegate :oldest_metric_date, :newest_metric_date_or_today, to: :user
+ delegate :show_forecasts?, :oldest_metric_date, :newest_metric_date_or_today, to: :user
def app_titles
@user.app_titles(@charge_type)
@@ -53,7 +53,7 @@ def to_param
private
def previous_date
- @date - @period.days + 1
+ @date - @period.days
end
def tiles_presenter
diff --git a/app/models/partner_api_credential.rb b/app/models/partner_api_credential.rb
index 769b68f..c109e34 100644
--- a/app/models/partner_api_credential.rb
+++ b/app/models/partner_api_credential.rb
@@ -21,6 +21,8 @@ class PartnerApiCredential < ApplicationRecord
validates :status, presence: true, inclusion: {in: statuses.keys}
validate :credentials_have_access, on: [:create, :update]
+ accepts_nested_attributes_for :user, update_only: true
+
def context
{
access_token: access_token,
diff --git a/app/models/payment.rb b/app/models/payment.rb
index 33e1521..bc0bde6 100644
--- a/app/models/payment.rb
+++ b/app/models/payment.rb
@@ -1,5 +1,4 @@
class Payment < ApplicationRecord
- YEARS_TO_IMPORT = 4.years.freeze
UNKNOWN_APP_TITLE = "Unknown".freeze
belongs_to :user
@@ -9,9 +8,5 @@ class << self
def by_optional_app_title(app_title)
app_title.blank? ? all : where(app_title: app_title)
end
-
- def default_start_date
- YEARS_TO_IMPORT.ago.to_date
- end
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 8ffef7f..eb107bc 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -29,12 +29,8 @@ def oldest_metric_date
metrics.minimum("metric_date")
end
- def calculate_from_date
- @calculate_from_date ||= newest_metric_date.presence || Payment.default_start_date
- end
-
- def clear_old_payments!
- payments.where("payment_date > ?", calculate_from_date).delete_all
+ def clear_old_payments!(after:)
+ payments.where("payment_date > ?", after).delete_all
end
# TODO: DRY the following methods up
diff --git a/app/views/imports/_form.html.erb b/app/views/imports/_form.html.erb
index 36e6857..e23adbb 100644
--- a/app/views/imports/_form.html.erb
+++ b/app/views/imports/_form.html.erb
@@ -3,8 +3,7 @@
model: import,
builder: Polaris::FormBuilder,
data: {
- controller: "form",
- form_show_save_bar_value: false
+ controller: "form"
}
) do |form| %>
@@ -32,6 +31,9 @@
label_hidden: false,
accept: Import::ACCEPTED_FILE_TYPES.join(","),
multiple: false,
+ data: {
+ action: "ondrop@window->form#markAsDirty"
+ }
) %>
<% end %>
diff --git a/app/views/imports/_table.html.erb b/app/views/imports/_table.html.erb
index 1ceb62a..1f828fd 100644
--- a/app/views/imports/_table.html.erb
+++ b/app/views/imports/_table.html.erb
@@ -3,7 +3,7 @@
<% table.with_column(t('imports.imported_at', date: nil)) do |import| %>
<%= polaris_link(url: import_path(import), monochrome: true, no_underline: true) do %>
- <%= polaris_text_style(variation: :strong) { import.created_at.to_fs(:long) }%>
+ <%= polaris_text_style(variation: :strong) { import.created_at.to_fs(:short) }%>
<% end %>
<% end %>
diff --git a/app/views/imports/index.html.erb b/app/views/imports/index.html.erb
index be0aad0..fd618fd 100644
--- a/app/views/imports/index.html.erb
+++ b/app/views/imports/index.html.erb
@@ -6,12 +6,23 @@
<% page.with_primary_action(url: new_import_path, disabled: @imports.in_progress.any?) { "New import" } %>
<% page.with_action_group(
- title: t("actions.more_actions"),
- actions: [
- { content: t("rename_apps.rename"), url: rename_apps_path },
- { content: t("actions.delete_all", resource: resource_name_for(Import, true)), url: destroy_all_imports_path, data: { turbo_method: :delete, turbo_confirm: t("imports.confirm_destroy") }, destructive: true }
- ]
- ) %>
+ title: t("actions.more_actions"),
+ actions: [
+ {
+ content: t("rename_apps.rename"),
+ url: rename_apps_path
+ },
+ {
+ content: t("actions.delete_all", resource: resource_name_for(Import, true)),
+ destructive: true,
+ data: {
+ controller: "polaris",
+ target: "#destroy-modal",
+ action: "polaris#openModal"
+ }
+ }
+ ]
+ ) %>
<% if @imports.any? %>
@@ -30,3 +41,11 @@
<%= render "shared/empty_state", resource: Import %>
<% end %>
<% end %>
+
+<%= render "modals/destroy",
+ id: "destroy-modal",
+ url: destroy_all_imports_path,
+ title: t("actions.delete", resource: resource_name_for(Import, true)) + "?",
+ message: t("imports.confirm_destroy"),
+ primary_action_text: t("actions.delete", resource: resource_name_for(Import, true))
+%>
diff --git a/app/views/imports/show.html.erb b/app/views/imports/show.html.erb
index 55cae6c..95681ad 100644
--- a/app/views/imports/show.html.erb
+++ b/app/views/imports/show.html.erb
@@ -9,12 +9,12 @@
secondary_actions: [
{
content: t("actions.delete", resource: resource_name_for(Import)),
- url: @import,
destructive: true,
data: {
- turbo_method: :delete,
- turbo_confirm: t("imports.confirm_destroy")
- },
+ controller: "polaris",
+ target: "#destroy-modal",
+ action: "polaris#openModal"
+ }
}
],
) do |page| %>
@@ -41,3 +41,11 @@
globe_target: "container",
globe_fetch_url_value: import_globe_path(@import),
} %>
+
+<%= render "modals/destroy",
+ id: "destroy-modal",
+ url: @import,
+ title: t("actions.delete", resource: resource_name_for(Import)) + "?",
+ message: t("imports.confirm_destroy"),
+ primary_action_text: t("actions.delete", resource: resource_name_for(Import))
+%>
diff --git a/app/views/metrics/_forecasts_form.html.erb b/app/views/metrics/_forecasts_form.html.erb
new file mode 100644
index 0000000..3b82f50
--- /dev/null
+++ b/app/views/metrics/_forecasts_form.html.erb
@@ -0,0 +1,20 @@
+ <%= form_with(
+ model: user,
+ builder: Polaris::FormBuilder,
+ data: {
+ controller: "submittable",
+ submittable_target: "form",
+ action: "change->submittable#submit"
+ }
+ ) do |form| %>
+
+ <%= polaris_tooltip(text: t("user.show_forecasts_help_text")) do %>
+ <%= form.polaris_check_box(
+ :show_forecasts,
+ checked: user.show_forecasts?,
+ label: t('user.show_forecasts'),
+ ) %>
+
+ <% end %>
+
+<% end %>
diff --git a/app/views/metrics/show.html.erb b/app/views/metrics/show.html.erb
index 061db20..5190be9 100644
--- a/app/views/metrics/show.html.erb
+++ b/app/views/metrics/show.html.erb
@@ -7,12 +7,24 @@
<%= turbo_frame_tag :metrics, data: {
controller: "chartkick",
- action: "turbo:load@window->chartkick#createChart"
+ action: "turbo:load@window->chartkick#createChart turbo:frame-load->chartkick#createChart"
} do %>
<%= polaris_vertical_stack(gap: "5") do %>
- <%= render "filter", filter: @filter, app_titles: @app_titles %>
+ <%= polaris_stack(alignment: :center) do |stack| %>
+
+ <% stack.with_item(fill:true) do %>
+ <%= render "filter", filter: @filter, app_titles: @app_titles %>
+ <% end %>
+
+ <% stack.with_item do %>
+ <%= render "forecasts_form", user: current_user %>
+ <% end %>
+
+ <% end %>
+
+
<% if @filter.has_metrics? %>
diff --git a/app/views/modals/_destroy.html.erb b/app/views/modals/_destroy.html.erb
new file mode 100644
index 0000000..e933523
--- /dev/null
+++ b/app/views/modals/_destroy.html.erb
@@ -0,0 +1,15 @@
+<%= polaris_modal(id: id, title: title) do |modal| %>
+
+ <%= polaris_text_container do %>
+
<%= message %>
+ <% end %> + + <% modal.with_primary_action( + destructive: true, + url: url, + data: {turbo_method: :delete} + ) { primary_action_text } %> + + <% modal.with_secondary_action(data: {action: "polaris-modal#close"}) { t("actions.cancel") } %> + +<% end %> diff --git a/app/views/partner_api_credentials/_form.html.erb b/app/views/partner_api_credentials/_form.html.erb index a8e0f66..6fc8ceb 100644 --- a/app/views/partner_api_credentials/_form.html.erb +++ b/app/views/partner_api_credentials/_form.html.erb @@ -3,8 +3,7 @@ model: partner_api_credential, builder: Polaris::FormBuilder, data: { - controller: "form", - form_show_save_bar_value: false + controller: "form" } ) do |form| %> diff --git a/app/views/partner_api_credentials/edit.html.erb b/app/views/partner_api_credentials/edit.html.erb index 41592dc..c89044b 100644 --- a/app/views/partner_api_credentials/edit.html.erb +++ b/app/views/partner_api_credentials/edit.html.erb @@ -4,15 +4,16 @@ narrow_width: true, title: t(".title"), subtitle: t(".subtitle"), + back_url: imports_path, secondary_actions: [ { content: t("actions.delete", resource: nil), - url: partner_api_credential_path, destructive: true, data: { - turbo_method: :delete, - turbo_confirm: t(".confirm_destroy") - }, + controller: "polaris", + target: "#destroy-modal", + action: "polaris#openModal" + } } ], ) do |page| %> @@ -24,3 +25,11 @@ <%= render "form", partner_api_credential: @partner_api_credential %> <% end %> + +<%= render "modals/destroy", + id: "destroy-modal", + url: partner_api_credential_path, + title: t("actions.delete", resource: resource_name_for(PartnerApiCredential, true)) + "?", + message: t(".confirm_destroy"), + primary_action_text: t("actions.delete", resource: nil) +%> diff --git a/app/views/partner_api_credentials/new.html.erb b/app/views/partner_api_credentials/new.html.erb index f8ae14a..5dc8417 100644 --- a/app/views/partner_api_credentials/new.html.erb +++ b/app/views/partner_api_credentials/new.html.erb @@ -3,7 +3,8 @@ <%= polaris_page( narrow_width: true, title: t(".title"), - subtitle: t(".subtitle", instructions_link: link_to("wiki article for instructions", "https://github.com/forsbergplustwo/partner-metrics/wiki/How-to-create-your-Shopify-Partner-API-credentials", target: "_blank")).html_safe + subtitle: t(".subtitle", instructions_link: link_to("wiki article for instructions", "https://github.com/forsbergplustwo/partner-metrics/wiki/How-to-create-your-Shopify-Partner-API-credentials", target: "_blank")).html_safe, + back_url: request.referer, ) do |page| %> <% page.with_title_metadata do %> diff --git a/app/views/summarys/_monthly.html.erb b/app/views/summarys/_monthly.html.erb index 38cf174..61775ed 100644 --- a/app/views/summarys/_monthly.html.erb +++ b/app/views/summarys/_monthly.html.erb @@ -1,9 +1,7 @@ <%= polaris_index_table(rows) do |table| %> <% table.with_column(t('.date', date: nil)) do |row| %> -