mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Move health check components into core
The idea is to reuse them in the wikis module and probably elsewhere as well, offering a similar look & feel. The ReportComponent has been lightened for this, though. Previously it included the page layout and a default to render when there was no report. Now it only focusses on rendering an actual report and leaves the rest up to the including component or page.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
<%#-- copyright
|
||||
OpenProject is an open source project management software.
|
||||
Copyright (C) the OpenProject GmbH
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License version 3.
|
||||
|
||||
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
Copyright (C) 2010-2013 the ChiliProject Team
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
++#%>
|
||||
|
||||
<%=
|
||||
component_wrapper do
|
||||
flex_layout do |report_container|
|
||||
report_container.with_row do
|
||||
concat(render(Primer::Beta::Octicon.new(mr: 2, **summary_icon(report.tally))))
|
||||
concat(render(Primer::Beta::Text.new(font_weight: :bold)) { humanize_summary(report.tally) })
|
||||
end
|
||||
|
||||
report_container.with_row(mt: 2) do
|
||||
render(Primer::Beta::Text.new) do
|
||||
if report.healthy?
|
||||
t(".summary.success")
|
||||
elsif report.unhealthy?
|
||||
t(".summary.failure")
|
||||
else
|
||||
t(".summary.warning")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
report.results.each do |result_group|
|
||||
report_container.with_row(mt: 3) do
|
||||
render(Primer::Beta::BorderBox.new(test_selector: "op-health-report--result-group")) do |box|
|
||||
box.with_header do
|
||||
flex_layout(justify_content: :space_between, classes: "flex-wrap") do |header|
|
||||
header.with_column do
|
||||
render(Primer::Beta::Text.new(font_weight: :bold)) { I18n.t("#{result_group.key}.header", scope: i18n_scope) }
|
||||
end
|
||||
|
||||
header.with_column do
|
||||
concat(render(Primer::Beta::Octicon.new(mr: 2, **summary_icon(result_group.tally))))
|
||||
concat(render(Primer::Beta::Text.new) { humanize_summary(result_group.tally) })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
result_group.results.each do |value|
|
||||
box.with_row do
|
||||
render(HealthReports::ResultComponent.new(group: result_group.key, result: value, i18n_scope:))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
%>
|
||||
+34
-32
@@ -23,46 +23,48 @@
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Admin
|
||||
module Health
|
||||
class HealthReportComponent < ApplicationComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
include OpTurbo::Streamable
|
||||
module HealthReports
|
||||
class ReportComponent < ApplicationComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
include OpTurbo::Streamable
|
||||
|
||||
def initialize(storage:, report:)
|
||||
super(storage)
|
||||
@report = report
|
||||
end
|
||||
alias report model
|
||||
|
||||
private
|
||||
# The i18n_scope parameter defines the I18n scope that should be used to resolve
|
||||
# names of groups, checks and error messages indicated by the results.
|
||||
def initialize(*, i18n_scope:, **)
|
||||
super(*, **)
|
||||
@i18n_scope = i18n_scope
|
||||
end
|
||||
|
||||
def summary_icon(check_tally)
|
||||
case check_tally
|
||||
in { failure: 1.. }
|
||||
{ icon: :alert, color: :danger }
|
||||
in { warning: 1.. }
|
||||
{ icon: :alert, color: :attention }
|
||||
else
|
||||
{ icon: :"check-circle", color: :success }
|
||||
end
|
||||
end
|
||||
private
|
||||
|
||||
def humanize_summary(check_tally)
|
||||
case check_tally
|
||||
in { failure: 1.. }
|
||||
I18n.t("health_report.checks.failures", count: check_tally[:failure])
|
||||
in { warning: 1.. }
|
||||
I18n.t("health_report.checks.warnings", count: check_tally[:warning])
|
||||
else
|
||||
I18n.t("health_report.checks.success")
|
||||
end
|
||||
end
|
||||
attr_reader :i18n_scope
|
||||
|
||||
def summary_icon(check_tally)
|
||||
case check_tally
|
||||
in { failure: 1.. }
|
||||
{ icon: :alert, color: :danger }
|
||||
in { warning: 1.. }
|
||||
{ icon: :alert, color: :attention }
|
||||
else
|
||||
{ icon: :"check-circle", color: :success }
|
||||
end
|
||||
end
|
||||
|
||||
def humanize_summary(check_tally)
|
||||
case check_tally
|
||||
in { failure: 1.. }
|
||||
t(".checks.failures", count: check_tally[:failure])
|
||||
in { warning: 1.. }
|
||||
t(".checks.warnings", count: check_tally[:warning])
|
||||
else
|
||||
t(".checks.success")
|
||||
end
|
||||
end
|
||||
end
|
||||
+2
-2
@@ -21,7 +21,7 @@ GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
@@ -57,7 +57,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
if error_text.present?
|
||||
cell.with_row(mt: 1) do
|
||||
render(Primer::Beta::Text.new(test_selector: "op-storages--health-status-check-information")) do
|
||||
render(Primer::Beta::Text.new(test_selector: "op-health-report--result-status")) do
|
||||
error_text
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,88 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module HealthReports
|
||||
class ResultComponent < ApplicationComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
def initialize(group:, result:, i18n_scope:)
|
||||
super(result)
|
||||
@group = group
|
||||
@i18n_scope = i18n_scope
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def text = I18n.t("#{@group}.#{model.key}", scope: @i18n_scope)
|
||||
|
||||
def error_text
|
||||
return nil if model.code.nil?
|
||||
|
||||
# TODO: fix translation namespace
|
||||
I18n.t("errors.#{model.code}", scope: @i18n_scope, **model.context&.symbolize_keys)
|
||||
end
|
||||
|
||||
def docs_href = ::OpenProject::Static::Links.url_for(:storage_docs, :health_status)
|
||||
|
||||
def error_code
|
||||
if model.failure?
|
||||
"ERR_#{model.code.upcase}"
|
||||
elsif model.warning?
|
||||
"WRN_#{model.code.upcase}"
|
||||
end
|
||||
end
|
||||
|
||||
def status_color
|
||||
if model.success?
|
||||
:success
|
||||
elsif model.failure?
|
||||
:danger
|
||||
elsif model.warning? || model.skipped?
|
||||
:attention
|
||||
else
|
||||
raise ArgumentError, "invalid check result state"
|
||||
end
|
||||
end
|
||||
|
||||
def status_text
|
||||
if model.success?
|
||||
t(".status.passed")
|
||||
elsif model.failure?
|
||||
t(".status.failed")
|
||||
elsif model.warning?
|
||||
t(".status.warning")
|
||||
elsif model.skipped?
|
||||
t(".status.skipped")
|
||||
else
|
||||
raise ArgumentError, "invalid check result state"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -69,8 +69,8 @@ search:
|
||||
# By default, if a relative translation is used inside a method, the name of the method will be considered part of the resolved key.
|
||||
# Directories listed here will not consider the name of the method part of the resolved key
|
||||
#
|
||||
# relative_exclude_method_name_paths:
|
||||
# -
|
||||
relative_exclude_method_name_paths:
|
||||
- modules/storages/app/components
|
||||
|
||||
## Files or `File.fnmatch` patterns to exclude from search. Some files are always excluded regardless of this setting:
|
||||
## *.jpg *.jpeg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less
|
||||
@@ -151,7 +151,7 @@ ignore_unused:
|
||||
- '*.permission_header_explanation'
|
||||
- 'storages.upsell.*'
|
||||
- 'services.*'
|
||||
- 'storages.health.connection_validation.{one_drive,nextcloud}.*'
|
||||
- 'storages.health.checks.*'
|
||||
|
||||
## Exclude these keys from the `i18n-tasks eq-base' report:
|
||||
# ignore_eq_base:
|
||||
|
||||
+20
-9
@@ -3524,15 +3524,26 @@ en:
|
||||
gui_validation_error: "1 error"
|
||||
gui_validation_error_plural: "%{count} errors"
|
||||
|
||||
health_report:
|
||||
checks:
|
||||
failures:
|
||||
one: "%{count} check failed"
|
||||
other: "%{count} checks failed"
|
||||
success: All checks passed
|
||||
warnings:
|
||||
one: "%{count} check returned a warning"
|
||||
other: "%{count} checks returned a warning"
|
||||
health_reports:
|
||||
report_component:
|
||||
checks:
|
||||
failures:
|
||||
one: "%{count} check failed"
|
||||
other: "%{count} checks failed"
|
||||
success: All checks passed
|
||||
warnings:
|
||||
one: "%{count} check returned a warning"
|
||||
other: "%{count} checks returned a warning"
|
||||
summary:
|
||||
failure: Some checks failed and the system does not work as expected.
|
||||
success: All connections and systems are working as expected.
|
||||
warning: Some checks returned a warning. This can lead to unexpected behaviour.
|
||||
result_component:
|
||||
status:
|
||||
failed: Failed
|
||||
passed: Passed
|
||||
skipped: Skipped
|
||||
warning: Warning
|
||||
|
||||
homescreen:
|
||||
additional:
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module Storages
|
||||
module Admin
|
||||
module Health
|
||||
class CheckResultComponent < ApplicationComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
def initialize(group:, result:)
|
||||
super(result)
|
||||
@group = group
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def text = I18n.t("storages.health.checks.#{@group}.#{model.key}")
|
||||
|
||||
def error_text
|
||||
return nil if model.code.nil?
|
||||
|
||||
I18n.t("storages.health.connection_validation.#{model.code}", **model.context&.symbolize_keys)
|
||||
end
|
||||
|
||||
def docs_href = ::OpenProject::Static::Links.url_for(:storage_docs, :health_status)
|
||||
|
||||
def error_code
|
||||
if model.failure?
|
||||
"ERR_#{model.code.upcase}"
|
||||
elsif model.warning?
|
||||
"WRN_#{model.code.upcase}"
|
||||
end
|
||||
end
|
||||
|
||||
def status_color
|
||||
if model.success?
|
||||
:success
|
||||
elsif model.failure?
|
||||
:danger
|
||||
elsif model.warning? || model.skipped?
|
||||
:attention
|
||||
else
|
||||
raise ArgumentError, "invalid check result state"
|
||||
end
|
||||
end
|
||||
|
||||
def status_text
|
||||
if model.success?
|
||||
I18n.t("storages.health.label_passed")
|
||||
elsif model.failure?
|
||||
I18n.t("storages.health.label_failed")
|
||||
elsif model.warning?
|
||||
I18n.t("storages.health.label_warning")
|
||||
elsif model.skipped?
|
||||
I18n.t("storages.health.label_skipped")
|
||||
else
|
||||
raise ArgumentError, "invalid check result state"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
<%#-- copyright
|
||||
OpenProject is an open source project management software.
|
||||
Copyright (C) the OpenProject GmbH
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License version 3.
|
||||
|
||||
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
Copyright (C) 2010-2013 the ChiliProject Team
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
++#%>
|
||||
|
||||
<%=
|
||||
component_wrapper do
|
||||
render(Primer::Alpha::Layout.new(stacking_breakpoint: :lg)) do |page|
|
||||
page.with_main do
|
||||
if @report.nil?
|
||||
render(Primer::Beta::Blankslate.new(border: true)) do |placeholder|
|
||||
placeholder.with_visual_icon(icon: :meter)
|
||||
placeholder.with_heading(tag: :h3) { I18n.t("storages.health.no_report") }
|
||||
placeholder.with_description { I18n.t("storages.health.no_report_description") }
|
||||
placeholder.with_primary_action(
|
||||
href: admin_settings_storage_health_status_report_path(model),
|
||||
data: { turbo_method: :post, turbo: true },
|
||||
aria: { label: I18n.t("storages.health.actions.run_checks") }
|
||||
) do
|
||||
I18n.t("storages.health.actions.run_checks")
|
||||
end
|
||||
end
|
||||
else
|
||||
flex_layout do |report_container|
|
||||
report_container.with_row do
|
||||
concat(render(Primer::Beta::Octicon.new(mr: 2, **summary_icon(@report.tally))))
|
||||
concat(render(Primer::Beta::Text.new(font_weight: :bold)) { humanize_summary(@report.tally) })
|
||||
end
|
||||
|
||||
report_container.with_row(mt: 2) do
|
||||
render(Primer::Beta::Text.new) do
|
||||
if @report.healthy?
|
||||
I18n.t("storages.health.summary.success")
|
||||
elsif @report.unhealthy?
|
||||
I18n.t("storages.health.summary.failure")
|
||||
else
|
||||
I18n.t("storages.health.summary.warning")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@report.results.each do |result_group|
|
||||
report_container.with_row(mt: 3) do
|
||||
render(Primer::Beta::BorderBox.new(test_selector: "op-storages--health-report-group")) do |box|
|
||||
box.with_header do
|
||||
flex_layout(justify_content: :space_between, classes: "flex-wrap") do |header|
|
||||
header.with_column do
|
||||
render(Primer::Beta::Text.new(font_weight: :bold)) { I18n.t("storages.health.checks.#{result_group.key}.header") }
|
||||
end
|
||||
|
||||
header.with_column do
|
||||
concat(render(Primer::Beta::Octicon.new(mr: 2, **summary_icon(result_group.tally))))
|
||||
concat(render(Primer::Beta::Text.new) { humanize_summary(result_group.tally) })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
result_group.results.each do |value|
|
||||
box.with_row do
|
||||
render(Storages::Admin::Health::CheckResultComponent.new(group: result_group.key, result: value))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
page.with_sidebar(col_placement: :end, row_placement: :end)
|
||||
end
|
||||
end
|
||||
%>
|
||||
+6
-6
@@ -49,26 +49,26 @@ module Storages
|
||||
{
|
||||
icon: :alert,
|
||||
icon_color: :danger,
|
||||
text: I18n.t("health_report.checks.failures", count: tally[:failure])
|
||||
text: t(".checks.failures", count: tally[:failure])
|
||||
}
|
||||
in { warning: 1.. }
|
||||
{
|
||||
icon: :alert,
|
||||
icon_color: :attention,
|
||||
text: I18n.t("health_report.checks.warnings", count: tally[:warning])
|
||||
text: t(".checks.warnings", count: tally[:warning])
|
||||
}
|
||||
else
|
||||
{ icon: :"check-circle", icon_color: :success, text: I18n.t("health_report.checks.success") }
|
||||
{ icon: :"check-circle", icon_color: :success, text: t(".checks.success") }
|
||||
end
|
||||
end
|
||||
|
||||
def summary_description
|
||||
text = if @result.healthy?
|
||||
I18n.t("storages.health.summary.success")
|
||||
t(".summary.success")
|
||||
elsif @result.unhealthy?
|
||||
I18n.t("storages.health.summary.failure")
|
||||
t(".summary.failure")
|
||||
else
|
||||
I18n.t("storages.health.summary.warning")
|
||||
t(".summary.warning")
|
||||
end
|
||||
|
||||
"#{text} #{I18n.t('storages.health.checked', datetime: helpers.format_time(@result.created_at))}"
|
||||
|
||||
@@ -53,8 +53,8 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
end
|
||||
%>
|
||||
|
||||
<%=
|
||||
if @report.present?
|
||||
<% if @report.present? %>
|
||||
<%=
|
||||
button_label = I18n.t("storages.health.actions.rerun_checks")
|
||||
download_label = I18n.t("storages.health.actions.download_report")
|
||||
|
||||
@@ -81,9 +81,32 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
button_label
|
||||
end
|
||||
end
|
||||
end
|
||||
%>
|
||||
%>
|
||||
<% end %>
|
||||
|
||||
<%= render(Storages::Admin::Health::HealthReportComponent.new(storage: @storage, report: @report)) %>
|
||||
<%=
|
||||
render(Primer::Alpha::Layout.new(stacking_breakpoint: :lg)) do |page|
|
||||
page.with_sidebar(col_placement: :end, row_placement: :end)
|
||||
page.with_main do %>
|
||||
|
||||
<% if @report.present? %>
|
||||
<%= render(HealthReports::ReportComponent.new(@report, i18n_scope: "storages.health.checks")) %>
|
||||
<% else %>
|
||||
<%=
|
||||
render(Primer::Beta::Blankslate.new(border: true)) do |placeholder|
|
||||
placeholder.with_visual_icon(icon: :meter)
|
||||
placeholder.with_heading(tag: :h3) { I18n.t("storages.health.no_report") }
|
||||
placeholder.with_description { I18n.t("storages.health.no_report_description") }
|
||||
placeholder.with_primary_action(
|
||||
href: admin_settings_storage_health_status_report_path(@storage),
|
||||
data: { turbo_method: :post, turbo: true },
|
||||
aria: { label: I18n.t("storages.health.actions.run_checks") }
|
||||
) do
|
||||
I18n.t("storages.health.actions.run_checks")
|
||||
end
|
||||
end
|
||||
%>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -209,6 +209,19 @@ en:
|
||||
health_notifications_component:
|
||||
sync_now: Sync now
|
||||
sync_queued: Synchronization queued.
|
||||
validation_result_component:
|
||||
checks:
|
||||
failures:
|
||||
one: "%{count} check failed"
|
||||
other: "%{count} checks failed"
|
||||
success: All checks passed
|
||||
warnings:
|
||||
one: "%{count} check returned a warning"
|
||||
other: "%{count} checks returned a warning"
|
||||
summary:
|
||||
failure: Some checks failed and the system does not work as expected.
|
||||
success: All connections and systems are working as expected.
|
||||
warning: Some checks returned a warning. This can lead to unexpected behaviour.
|
||||
buttons:
|
||||
done_continue: Done, continue
|
||||
open_storage: Open file storage
|
||||
@@ -314,67 +327,59 @@ en:
|
||||
host_url_accessible: Host URL accessible
|
||||
storage_configured: Configuration complete
|
||||
tenant_id: Tenant ID
|
||||
connection_validation:
|
||||
client_id_invalid: The configured OAuth 2 client id is invalid. Please check the configuration.
|
||||
client_secret_invalid: The configured OAuth 2 client secret is invalid. Please check the configuration.
|
||||
nc_dependency_missing: 'A required dependency is missing on the file storage. Please add the following dependency: %{dependency}.'
|
||||
nc_dependency_version_mismatch: The %{dependency} app version is not supported. Please update your Nextcloud server.
|
||||
nc_host_not_found: No Nextcloud server found at the configured host url. Please check the configuration.
|
||||
nc_oauth_request_not_found: The endpoint to fetch the currently connected user was not found. Please check the server logs for further information.
|
||||
nc_oauth_request_unauthorized: The current user isn't authorized to access the remote file storage. Please check the server logs for further information.
|
||||
nc_oauth_token_missing: OpenProject cannot test the user level communication with Nextcloud as the user did not yet link their Nextcloud account.
|
||||
nc_project_folder_missing: The previously created project folder for project "%{project}" could not be found.
|
||||
nc_team_folder_not_found: The team folder could not be found.
|
||||
nc_unexpected_files: 'Unexpected files found in the managed team folder. For example: %{sample}'
|
||||
nc_unlinked_project_folders: Not all project folders have been created yet (%{actual} / %{expected}). This can indicate errors during the AMPF background synchronization.
|
||||
nc_userless_access_denied: The configured app password is invalid.
|
||||
not_configured: The connection could not be validated. Please finish configuration first.
|
||||
od_client_cant_delete_folder: The client is having trouble deleting folders. Please check the setup documentation for your storage.
|
||||
od_client_write_permission_missing: The client seems to have write permissions missing. Please check the setup documentation for your storage.
|
||||
od_drive_id_invalid: The configured drive id seems invalid. Please check the configuration.
|
||||
od_drive_id_not_found: The configured drive id could not be found. Please check the configuration.
|
||||
od_oauth_request_not_found: The endpoint to fetch the currently connected user was not found. Please check the server logs for further information.
|
||||
od_oauth_request_unauthorized: The current user isn't authorized to access the remote file storage. Please check the server logs for further information.
|
||||
od_oauth_token_missing: OpenProject cannot test the user level communication with OneDrive as the user did not yet link their Microsoft account.
|
||||
od_tenant_id_wrong: The configured directory (tenant) id is invalid. Please check the configuration.
|
||||
od_test_folder_exists: The folder %{folder_name} needed for testing already exists. Please delete it and try again.
|
||||
od_unexpected_content: Unexpected content found in the drive.
|
||||
offline_access_scope_missing: It is recommended to configure the OpenID Connect provider to request the offline_access scope. The integration may still work anyways, but make sure that refresh tokens do not expire.
|
||||
oidc_cant_refresh_token: There was an error while trying to check your access to the storage. Please check the server logs for further information.
|
||||
oidc_non_oidc_user: The current user, while provisioned, wasn't provisioned by an OpenID Connect (OIDC) Identity Provider. Please re-run the check with an OIDC provisioned user.
|
||||
oidc_non_provisioned_user: The current user isn't provided by an OpenID Connect Identity Provider. Please re-run the check with a provided user.
|
||||
oidc_provider_cant_exchange: The OpenID Connect provider does not seem to support token exchange, but token exchange was configured for the storage.
|
||||
oidc_token_acquisition_failed: Your OpenID Connect setup doesn't provide the necessary audience, nor does it provide token exchange capabilities. Please check out our documentation for more information.
|
||||
oidc_token_exchange_failed: There seems to be a problem with the Token Exchange setup on your OpenID Connect Provider. Please check its configuration and try again.
|
||||
oidc_token_refresh_failed: There was an error while trying to check your access to the storage. Please check the server logs for further information.
|
||||
sp_client_cant_delete_folder: The client is having trouble deleting folders in SharePoint. Please check the setup documentation for your storage.
|
||||
sp_client_id_missing: The configured OAuth 2 client id is missing for SharePoint. Please check the configuration.
|
||||
sp_client_secret_missing: The configured OAuth 2 client secret is missing for SharePoint. Please check the configuration.
|
||||
sp_client_write_permission_missing: The client seems to have write permissions missing in SharePoint. Please check the setup documentation for your storage.
|
||||
sp_existing_test_folder: The folder %{folder_name} needed for testing already exists in SharePoint. Please delete it and try again.
|
||||
sp_oauth_request_error: The user-bound request to SharePoint failed. Please check the server logs for further information.
|
||||
sp_oauth_request_unauthorized: The current user isn't authorized to access the remote file storage. Please check the server logs for further information.
|
||||
sp_oauth_token_missing: OpenProject cannot test the user level communication with SharePoint as the user did not yet link their SharePoint account.
|
||||
sp_tenant_id_missing: The configured directory (tenant) id is missing for SharePoint. Please check the configuration.
|
||||
sp_unexpected_content: Unexpected content found in the SharePoint Document Library.
|
||||
unknown_error: The connection could not be validated. An unknown error occurred. Please check the server logs for further information.
|
||||
errors:
|
||||
client_id_invalid: The configured OAuth 2 client id is invalid. Please check the configuration.
|
||||
client_secret_invalid: The configured OAuth 2 client secret is invalid. Please check the configuration.
|
||||
nc_dependency_missing: 'A required dependency is missing on the file storage. Please add the following dependency: %{dependency}.'
|
||||
nc_dependency_version_mismatch: The %{dependency} app version is not supported. Please update your Nextcloud server.
|
||||
nc_host_not_found: No Nextcloud server found at the configured host url. Please check the configuration.
|
||||
nc_oauth_request_not_found: The endpoint to fetch the currently connected user was not found. Please check the server logs for further information.
|
||||
nc_oauth_request_unauthorized: The current user isn't authorized to access the remote file storage. Please check the server logs for further information.
|
||||
nc_oauth_token_missing: OpenProject cannot test the user level communication with Nextcloud as the user did not yet link their Nextcloud account.
|
||||
nc_project_folder_missing: The previously created project folder for project "%{project}" could not be found.
|
||||
nc_team_folder_not_found: The team folder could not be found.
|
||||
nc_unexpected_files: 'Unexpected files found in the managed team folder. For example: %{sample}'
|
||||
nc_unlinked_project_folders: Not all project folders have been created yet (%{actual} / %{expected}). This can indicate errors during the AMPF background synchronization.
|
||||
nc_userless_access_denied: The configured app password is invalid.
|
||||
not_configured: The connection could not be validated. Please finish configuration first.
|
||||
od_client_cant_delete_folder: The client is having trouble deleting folders. Please check the setup documentation for your storage.
|
||||
od_client_write_permission_missing: The client seems to have write permissions missing. Please check the setup documentation for your storage.
|
||||
od_drive_id_invalid: The configured drive id seems invalid. Please check the configuration.
|
||||
od_drive_id_not_found: The configured drive id could not be found. Please check the configuration.
|
||||
od_oauth_request_not_found: The endpoint to fetch the currently connected user was not found. Please check the server logs for further information.
|
||||
od_oauth_request_unauthorized: The current user isn't authorized to access the remote file storage. Please check the server logs for further information.
|
||||
od_oauth_token_missing: OpenProject cannot test the user level communication with OneDrive as the user did not yet link their Microsoft account.
|
||||
od_tenant_id_wrong: The configured directory (tenant) id is invalid. Please check the configuration.
|
||||
od_test_folder_exists: The folder %{folder_name} needed for testing already exists. Please delete it and try again.
|
||||
od_unexpected_content: Unexpected content found in the drive.
|
||||
offline_access_scope_missing: It is recommended to configure the OpenID Connect provider to request the offline_access scope. The integration may still work anyways, but make sure that refresh tokens do not expire.
|
||||
oidc_cant_refresh_token: There was an error while trying to check your access to the storage. Please check the server logs for further information.
|
||||
oidc_non_oidc_user: The current user, while provisioned, wasn't provisioned by an OpenID Connect (OIDC) Identity Provider. Please re-run the check with an OIDC provisioned user.
|
||||
oidc_non_provisioned_user: The current user isn't provided by an OpenID Connect Identity Provider. Please re-run the check with a provided user.
|
||||
oidc_provider_cant_exchange: The OpenID Connect provider does not seem to support token exchange, but token exchange was configured for the storage.
|
||||
oidc_token_acquisition_failed: Your OpenID Connect setup doesn't provide the necessary audience, nor does it provide token exchange capabilities. Please check out our documentation for more information.
|
||||
oidc_token_exchange_failed: There seems to be a problem with the Token Exchange setup on your OpenID Connect Provider. Please check its configuration and try again.
|
||||
oidc_token_refresh_failed: There was an error while trying to check your access to the storage. Please check the server logs for further information.
|
||||
sp_client_cant_delete_folder: The client is having trouble deleting folders in SharePoint. Please check the setup documentation for your storage.
|
||||
sp_client_id_missing: The configured OAuth 2 client id is missing for SharePoint. Please check the configuration.
|
||||
sp_client_secret_missing: The configured OAuth 2 client secret is missing for SharePoint. Please check the configuration.
|
||||
sp_client_write_permission_missing: The client seems to have write permissions missing in SharePoint. Please check the setup documentation for your storage.
|
||||
sp_existing_test_folder: The folder %{folder_name} needed for testing already exists in SharePoint. Please delete it and try again.
|
||||
sp_oauth_request_error: The user-bound request to SharePoint failed. Please check the server logs for further information.
|
||||
sp_oauth_request_unauthorized: The current user isn't authorized to access the remote file storage. Please check the server logs for further information.
|
||||
sp_oauth_token_missing: OpenProject cannot test the user level communication with SharePoint as the user did not yet link their SharePoint account.
|
||||
sp_tenant_id_missing: The configured directory (tenant) id is missing for SharePoint. Please check the configuration.
|
||||
sp_unexpected_content: Unexpected content found in the SharePoint Document Library.
|
||||
unknown_error: The connection could not be validated. An unknown error occurred. Please check the server logs for further information.
|
||||
label_error: Error
|
||||
label_failed: Failed
|
||||
label_healthy: Healthy
|
||||
label_passed: Passed
|
||||
label_pending: Pending
|
||||
label_skipped: Skipped
|
||||
label_warning: Warning
|
||||
no_report: No report available
|
||||
no_report_description: Run the checks now for a full health status report for this file storage.
|
||||
open_report: Open full health report
|
||||
project_folders:
|
||||
subtitle: Automatically managed project folders
|
||||
since: since %{datetime}
|
||||
summary:
|
||||
failure: Some checks failed and the system does not work as expected.
|
||||
success: All connections and systems are working as expected.
|
||||
warning: Some checks returned a warning. This can lead to unexpected behaviour.
|
||||
synced: 'Last sync: %{datetime}'
|
||||
title: Health status report
|
||||
health_email_notifications:
|
||||
|
||||
+31
-44
@@ -30,57 +30,44 @@
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Storages::Admin::Health::HealthReportComponent, type: :component do
|
||||
let(:storage) { create(:nextcloud_storage_configured) }
|
||||
RSpec.describe HealthReports::ReportComponent, type: :component do
|
||||
let(:report) do
|
||||
# rubocop:disable Naming/VariableNumber
|
||||
generate_test_report(
|
||||
group_1: %i[success success],
|
||||
group_2: %i[skipped skipped],
|
||||
group_3: %i[success success warning warning],
|
||||
group_4: %i[success failure failure],
|
||||
group_5: %i[success failure warning]
|
||||
)
|
||||
# rubocop:enable Naming/VariableNumber
|
||||
end
|
||||
|
||||
subject(:health_report_component) { described_class.new(storage:, report:) }
|
||||
subject(:health_report_component) { described_class.new(report, i18n_scope: "test.scope") }
|
||||
|
||||
before do
|
||||
render_inline(health_report_component)
|
||||
end
|
||||
|
||||
context "if report is not available" do
|
||||
let(:report) { nil }
|
||||
|
||||
it "renders a placeholder blankslate" do
|
||||
expect(page).to have_text("No report available")
|
||||
expect(page).to have_link("Run checks now")
|
||||
end
|
||||
it "renders a summary" do
|
||||
expect(page).to have_text("3 checks failed")
|
||||
expect(page).to have_text("Some checks failed and the system does not work as expected.")
|
||||
end
|
||||
|
||||
context "if report is available" do
|
||||
let(:report) do
|
||||
# rubocop:disable Naming/VariableNumber
|
||||
generate_test_report(
|
||||
group_1: %i[success success],
|
||||
group_2: %i[skipped skipped],
|
||||
group_3: %i[success success warning warning],
|
||||
group_4: %i[success failure failure],
|
||||
group_5: %i[success failure warning]
|
||||
)
|
||||
# rubocop:enable Naming/VariableNumber
|
||||
end
|
||||
it "renders each group separately" do
|
||||
expect(page).to have_test_selector("op-health-report--result-group", count: 5)
|
||||
|
||||
it "renders a summary" do
|
||||
expect(page).to have_text("3 checks failed")
|
||||
expect(page).to have_text("Some checks failed and the system does not work as expected.")
|
||||
end
|
||||
summaries = {
|
||||
0 => "All checks passed",
|
||||
1 => "All checks passed",
|
||||
2 => "2 checks returned a warning",
|
||||
3 => "2 checks failed",
|
||||
4 => "1 check failed"
|
||||
}
|
||||
|
||||
it "renders each group separately" do
|
||||
expect(page).to have_test_selector("op-storages--health-report-group", count: 5)
|
||||
|
||||
summaries = {
|
||||
0 => "All checks passed",
|
||||
1 => "All checks passed",
|
||||
2 => "2 checks returned a warning",
|
||||
3 => "2 checks failed",
|
||||
4 => "1 check failed"
|
||||
}
|
||||
|
||||
page.all(test_selector("op-storages--health-report-group")).each_with_index do |group, idx|
|
||||
expect(group).to have_text("Group #{idx + 1}")
|
||||
expect(group).to have_text(summaries[idx])
|
||||
end
|
||||
page.all(test_selector("op-health-report--result-group")).each_with_index do |group, idx|
|
||||
expect(group).to have_text("Group #{idx + 1}")
|
||||
expect(group).to have_text(summaries[idx])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -103,9 +90,9 @@ RSpec.describe Storages::Admin::Health::HealthReportComponent, type: :component
|
||||
end
|
||||
|
||||
group.results << result
|
||||
allow(I18n).to receive(:t).with("storages.health.checks.#{group_key}.#{key}").and_return(key.to_s.humanize)
|
||||
allow(I18n).to receive(:t).with("#{group_key}.#{key}", scope: "test.scope").and_return(key.to_s.humanize)
|
||||
if result.code.present?
|
||||
allow(I18n).to receive(:t).with("storages.health.connection_validation.#{result.code}")
|
||||
allow(I18n).to receive(:t).with("errors.#{result.code}", scope: "test.scope")
|
||||
.and_return(result.code.to_s.humanize)
|
||||
end
|
||||
end
|
||||
@@ -119,7 +106,7 @@ RSpec.describe Storages::Admin::Health::HealthReportComponent, type: :component
|
||||
|
||||
map.each_pair do |key, values|
|
||||
report.results << generate_test_group(key, values)
|
||||
allow(I18n).to receive(:t).with("storages.health.checks.#{key}.header").and_return(key.to_s.humanize)
|
||||
allow(I18n).to receive(:t).with("#{key}.header", scope: "test.scope").and_return(key.to_s.humanize)
|
||||
end
|
||||
|
||||
report
|
||||
+17
-11
@@ -30,24 +30,28 @@
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Storages::Admin::Health::CheckResultComponent, type: :component do
|
||||
RSpec.describe HealthReports::ResultComponent, type: :component do
|
||||
let(:group_key) { :base_configuration }
|
||||
|
||||
subject(:check_result_component) { described_class.new(group: group_key, result: check_result) }
|
||||
subject(:result_component) { described_class.new(group: group_key, result: check_result, i18n_scope: "test.scope") }
|
||||
|
||||
before do
|
||||
render_inline(check_result_component)
|
||||
allow(I18n).to receive(:t).and_call_original
|
||||
allow(I18n).to receive(:t).with("#{group_key}.#{check_result.key}", scope: "test.scope").and_return("Translated check")
|
||||
allow(I18n).to receive(:t).with("errors.#{check_result.code}", scope: "test.scope").and_return("Translated error")
|
||||
|
||||
render_inline(result_component)
|
||||
end
|
||||
|
||||
context "if check result is successful" do
|
||||
let(:check_result) { HealthReport::Result.success(:capabilities_request) }
|
||||
|
||||
it "renders the component" do
|
||||
expect(page).to have_text(I18n.t("storages.health.checks.#{group_key}.#{check_result.key}"))
|
||||
expect(page).to have_text("Translated check")
|
||||
expect(page).to have_css(".color-fg-success", text: "Passed")
|
||||
expect(page).to have_no_css(".Label")
|
||||
expect(page).to have_no_link("More information")
|
||||
expect(page).not_to have_test_selector("op-storages--health-status-check-information")
|
||||
expect(page).not_to have_test_selector("op-health-report--result-status")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -55,11 +59,11 @@ RSpec.describe Storages::Admin::Health::CheckResultComponent, type: :component d
|
||||
let(:check_result) { HealthReport::Result.skipped(:capabilities_request) }
|
||||
|
||||
it "renders the component" do
|
||||
expect(page).to have_text(I18n.t("storages.health.checks.#{group_key}.#{check_result.key}"))
|
||||
expect(page).to have_text("Translated check")
|
||||
expect(page).to have_css(".color-fg-attention", text: "Skipped")
|
||||
expect(page).to have_no_css(".Label")
|
||||
expect(page).to have_no_link("More information")
|
||||
expect(page).not_to have_test_selector("op-storages--health-status-check-information")
|
||||
expect(page).not_to have_test_selector("op-health-report--result-status")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -70,11 +74,12 @@ RSpec.describe Storages::Admin::Health::CheckResultComponent, type: :component d
|
||||
end
|
||||
|
||||
it "renders the component" do
|
||||
expect(page).to have_text(I18n.t("storages.health.checks.#{group_key}.#{check_result.key}"))
|
||||
expect(page).to have_text("Translated check")
|
||||
expect(page).to have_css(".color-fg-attention", text: "Warning")
|
||||
expect(page).to have_css(".Label", text: "WRN_#{check_result.code.upcase}")
|
||||
expect(page).to have_text("Translated error")
|
||||
expect(page).to have_link("More information")
|
||||
expect(page).to have_test_selector("op-storages--health-status-check-information")
|
||||
expect(page).to have_test_selector("op-health-report--result-status")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -84,11 +89,12 @@ RSpec.describe Storages::Admin::Health::CheckResultComponent, type: :component d
|
||||
end
|
||||
|
||||
it "renders the component" do
|
||||
expect(page).to have_text(I18n.t("storages.health.checks.#{group_key}.#{check_result.key}"))
|
||||
expect(page).to have_text("Translated check")
|
||||
expect(page).to have_css(".color-fg-danger", text: "Failed")
|
||||
expect(page).to have_css(".Label", text: "ERR_#{check_result.code.upcase}")
|
||||
expect(page).to have_text("Translated error")
|
||||
expect(page).to have_link("More information")
|
||||
expect(page).to have_test_selector("op-storages--health-status-check-information")
|
||||
expect(page).to have_test_selector("op-health-report--result-status")
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user