From 3e095b25426abda2d97fe7e15af16fdcd798129d Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 12 May 2026 09:00:21 +0700 Subject: [PATCH 1/2] Code refator and fix bug form validate --- .../kanaui/kiddo/kiddo_initialize.js | 25 ++++++++- app/assets/stylesheets/kanaui/kanaui.css | 14 +++++ .../kanaui/dashboard_controller.rb | 10 +++- app/controllers/kanaui/engine_controller.rb | 18 +++++- app/controllers/kanaui/reports_controller.rb | 24 +++++--- app/views/kanaui/dashboard/index.html.erb | 2 +- .../layouts/kanaui_application.html.erb | 26 ++++----- app/views/kanaui/reports/_form.html.erb | 55 ++++++++++++++----- .../kanaui/reports/_reports_table.html.erb | 32 +++++------ app/views/kanaui/reports/edit.html.erb | 2 +- app/views/kanaui/reports/index.html.erb | 9 ++- app/views/kanaui/reports/new.html.erb | 2 +- config/locales/en.bootstrap.yml | 51 +++++++++++++++++ 13 files changed, 205 insertions(+), 65 deletions(-) diff --git a/app/assets/javascripts/kanaui/kiddo/kiddo_initialize.js b/app/assets/javascripts/kanaui/kiddo/kiddo_initialize.js index 85b8332..4a1ccef 100644 --- a/app/assets/javascripts/kanaui/kiddo/kiddo_initialize.js +++ b/app/assets/javascripts/kanaui/kiddo/kiddo_initialize.js @@ -1,4 +1,26 @@ (function (d3, $, window, document, undefined) { + function errorMessage(error) { + if (error && error.responseText) { + try { + var response = JSON.parse(error.responseText); + if (response.message) { + return response.message; + } + } catch (ex) { + return error.responseText; + } + } + + return error && error.message ? error.message : String(error); + } + + function renderError(message) { + var escapedMessage = $("
").text(message).html(); + $("#chartAnchor").prepend( + '" + ); + } + $(document).ready(function () { if ($("#chartAnchor").length == 0) { return; @@ -10,7 +32,8 @@ var renderer = new Kiddo.Renderer("#chartAnchor"); if (error) { - ajaxErrorAlert(error); + var message = errorMessage(error); + renderError(message); return renderer.noData(); } diff --git a/app/assets/stylesheets/kanaui/kanaui.css b/app/assets/stylesheets/kanaui/kanaui.css index fc3cd73..b5a4bfa 100644 --- a/app/assets/stylesheets/kanaui/kanaui.css +++ b/app/assets/stylesheets/kanaui/kanaui.css @@ -329,11 +329,25 @@ /* app/views/kanaui/reports/index.html.erb */ +.kanaui-flash-container { + margin: 1rem 0 1.5rem; + position: relative; + z-index: 1; +} + +.kanaui-flash-container .alert { + margin-bottom: 0.75rem; +} + .kanaui-reports-index .configured-reports { max-width: 80rem; width: 100%; } +.kanaui-reports-index .kanaui-report-notice { + margin-bottom: 1rem; +} + .kanaui-reports-index .configured-reports .configured-reports-header { display: flex; align-items: start; diff --git a/app/controllers/kanaui/dashboard_controller.rb b/app/controllers/kanaui/dashboard_controller.rb index bdfd1c5..20d4ec6 100644 --- a/app/controllers/kanaui/dashboard_controller.rb +++ b/app/controllers/kanaui/dashboard_controller.rb @@ -19,9 +19,13 @@ def index @reports = JSON.parse(raw_reports) @report = current_report(@reports) || {} - # If no report name is provided, redirect to the default (second) report - if @raw_name.blank? && @reports.is_a?(Array) && @reports[1].present? - default_name = @reports[1]['reportName'] + # If no report name is provided, redirect to a default report. + # Prefer the historical default (second report) when present, but fall back + # to the first report so a single configured report still renders the + # dashboard controls and chart area. + default_report = @reports[1] || @reports[0] if @reports.is_a?(Array) + if @raw_name.blank? && default_report.present? + default_name = default_report['reportName'] query_params = { start_date: @start_date, end_date: @end_date, name: default_name, diff --git a/app/controllers/kanaui/engine_controller.rb b/app/controllers/kanaui/engine_controller.rb index 26dc810..92f18ba 100644 --- a/app/controllers/kanaui/engine_controller.rb +++ b/app/controllers/kanaui/engine_controller.rb @@ -27,8 +27,22 @@ def options_for_klient end rescue_from(KillBillClient::API::ResponseError) do |killbill_exception| - flash[:error] = "Error while communicating with the Kill Bill server: #{as_string(killbill_exception)}" - redirect_to dashboard_index_path + error_message = I18n.t('kanaui.errors.killbill_communication', error: as_string(killbill_exception)) + + if json_request? + render json: { message: error_message }, status: killbill_exception.response.code.to_i + else + flash[:error] = error_message + redirect_to dashboard_index_path + end + end + + def json_request? + request.format.json? || params[:format] == 'json' || dashboard_reports_json_request? + end + + def dashboard_reports_json_request? + controller_name == 'dashboard' && action_name == 'reports' && params[:format].blank? end def as_string(e) diff --git a/app/controllers/kanaui/reports_controller.rb b/app/controllers/kanaui/reports_controller.rb index b103616..fa609eb 100644 --- a/app/controllers/kanaui/reports_controller.rb +++ b/app/controllers/kanaui/reports_controller.rb @@ -4,6 +4,7 @@ module Kanaui class ReportsController < Kanaui::EngineController def index @reports = JSON.parse(Kanaui::DashboardHelper::DashboardApi.available_reports(options_for_klient)).map(&:deep_symbolize_keys) + @report_notice = report_notice_from_params end def new @@ -13,8 +14,7 @@ def new def create Kanaui::DashboardHelper::DashboardApi.create_report(report_from_params.to_json, options_for_klient) - flash[:notice] = 'Report successfully created' - redirect_to action: :index + redirect_to_index_with_notice(:created) end def edit @@ -26,26 +26,34 @@ def edit def update Kanaui::DashboardHelper::DashboardApi.update_report(params.require(:id), report_from_params.to_json, options_for_klient) - flash[:notice] = 'Report successfully updated' - redirect_to action: :index + redirect_to_index_with_notice(:updated) end def refresh Kanaui::DashboardHelper::DashboardApi.refresh_report(params.require(:id), options_for_klient) - flash[:notice] = 'Report refresh successfully scheduled' - redirect_to action: :index + redirect_to_index_with_notice(:refresh_scheduled) end def destroy Kanaui::DashboardHelper::DashboardApi.delete_report(params.require(:id), options_for_klient) - flash[:notice] = 'Report successfully deleted' - redirect_to action: :index + redirect_to_index_with_notice(:deleted) end private + def report_notice_from_params + notice_key = params[:report_notice].presence + return nil if notice_key.blank? + + I18n.t("kanaui.reports.notices.#{notice_key}", default: nil) + end + + def redirect_to_index_with_notice(notice_key) + redirect_to action: :index, report_notice: notice_key + end + def report_from_params { reportName: params[:report_name], diff --git a/app/views/kanaui/dashboard/index.html.erb b/app/views/kanaui/dashboard/index.html.erb index ba9ea43..910b94d 100644 --- a/app/views/kanaui/dashboard/index.html.erb +++ b/app/views/kanaui/dashboard/index.html.erb @@ -261,7 +261,7 @@
<%= params[:name] -%>
<% end %> -
  • <%= link_to 'SQL query', kanaui_engine.reports_path(params.to_h.merge(:sql_only => true)) %>
  • +
  • <%= link_to t('kanaui.dashboard.advanced_controls.sql_query'), kanaui_engine.reports_path(params.to_h.merge(:sql_only => true)) %>
  • diff --git a/app/views/kanaui/layouts/kanaui_application.html.erb b/app/views/kanaui/layouts/kanaui_application.html.erb index e060dfb..db880d1 100644 --- a/app/views/kanaui/layouts/kanaui_application.html.erb +++ b/app/views/kanaui/layouts/kanaui_application.html.erb @@ -11,24 +11,20 @@
    <%- # :alert used by devise -%> - <% [:error, :alert].each do |key| %> - <% if flash[key] %> -
    -
    -
    <%= flash[key] %>
    -
    -
    + <% if flash[:error] || flash[:alert] || flash[:notice] %> +
    + <% [:error, :alert].each do |key| %> + <% if flash[key] %> + + <% end %> <% end %> - <% end %> - <% if flash[:notice] %> -
    -
    -
    <%= flash[:notice] %>
    -
    -
    + <% if flash[:notice] %> +
    <%= flash[:notice] %>
    + <% end %> +
    <% end %> <%= yield %> -
    +
    diff --git a/app/views/kanaui/reports/_form.html.erb b/app/views/kanaui/reports/_form.html.erb index ccfaf55..d0e5cc8 100644 --- a/app/views/kanaui/reports/_form.html.erb +++ b/app/views/kanaui/reports/_form.html.erb @@ -1,56 +1,59 @@ -<%= form_tag @report[:reportName].blank? ? url_for(:controller => :reports, :action => :create) : report_path(@report[:reportName]), :method => (@report[:reportName].blank? ? :post : :put), :class => 'form-horizontal' do %> +<%= form_tag @report[:reportName].blank? ? url_for(:controller => :reports, :action => :create) : report_path(@report[:reportName]), :method => (@report[:reportName].blank? ? :post : :put), :class => 'form-horizontal', :id => 'kanaui-report-form' do %>
    - <%= label_tag :report_name, 'Report name', :class => 'col-sm-3 control-label' %> + <%= label_tag :report_name, t('kanaui.reports.form.report_name'), :class => 'col-sm-3 control-label' %>
    <%= text_field_tag :report_name, @report[:reportName], :class => 'form-control', :disabled => !@report[:reportName].blank?, :read_only => !@report[:reportName].blank? %>
    - <%= label_tag :report_pretty_name, 'Pretty name', :class => 'col-sm-3 control-label' %> + <%= label_tag :report_pretty_name, t('kanaui.reports.form.pretty_name'), :class => 'col-sm-3 control-label' %>
    <%= text_field_tag :report_pretty_name, @report[:reportPrettyName], :class => 'form-control' %>
    - <%= label_tag :report_type, 'Type', :class => 'col-sm-3 control-label' %> + <%= label_tag :report_type, t('kanaui.reports.form.type'), :class => 'col-sm-3 control-label' %>
    <%= select_tag :report_type, options_for_select(%w(TIMELINE COUNTERS TABLE), @report[:reportType] || 'TABLE'), :class => 'form-control' %>
    - <%= label_tag :source_table_name, 'Source', :class => 'col-sm-3 control-label' %> + <%= label_tag :source_table_name, t('kanaui.reports.form.source'), :class => 'col-sm-3 control-label' %>
    <%= text_field_tag :source_table_name, @report[:sourceTableName], :class => 'form-control' %>
    - <%= label_tag :source_name, 'Source name', :class => 'col-sm-3 control-label' %> + <%= label_tag :source_name, t('kanaui.reports.form.source_name'), :class => 'col-sm-3 control-label' %>
    <%= text_field_tag :source_name, @report[:sourceName], :class => 'form-control' %> - Must match a database configuration entry in the Analytics plugin + <%= t('kanaui.reports.form.source_name_help') %>
    - <%= label_tag :source_query, 'SQL query', :class => 'col-sm-3 control-label' %> + <%= label_tag :source_query, t('kanaui.reports.form.source_query'), :class => 'col-sm-3 control-label' %>
    <%= text_area_tag :source_query, @report[:sourceQuery], rows: 10, cols: 25, :class => 'form-control' %> - Don't add a trailing ; + <%= t('kanaui.reports.form.source_query_help') %> +
    - <%= label_tag :refresh_procedure_name, 'Refresh procedure', :class => 'col-sm-3 control-label' %> + <%= label_tag :refresh_procedure_name, t('kanaui.reports.form.refresh_procedure'), :class => 'col-sm-3 control-label' %>
    <%= text_field_tag :refresh_procedure_name, @report[:refreshProcedureName], :class => 'form-control' %>
    - <%= label_tag :refresh_frequency, 'Refresh frequency', :class => 'col-sm-3 control-label' %> + <%= label_tag :refresh_frequency, t('kanaui.reports.form.refresh_frequency'), :class => 'col-sm-3 control-label' %>
    <%= select_tag :refresh_frequency, options_for_select(%w(HOURLY DAILY), @report[:refreshFrequency]), :class => 'form-control', :include_blank => true %>
    - <%= label_tag :refresh_hour_of_day_gmt, 'Refresh hour (GMT)', :class => 'col-sm-3 control-label' %> + <%= label_tag :refresh_hour_of_day_gmt, t('kanaui.reports.form.refresh_hour_gmt'), :class => 'col-sm-3 control-label' %>
    <%= number_field_tag :refresh_hour_of_day_gmt, @report[:refreshHourOfDayGmt], {:min => 1, :max => 23, :class => 'form-control'} %>
    @@ -58,7 +61,7 @@
    <%= render "kaui/components/button/button", { - label: 'Close', + label: t('kanaui.actions.close'), variant: "outline-secondary d-inline-flex align-items-center gap-1", type: "button", html_class: "kaui-button custom-hover mx-2", @@ -67,10 +70,34 @@ } } %> <%= render "kaui/components/button/button", { - label: 'Save', + label: t('kanaui.actions.save'), variant: "outline-secondary d-inline-flex align-items-center gap-1", type: "submit", html_class: "kaui-dropdown custom-hover" } %>
    <% end %> + +<%= javascript_tag do %> + (function () { + var form = document.getElementById('kanaui-report-form'); + if (!form) { + return; + } + + form.addEventListener('submit', function (event) { + var sourceName = document.getElementById('source_name').value.trim(); + var sourceTableName = document.getElementById('source_table_name').value.trim(); + var sourceQuery = document.getElementById('source_query').value.trim(); + var error = document.getElementById('source-configuration-error'); + + if (sourceName && !!sourceTableName === !!sourceQuery) { + event.preventDefault(); + error.style.display = 'block'; + document.getElementById(sourceTableName ? 'source_query' : 'source_table_name').focus(); + } else { + error.style.display = 'none'; + } + }); + })(); +<% end %> diff --git a/app/views/kanaui/reports/_reports_table.html.erb b/app/views/kanaui/reports/_reports_table.html.erb index ce29499..4efa4b3 100644 --- a/app/views/kanaui/reports/_reports_table.html.erb +++ b/app/views/kanaui/reports/_reports_table.html.erb @@ -1,16 +1,16 @@ - - - - - - - - - - + + + + + + + + + + @@ -25,7 +25,7 @@ <% end %> diff --git a/app/views/kanaui/reports/edit.html.erb b/app/views/kanaui/reports/edit.html.erb index 349081a..b10981d 100644 --- a/app/views/kanaui/reports/edit.html.erb +++ b/app/views/kanaui/reports/edit.html.erb @@ -9,7 +9,7 @@ - Update report + <%= t('kanaui.reports.edit.title') %> <%= render 'form' %> diff --git a/app/views/kanaui/reports/index.html.erb b/app/views/kanaui/reports/index.html.erb index 02a970e..3b20c45 100644 --- a/app/views/kanaui/reports/index.html.erb +++ b/app/views/kanaui/reports/index.html.erb @@ -2,14 +2,17 @@
    + <% if @report_notice %> +
    <%= @report_notice %>
    + <% end %>
    -

    Configured reports

    +

    <%= t('kanaui.reports.index.title') %>

    <%= link_to dashboard_index_path, class: 'text-decoration-none' do %> <%= render "kaui/components/button/button", { - label: "Dashboard", + label: t('kanaui.reports.index.dashboard'), variant: "outline-secondary d-inline-flex align-items-center gap-1", type: "button", html_class: "btn btn-xs kaui-button custom-hover py-2 px-3" @@ -17,7 +20,7 @@ <% end %> <%= link_to new_report_path, class: 'text-decoration-none' do %> <%= render "kaui/components/button/button", { - label: "New Report", + label: t('kanaui.reports.index.new_report'), icon: "kaui/plus.svg", variant: "outline-secondary d-inline-flex align-items-center gap-1", type: "button", diff --git a/app/views/kanaui/reports/new.html.erb b/app/views/kanaui/reports/new.html.erb index 5fb24e8..67ff4b7 100644 --- a/app/views/kanaui/reports/new.html.erb +++ b/app/views/kanaui/reports/new.html.erb @@ -8,7 +8,7 @@ - Add new report + <%= t('kanaui.reports.new.title') %> <%= render 'form' %>
    diff --git a/config/locales/en.bootstrap.yml b/config/locales/en.bootstrap.yml index c98d8d8..74e869a 100644 --- a/config/locales/en.bootstrap.yml +++ b/config/locales/en.bootstrap.yml @@ -2,6 +2,57 @@ # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. en: + kanaui: + actions: + close: "Close" + delete: "Delete" + edit: "Edit" + refresh: "Refresh" + save: "Save" + errors: + killbill_communication: "Error while communicating with the Kill Bill server: %{error}" + dashboard: + advanced_controls: + sql_query: "SQL query" + reports: + notices: + created: "Report successfully created" + updated: "Report successfully updated" + refresh_scheduled: "Report refresh successfully scheduled" + deleted: "Report successfully deleted" + index: + title: "Configured reports" + dashboard: "Dashboard" + new_report: "New Report" + new: + title: "Add new report" + edit: + title: "Update report" + form: + report_name: "Report name" + pretty_name: "Pretty name" + type: "Type" + source: "Source" + source_name: "Source name" + source_name_help: "Must match a database configuration entry in the Analytics plugin" + source_query: "SQL query" + source_query_help: "Don't add a trailing ;" + source_configuration_error: "When Source name is set, provide exactly one of Source or SQL query." + refresh_procedure: "Refresh procedure" + refresh_frequency: "Refresh frequency" + refresh_hour_gmt: "Refresh hour (GMT)" + table: + name: "Name" + pretty_name: "Pretty name" + type: "Type" + source: "Source" + source_name: "Source Name" + source_query: "Source Query" + refresh_procedure: "Refresh procedure" + refresh_frequency: "Refresh frequency" + refresh_hour_gmt: "Refresh hour (GMT)" + fields: "Fields" + show_sql: "Show SQL" helpers: actions: "Actions" links: From e7c3034a64e8cbbec4990abd03e3dc55db440dbb Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 12 May 2026 09:21:01 +0700 Subject: [PATCH 2/2] Fix failed CI --- Gemfile | 4 ++++ app/controllers/kanaui/reports_controller.rb | 11 ++++++----- docker/docker-compose.ci.mysql.yml | 2 +- docker/docker-compose.ci.postgresql.yml | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Gemfile b/Gemfile index e831ac8..832f5ad 100644 --- a/Gemfile +++ b/Gemfile @@ -12,6 +12,10 @@ gemspec # Git. Remember to move these dependencies to your gemspec before releasing # your gem to rubygems.org. +# Lock minitest to 5.x until Rails 7.1+ adds Minitest 6.0 support +# Minitest 6.0.0 was released Dec 2024 with breaking API changes +gem 'minitest', '~> 5.0' + group :development do gem 'gem-release' gem 'json' diff --git a/app/controllers/kanaui/reports_controller.rb b/app/controllers/kanaui/reports_controller.rb index fa609eb..0dd1e9b 100644 --- a/app/controllers/kanaui/reports_controller.rb +++ b/app/controllers/kanaui/reports_controller.rb @@ -4,7 +4,7 @@ module Kanaui class ReportsController < Kanaui::EngineController def index @reports = JSON.parse(Kanaui::DashboardHelper::DashboardApi.available_reports(options_for_klient)).map(&:deep_symbolize_keys) - @report_notice = report_notice_from_params + @report_notice = report_notice_from_flash end def new @@ -43,15 +43,16 @@ def destroy private - def report_notice_from_params - notice_key = params[:report_notice].presence - return nil if notice_key.blank? + def report_notice_from_flash + notice_key = flash[:report_notice].presence&.to_s + return nil unless %w[created updated refresh_scheduled deleted].include?(notice_key) I18n.t("kanaui.reports.notices.#{notice_key}", default: nil) end def redirect_to_index_with_notice(notice_key) - redirect_to action: :index, report_notice: notice_key + flash[:report_notice] = notice_key + redirect_to action: :index end def report_from_params diff --git a/docker/docker-compose.ci.mysql.yml b/docker/docker-compose.ci.mysql.yml index 42230ac..28b8d1a 100644 --- a/docker/docker-compose.ci.mysql.yml +++ b/docker/docker-compose.ci.mysql.yml @@ -3,7 +3,7 @@ version: '3.8' services: killbill: network_mode: host - image: killbill/killbill:0.24.0 + image: killbill/killbill:0.24.16 environment: - KILLBILL_CATALOG_URI=SpyCarAdvanced.xml - KILLBILL_DAO_URL=jdbc:mysql://127.0.0.1:3306/killbill diff --git a/docker/docker-compose.ci.postgresql.yml b/docker/docker-compose.ci.postgresql.yml index 08f5a4d..a24124f 100644 --- a/docker/docker-compose.ci.postgresql.yml +++ b/docker/docker-compose.ci.postgresql.yml @@ -3,7 +3,7 @@ version: '3.8' services: killbill: network_mode: host - image: killbill/killbill:0.24.0 + image: killbill/killbill:0.24.16 environment: - KILLBILL_CATALOG_URI=SpyCarAdvanced.xml - KILLBILL_DAO_URL=jdbc:postgresql://127.0.0.1:5432/killbill
    NamePretty nameTypeSourceSource NameSource QueryRefresh procedureRefresh frequencyRefresh hour (GMT)Fields<%= t('kanaui.reports.table.name') %><%= t('kanaui.reports.table.pretty_name') %><%= t('kanaui.reports.table.type') %><%= t('kanaui.reports.table.source') %><%= t('kanaui.reports.table.source_name') %><%= t('kanaui.reports.table.source_query') %><%= t('kanaui.reports.table.refresh_procedure') %><%= t('kanaui.reports.table.refresh_frequency') %><%= t('kanaui.reports.table.refresh_hour_gmt') %><%= t('kanaui.reports.table.fields') %>
    <% unless report[:sourceQuery].blank? %> - Show SQL + <%= t('kanaui.reports.table.show_sql') %> - <%= link_to 'Delete', report_path(report[:reportName]), :method => :delete, :class => 'delete-button table-button' %> - <%= link_to 'Refresh', refresh_report_path(report[:reportName]), :method => :put, :class => 'refresh-button table-button' %> - <%= link_to 'Edit', edit_report_path(report[:reportName]), :class => 'edit-button table-button' %> + <%= link_to t('kanaui.actions.delete'), report_path(report[:reportName]), :method => :delete, :class => 'delete-button table-button' %> + <%= link_to t('kanaui.actions.refresh'), refresh_report_path(report[:reportName]), :method => :put, :class => 'refresh-button table-button' %> + <%= link_to t('kanaui.actions.edit'), edit_report_path(report[:reportName]), :class => 'edit-button table-button' %>