|
|
2c83a8 |
From df9da3bc28d316032f56c6b8d538575e4f097bf5 Mon Sep 17 00:00:00 2001
|
|
|
2c83a8 |
From: Matej Habrnal <mhabrnal@redhat.com>
|
|
|
2c83a8 |
Date: Tue, 29 Mar 2016 11:06:24 +0200
|
|
|
2c83a8 |
Subject: [PATCH] report-gtk: Require Reproducer for RHTSupport
|
|
|
2c83a8 |
|
|
|
2c83a8 |
- Introduce a new event configuration option to mark events that
|
|
|
2c83a8 |
needs good reproducer or comprehensive description. I decided
|
|
|
2c83a8 |
to go this way because there are events like uReport or e-mail
|
|
|
2c83a8 |
that can have Comment but do not need to be so strict.
|
|
|
2c83a8 |
(We can use the new option for Bugzilla in future).
|
|
|
2c83a8 |
- Add problem description policies based on problem reproducibility
|
|
|
2c83a8 |
* unknow reproducer -> detailed description of circumstances
|
|
|
2c83a8 |
* known reproducer -> description of circumstances + steps
|
|
|
2c83a8 |
* recurrent problem -> steps
|
|
|
2c83a8 |
|
|
|
2c83a8 |
Related: #1258482
|
|
|
2c83a8 |
|
|
|
2c83a8 |
Signed-off-by: Matej Habrnal <mhabrnal@redhat.com>
|
|
|
2c83a8 |
---
|
|
|
2c83a8 |
src/gui-wizard-gtk/wizard.c | 198 +++++++++++++++++++++++++++++++++++++--
|
|
|
2c83a8 |
src/gui-wizard-gtk/wizard.glade | 166 ++++++++++++++++++++++++++------
|
|
|
2c83a8 |
src/include/event_config.h | 1 +
|
|
|
2c83a8 |
src/include/internal_libreport.h | 4 +
|
|
|
2c83a8 |
src/include/problem_data.h | 11 +++
|
|
|
2c83a8 |
src/lib/event_xml_parser.c | 5 +
|
|
|
2c83a8 |
src/lib/problem_data.c | 35 +++++++
|
|
|
2c83a8 |
7 files changed, 383 insertions(+), 37 deletions(-)
|
|
|
2c83a8 |
|
|
|
2c83a8 |
diff --git a/src/gui-wizard-gtk/wizard.c b/src/gui-wizard-gtk/wizard.c
|
|
|
2c83a8 |
index 6a1bdc0..31861a1 100644
|
|
|
2c83a8 |
--- a/src/gui-wizard-gtk/wizard.c
|
|
|
2c83a8 |
+++ b/src/gui-wizard-gtk/wizard.c
|
|
|
2c83a8 |
@@ -88,6 +88,13 @@ static GtkLabel *g_lbl_cd_reason;
|
|
|
2c83a8 |
static GtkTextView *g_tv_comment;
|
|
|
2c83a8 |
static GtkEventBox *g_eb_comment;
|
|
|
2c83a8 |
static GtkCheckButton *g_cb_no_comment;
|
|
|
2c83a8 |
+static GtkBox *g_vb_simple_details;
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+static GtkComboBoxText *g_cmb_reproducible;
|
|
|
2c83a8 |
+static GtkTextView *g_tv_steps;
|
|
|
2c83a8 |
+static GtkLabel *g_lbl_complex_details_hint;
|
|
|
2c83a8 |
+static GtkBox *g_vb_complex_details;
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
static GtkWidget *g_widget_warnings_area;
|
|
|
2c83a8 |
static GtkBox *g_box_warning_labels;
|
|
|
2c83a8 |
static GtkToggleButton *g_tb_approve_bt;
|
|
|
2c83a8 |
@@ -1187,6 +1194,51 @@ static event_gui_data_t *add_event_buttons(GtkBox *box,
|
|
|
2c83a8 |
return active_button;
|
|
|
2c83a8 |
}
|
|
|
2c83a8 |
|
|
|
2c83a8 |
+static bool isdigit_str(const char *str)
|
|
|
2c83a8 |
+{
|
|
|
2c83a8 |
+ do
|
|
|
2c83a8 |
+ {
|
|
|
2c83a8 |
+ if (*str < '0' || *str > '9') return false;
|
|
|
2c83a8 |
+ str++;
|
|
|
2c83a8 |
+ } while (*str);
|
|
|
2c83a8 |
+ return true;
|
|
|
2c83a8 |
+}
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+static void update_reproducible_hints(void)
|
|
|
2c83a8 |
+{
|
|
|
2c83a8 |
+ int reproducible = gtk_combo_box_get_active(GTK_COMBO_BOX(g_cmb_reproducible));
|
|
|
2c83a8 |
+ switch(reproducible)
|
|
|
2c83a8 |
+ {
|
|
|
2c83a8 |
+ case -1:
|
|
|
2c83a8 |
+ return;
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ case PROBLEM_REPRODUCIBLE_UNKNOWN:
|
|
|
2c83a8 |
+ gtk_label_set_text(g_lbl_complex_details_hint,
|
|
|
2c83a8 |
+ _("Since crashes without a known reproducer can be "
|
|
|
2c83a8 |
+ "difficult to diagnose, please provide a comprehensive "
|
|
|
2c83a8 |
+ "description of the problem you have encountered."));
|
|
|
2c83a8 |
+ break;
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ case PROBLEM_REPRODUCIBLE_YES:
|
|
|
2c83a8 |
+ gtk_label_set_text(g_lbl_complex_details_hint,
|
|
|
2c83a8 |
+ _("Please provide a short description of the problem and "
|
|
|
2c83a8 |
+ "please include the steps you have used to reproduce "
|
|
|
2c83a8 |
+ "the problem."));
|
|
|
2c83a8 |
+ break;
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ case PROBLEM_REPRODUCIBLE_RECURRENT:
|
|
|
2c83a8 |
+ gtk_label_set_text(g_lbl_complex_details_hint,
|
|
|
2c83a8 |
+ _("Please provide the steps you have used to reproduce the "
|
|
|
2c83a8 |
+ "problem."));
|
|
|
2c83a8 |
+ break;
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ default:
|
|
|
2c83a8 |
+ error_msg("BUG: %s:%s:%d: forgotten 'how reproducible' value",
|
|
|
2c83a8 |
+ __FILE__, __func__, __LINE__);
|
|
|
2c83a8 |
+ break;
|
|
|
2c83a8 |
+ }
|
|
|
2c83a8 |
+}
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
struct cd_stats {
|
|
|
2c83a8 |
off_t filesize;
|
|
|
2c83a8 |
unsigned filecount;
|
|
|
2c83a8 |
@@ -1425,6 +1477,7 @@ void update_gui_state_from_problem_data(int flags)
|
|
|
2c83a8 |
free(msg);
|
|
|
2c83a8 |
|
|
|
2c83a8 |
load_text_to_text_view(g_tv_comment, FILENAME_COMMENT);
|
|
|
2c83a8 |
+ load_text_to_text_view(g_tv_steps, FILENAME_REPRODUCER);
|
|
|
2c83a8 |
|
|
|
2c83a8 |
add_workflow_buttons(g_box_workflows, g_workflow_list,
|
|
|
2c83a8 |
G_CALLBACK(set_auto_event_chain));
|
|
|
2c83a8 |
@@ -1460,6 +1513,38 @@ void update_gui_state_from_problem_data(int flags)
|
|
|
2c83a8 |
* We created new widgets (buttons). Need to make them visible.
|
|
|
2c83a8 |
*/
|
|
|
2c83a8 |
gtk_widget_show_all(GTK_WIDGET(g_wnd_assistant));
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ /* Update Reproducible */
|
|
|
2c83a8 |
+ /* Try to get the old value */
|
|
|
2c83a8 |
+ const int reproducible = get_problem_data_reproducible(g_cd);
|
|
|
2c83a8 |
+ if (reproducible > -1)
|
|
|
2c83a8 |
+ {
|
|
|
2c83a8 |
+ gtk_combo_box_set_active(GTK_COMBO_BOX(g_cmb_reproducible), reproducible);
|
|
|
2c83a8 |
+ goto reproducible_done;
|
|
|
2c83a8 |
+ }
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ /* OK, no old value.
|
|
|
2c83a8 |
+ * Try to guess the reproducibility from the number of occurrences */
|
|
|
2c83a8 |
+ const char *count_str = problem_data_get_content_or_NULL(g_cd, FILENAME_COUNT);
|
|
|
2c83a8 |
+ if ( count_str == NULL
|
|
|
2c83a8 |
+ || strcmp(count_str, "0") == 0
|
|
|
2c83a8 |
+ || strcmp(count_str, "1") == 0
|
|
|
2c83a8 |
+ || strcmp(count_str, "2") == 0
|
|
|
2c83a8 |
+ || !isdigit_str(count_str))
|
|
|
2c83a8 |
+ {
|
|
|
2c83a8 |
+ gtk_combo_box_set_active(GTK_COMBO_BOX(g_cmb_reproducible), PROBLEM_REPRODUCIBLE_UNKNOWN);
|
|
|
2c83a8 |
+ }
|
|
|
2c83a8 |
+ else
|
|
|
2c83a8 |
+ {
|
|
|
2c83a8 |
+ int count = xatoi(count_str);
|
|
|
2c83a8 |
+ if (count < 5)
|
|
|
2c83a8 |
+ gtk_combo_box_set_active(GTK_COMBO_BOX(g_cmb_reproducible), PROBLEM_REPRODUCIBLE_YES);
|
|
|
2c83a8 |
+ else
|
|
|
2c83a8 |
+ gtk_combo_box_set_active(GTK_COMBO_BOX(g_cmb_reproducible), PROBLEM_REPRODUCIBLE_RECURRENT);
|
|
|
2c83a8 |
+ }
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+reproducible_done:
|
|
|
2c83a8 |
+ update_reproducible_hints();
|
|
|
2c83a8 |
}
|
|
|
2c83a8 |
|
|
|
2c83a8 |
|
|
|
2c83a8 |
@@ -1886,6 +1971,12 @@ _("If you want to update the configuration and try to report again, please open
|
|
|
2c83a8 |
show_warnings();
|
|
|
2c83a8 |
}
|
|
|
2c83a8 |
|
|
|
2c83a8 |
+static bool event_requires_details(const char *event_name)
|
|
|
2c83a8 |
+{
|
|
|
2c83a8 |
+ event_config_t *cfg = get_event_config(event_name);
|
|
|
2c83a8 |
+ return cfg != NULL && cfg->ec_requires_details;
|
|
|
2c83a8 |
+}
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
static gboolean consume_cmd_output(GIOChannel *source, GIOCondition condition, gpointer data)
|
|
|
2c83a8 |
{
|
|
|
2c83a8 |
struct analyze_event_data *evd = data;
|
|
|
2c83a8 |
@@ -2274,18 +2365,53 @@ static void toggle_eb_comment(void)
|
|
|
2c83a8 |
if (pages[PAGENO_EDIT_COMMENT].page_widget == NULL)
|
|
|
2c83a8 |
return;
|
|
|
2c83a8 |
|
|
|
2c83a8 |
- bool good =
|
|
|
2c83a8 |
- gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(g_tv_comment)) >= 10
|
|
|
2c83a8 |
- || gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g_cb_no_comment));
|
|
|
2c83a8 |
+ bool complex_details = g_event_selected
|
|
|
2c83a8 |
+ && event_requires_details(g_event_selected);
|
|
|
2c83a8 |
+ bool good = false;
|
|
|
2c83a8 |
+ if (complex_details)
|
|
|
2c83a8 |
+ {
|
|
|
2c83a8 |
+ int reproducible = gtk_combo_box_get_active(GTK_COMBO_BOX(g_cmb_reproducible));
|
|
|
2c83a8 |
+ const int comment_chars = gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(g_tv_comment));
|
|
|
2c83a8 |
+ const int steps_chars = gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(g_tv_steps));
|
|
|
2c83a8 |
+ const int steps_lines = steps_chars == 0 ? 0 : gtk_text_buffer_get_line_count(gtk_text_view_get_buffer(g_tv_steps));
|
|
|
2c83a8 |
+ switch(reproducible)
|
|
|
2c83a8 |
+ {
|
|
|
2c83a8 |
+ case -1:
|
|
|
2c83a8 |
+ VERB1 log("Uninitialized 'How reproducible' combobox");
|
|
|
2c83a8 |
+ break;
|
|
|
2c83a8 |
|
|
|
2c83a8 |
- /* Allow next page only when the comment has at least 10 chars */
|
|
|
2c83a8 |
- gtk_widget_set_sensitive(g_btn_next, good);
|
|
|
2c83a8 |
+ case PROBLEM_REPRODUCIBLE_UNKNOWN:
|
|
|
2c83a8 |
+ good = comment_chars + (steps_chars * 2) >= 20;
|
|
|
2c83a8 |
+ break;
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ case PROBLEM_REPRODUCIBLE_YES:
|
|
|
2c83a8 |
+ good = comment_chars >= 10 && steps_lines;
|
|
|
2c83a8 |
+ break;
|
|
|
2c83a8 |
|
|
|
2c83a8 |
- /* And show the eventbox with label */
|
|
|
2c83a8 |
- if (good)
|
|
|
2c83a8 |
- gtk_widget_hide(GTK_WIDGET(g_eb_comment));
|
|
|
2c83a8 |
+ case PROBLEM_REPRODUCIBLE_RECURRENT:
|
|
|
2c83a8 |
+ good = comment_chars >= 10 || steps_lines;
|
|
|
2c83a8 |
+ break;
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ default:
|
|
|
2c83a8 |
+ error_msg("BUG: %s:%s:%d: forgotten 'how reproducible' value",
|
|
|
2c83a8 |
+ __FILE__, __func__, __LINE__);
|
|
|
2c83a8 |
+ break;
|
|
|
2c83a8 |
+ }
|
|
|
2c83a8 |
+ }
|
|
|
2c83a8 |
else
|
|
|
2c83a8 |
- gtk_widget_show(GTK_WIDGET(g_eb_comment));
|
|
|
2c83a8 |
+ {
|
|
|
2c83a8 |
+ good = gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(g_tv_comment)) >= 10
|
|
|
2c83a8 |
+ || gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g_cb_no_comment));
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ /* And show the eventbox with label */
|
|
|
2c83a8 |
+ if (good)
|
|
|
2c83a8 |
+ gtk_widget_hide(GTK_WIDGET(g_eb_comment));
|
|
|
2c83a8 |
+ else
|
|
|
2c83a8 |
+ gtk_widget_show(GTK_WIDGET(g_eb_comment));
|
|
|
2c83a8 |
+ }
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ /* Allow next page only when the comment has at least 10 chars */
|
|
|
2c83a8 |
+ gtk_widget_set_sensitive(g_btn_next, good);
|
|
|
2c83a8 |
}
|
|
|
2c83a8 |
|
|
|
2c83a8 |
static void on_comment_changed(GtkTextBuffer *buffer, gpointer user_data)
|
|
|
2c83a8 |
@@ -2298,6 +2424,11 @@ static void on_no_comment_toggled(GtkToggleButton *togglebutton, gpointer user_d
|
|
|
2c83a8 |
toggle_eb_comment();
|
|
|
2c83a8 |
}
|
|
|
2c83a8 |
|
|
|
2c83a8 |
+static void on_steps_changed(GtkTextBuffer *buffer, gpointer user_data)
|
|
|
2c83a8 |
+{
|
|
|
2c83a8 |
+ toggle_eb_comment();
|
|
|
2c83a8 |
+}
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
static void on_log_changed(GtkTextBuffer *buffer, gpointer user_data)
|
|
|
2c83a8 |
{
|
|
|
2c83a8 |
gtk_widget_show(GTK_WIDGET(g_exp_report_log));
|
|
|
2c83a8 |
@@ -2723,6 +2854,23 @@ static void on_page_prepare(GtkNotebook *assistant, GtkWidget *page, gpointer us
|
|
|
2c83a8 |
* these tabs will be lost */
|
|
|
2c83a8 |
save_items_from_notepad();
|
|
|
2c83a8 |
save_text_from_text_view(g_tv_comment, FILENAME_COMMENT);
|
|
|
2c83a8 |
+ save_text_from_text_view(g_tv_steps, FILENAME_REPRODUCER);
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ int reproducible = gtk_combo_box_get_active(GTK_COMBO_BOX(g_cmb_reproducible));
|
|
|
2c83a8 |
+ if (reproducible > -1)
|
|
|
2c83a8 |
+ {
|
|
|
2c83a8 |
+ const char *reproducible_str = get_problem_data_reproducible_name(reproducible);
|
|
|
2c83a8 |
+ if (reproducible_str != NULL)
|
|
|
2c83a8 |
+ {
|
|
|
2c83a8 |
+ struct dump_dir *dd = wizard_open_directory_for_writing(g_dump_dir_name);
|
|
|
2c83a8 |
+ if (dd)
|
|
|
2c83a8 |
+ dd_save_text(dd, FILENAME_REPRODUCIBLE, reproducible_str);
|
|
|
2c83a8 |
+ else
|
|
|
2c83a8 |
+ error_msg(_("Failed to save file '%s'"), FILENAME_REPRODUCIBLE);
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ dd_close(dd);
|
|
|
2c83a8 |
+ }
|
|
|
2c83a8 |
+ }
|
|
|
2c83a8 |
problem_data_reload_from_dump_dir();
|
|
|
2c83a8 |
update_gui_state_from_problem_data(/* don't update selected event */ 0);
|
|
|
2c83a8 |
|
|
|
2c83a8 |
@@ -2782,6 +2930,11 @@ static void on_page_prepare(GtkNotebook *assistant, GtkWidget *page, gpointer us
|
|
|
2c83a8 |
|
|
|
2c83a8 |
if (pages[PAGENO_EDIT_COMMENT].page_widget == page)
|
|
|
2c83a8 |
{
|
|
|
2c83a8 |
+ bool complex_details = g_event_selected
|
|
|
2c83a8 |
+ && event_requires_details(g_event_selected);
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ gtk_widget_set_visible(GTK_WIDGET(g_vb_simple_details), !complex_details);
|
|
|
2c83a8 |
+ gtk_widget_set_visible(GTK_WIDGET(g_vb_complex_details), complex_details);
|
|
|
2c83a8 |
update_private_ticket_creation_warning_for_selected_event();
|
|
|
2c83a8 |
|
|
|
2c83a8 |
gtk_widget_set_sensitive(g_btn_next, false);
|
|
|
2c83a8 |
@@ -3024,7 +3177,9 @@ static gint select_next_page_no(gint current_page_no, gpointer data)
|
|
|
2c83a8 |
exit(0);
|
|
|
2c83a8 |
/* No! this would SEGV (infinitely recurse into select_next_page_no) */
|
|
|
2c83a8 |
/*gtk_assistant_commit(g_assistant);*/
|
|
|
2c83a8 |
- current_page_no = pages[PAGENO_EVENT_SELECTOR].page_no - 1;
|
|
|
2c83a8 |
+ gtk_widget_set_sensitive(pages[PAGENO_EVENT_SELECTOR].page_widget,
|
|
|
2c83a8 |
+ /*Radio buttons used == always selected*/FALSE);
|
|
|
2c83a8 |
+ current_page_no = pages[PAGENO_EVENT_SELECTOR].page_no-1;
|
|
|
2c83a8 |
goto again;
|
|
|
2c83a8 |
}
|
|
|
2c83a8 |
|
|
|
2c83a8 |
@@ -3297,6 +3452,12 @@ static gint on_key_press_event_in_item_list(GtkTreeView *treeview, GdkEventKey *
|
|
|
2c83a8 |
return FALSE;
|
|
|
2c83a8 |
}
|
|
|
2c83a8 |
|
|
|
2c83a8 |
+static void on_reproducible_changed(GtkComboBox *widget, gpointer user_data)
|
|
|
2c83a8 |
+{
|
|
|
2c83a8 |
+ update_reproducible_hints();
|
|
|
2c83a8 |
+ toggle_eb_comment();
|
|
|
2c83a8 |
+}
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
/* Initialization */
|
|
|
2c83a8 |
|
|
|
2c83a8 |
static void on_next_btn_cb(GtkWidget *btn, gpointer user_data)
|
|
|
2c83a8 |
@@ -3353,6 +3514,11 @@ static void add_pages(void)
|
|
|
2c83a8 |
g_img_process_fail = GTK_IMAGE( gtk_builder_get_object(g_builder, "img_process_fail"));
|
|
|
2c83a8 |
g_btn_startcast = GTK_BUTTON( gtk_builder_get_object(g_builder, "btn_startcast"));
|
|
|
2c83a8 |
g_exp_report_log = GTK_EXPANDER( gtk_builder_get_object(g_builder, "expand_report"));
|
|
|
2c83a8 |
+ g_vb_simple_details = GTK_BOX( gtk_builder_get_object(g_builder, "vb_simple_details"));
|
|
|
2c83a8 |
+ g_cmb_reproducible = GTK_COMBO_BOX_TEXT(gtk_builder_get_object(g_builder, "cmb_reproducible"));
|
|
|
2c83a8 |
+ g_tv_steps = GTK_TEXT_VIEW( gtk_builder_get_object(g_builder, "tv_steps"));
|
|
|
2c83a8 |
+ g_vb_complex_details = GTK_BOX( gtk_builder_get_object(g_builder, "vb_complex_details"));
|
|
|
2c83a8 |
+ g_lbl_complex_details_hint = GTK_LABEL( gtk_builder_get_object(g_builder, "lbl_complex_details_hint"));
|
|
|
2c83a8 |
|
|
|
2c83a8 |
gtk_widget_set_no_show_all(GTK_WIDGET(g_spinner_event_log), true);
|
|
|
2c83a8 |
|
|
|
2c83a8 |
@@ -3371,6 +3537,17 @@ static void add_pages(void)
|
|
|
2c83a8 |
|
|
|
2c83a8 |
g_signal_connect(g_tv_details, "key-press-event", G_CALLBACK(on_key_press_event_in_item_list), NULL);
|
|
|
2c83a8 |
g_tv_sensitive_sel_hndlr = g_signal_connect(g_tv_sensitive_sel, "changed", G_CALLBACK(on_sensitive_word_selection_changed), NULL);
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ gtk_combo_box_text_insert(g_cmb_reproducible, PROBLEM_REPRODUCIBLE_UNKNOWN, NULL,
|
|
|
2c83a8 |
+ _("I have experienced this problem for the first time"));
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ gtk_combo_box_text_insert(g_cmb_reproducible, PROBLEM_REPRODUCIBLE_YES, NULL,
|
|
|
2c83a8 |
+ _("I can reproduce this problem"));
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ gtk_combo_box_text_insert(g_cmb_reproducible, PROBLEM_REPRODUCIBLE_RECURRENT, NULL,
|
|
|
2c83a8 |
+ _("This problem occurs repeatedly"));
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ g_signal_connect(g_cmb_reproducible, "changed", G_CALLBACK(on_reproducible_changed), NULL);
|
|
|
2c83a8 |
}
|
|
|
2c83a8 |
|
|
|
2c83a8 |
static void create_details_treeview(void)
|
|
|
2c83a8 |
@@ -3649,6 +3826,7 @@ void create_assistant(GtkApplication *app, bool expert_mode)
|
|
|
2c83a8 |
|
|
|
2c83a8 |
g_signal_connect(g_tb_approve_bt, "toggled", G_CALLBACK(on_bt_approve_toggle), NULL);
|
|
|
2c83a8 |
g_signal_connect(gtk_text_view_get_buffer(g_tv_comment), "changed", G_CALLBACK(on_comment_changed), NULL);
|
|
|
2c83a8 |
+ g_signal_connect(gtk_text_view_get_buffer(g_tv_steps), "changed", G_CALLBACK(on_steps_changed), NULL);
|
|
|
2c83a8 |
|
|
|
2c83a8 |
g_signal_connect(g_btn_add_file, "clicked", G_CALLBACK(on_btn_add_file), NULL);
|
|
|
2c83a8 |
|
|
|
2c83a8 |
diff --git a/src/gui-wizard-gtk/wizard.glade b/src/gui-wizard-gtk/wizard.glade
|
|
|
2c83a8 |
index 441b2fc..1c45bd9 100644
|
|
|
2c83a8 |
--- a/src/gui-wizard-gtk/wizard.glade
|
|
|
2c83a8 |
+++ b/src/gui-wizard-gtk/wizard.glade
|
|
|
2c83a8 |
@@ -111,6 +111,7 @@
|
|
|
2c83a8 |
<property name="yalign">0</property>
|
|
|
2c83a8 |
<property name="label" translatable="yes">How did this problem happen (step-by-step)? How can it be reproduced? Any additional comments useful for diagnosing the problem? Please use English if possible.</property>
|
|
|
2c83a8 |
<property name="wrap">True</property>
|
|
|
2c83a8 |
+ <property name="xalign">0</property>
|
|
|
2c83a8 |
</object>
|
|
|
2c83a8 |
<packing>
|
|
|
2c83a8 |
<property name="expand">False</property>
|
|
|
2c83a8 |
@@ -122,12 +123,13 @@
|
|
|
2c83a8 |
<object class="GtkScrolledWindow" id="scrolledwindow4">
|
|
|
2c83a8 |
<property name="visible">True</property>
|
|
|
2c83a8 |
<property name="can_focus">True</property>
|
|
|
2c83a8 |
+ <property name="hexpand">True</property>
|
|
|
2c83a8 |
+ <property name="vexpand">True</property>
|
|
|
2c83a8 |
<property name="shadow_type">out</property>
|
|
|
2c83a8 |
<child>
|
|
|
2c83a8 |
<object class="GtkTextView" id="tv_comment">
|
|
|
2c83a8 |
<property name="visible">True</property>
|
|
|
2c83a8 |
<property name="can_focus">True</property>
|
|
|
2c83a8 |
- <property name="wrap_mode">word</property>
|
|
|
2c83a8 |
</object>
|
|
|
2c83a8 |
</child>
|
|
|
2c83a8 |
</object>
|
|
|
2c83a8 |
@@ -138,15 +140,92 @@
|
|
|
2c83a8 |
</packing>
|
|
|
2c83a8 |
</child>
|
|
|
2c83a8 |
<child>
|
|
|
2c83a8 |
- <object class="GtkEventBox" id="eb_comment">
|
|
|
2c83a8 |
+ <object class="GtkBox" id="vb_complex_details">
|
|
|
2c83a8 |
+ <property name="visible">True</property>
|
|
|
2c83a8 |
<property name="can_focus">False</property>
|
|
|
2c83a8 |
+ <property name="no_show_all">True</property>
|
|
|
2c83a8 |
+ <property name="orientation">vertical</property>
|
|
|
2c83a8 |
+ <child>
|
|
|
2c83a8 |
+ <object class="GtkLabel" id="lbl_reproducible">
|
|
|
2c83a8 |
+ <property name="visible">True</property>
|
|
|
2c83a8 |
+ <property name="can_focus">False</property>
|
|
|
2c83a8 |
+ <property name="label" translatable="yes">How reproducible is this problem?</property>
|
|
|
2c83a8 |
+ <property name="angle">0.02</property>
|
|
|
2c83a8 |
+ <property name="xalign">0</property>
|
|
|
2c83a8 |
+ </object>
|
|
|
2c83a8 |
+ <packing>
|
|
|
2c83a8 |
+ <property name="expand">False</property>
|
|
|
2c83a8 |
+ <property name="fill">True</property>
|
|
|
2c83a8 |
+ <property name="position">0</property>
|
|
|
2c83a8 |
+ </packing>
|
|
|
2c83a8 |
+ </child>
|
|
|
2c83a8 |
<child>
|
|
|
2c83a8 |
- <object class="GtkLabel" id="label5">
|
|
|
2c83a8 |
+ <object class="GtkComboBoxText" id="cmb_reproducible">
|
|
|
2c83a8 |
<property name="visible">True</property>
|
|
|
2c83a8 |
<property name="can_focus">False</property>
|
|
|
2c83a8 |
- <property name="label" translatable="yes">You need to fill the how to before you can proceed...</property>
|
|
|
2c83a8 |
- <property name="single_line_mode">True</property>
|
|
|
2c83a8 |
</object>
|
|
|
2c83a8 |
+ <packing>
|
|
|
2c83a8 |
+ <property name="expand">False</property>
|
|
|
2c83a8 |
+ <property name="fill">True</property>
|
|
|
2c83a8 |
+ <property name="position">1</property>
|
|
|
2c83a8 |
+ </packing>
|
|
|
2c83a8 |
+ </child>
|
|
|
2c83a8 |
+ <child>
|
|
|
2c83a8 |
+ <object class="GtkLabel" id="lbl_steps">
|
|
|
2c83a8 |
+ <property name="visible">True</property>
|
|
|
2c83a8 |
+ <property name="can_focus">False</property>
|
|
|
2c83a8 |
+ <property name="label" translatable="yes">How it can be reproduced (one step per line)?</property>
|
|
|
2c83a8 |
+ <property name="xalign">0</property>
|
|
|
2c83a8 |
+ <property name="margin_top">5</property>
|
|
|
2c83a8 |
+ </object>
|
|
|
2c83a8 |
+ <packing>
|
|
|
2c83a8 |
+ <property name="expand">False</property>
|
|
|
2c83a8 |
+ <property name="fill">True</property>
|
|
|
2c83a8 |
+ <property name="position">2</property>
|
|
|
2c83a8 |
+ </packing>
|
|
|
2c83a8 |
+ </child>
|
|
|
2c83a8 |
+ <child>
|
|
|
2c83a8 |
+ <object class="GtkScrolledWindow" id="sw_steps">
|
|
|
2c83a8 |
+ <property name="visible">True</property>
|
|
|
2c83a8 |
+ <property name="can_focus">True</property>
|
|
|
2c83a8 |
+ <property name="hexpand">True</property>
|
|
|
2c83a8 |
+ <property name="vexpand">True</property>
|
|
|
2c83a8 |
+ <property name="shadow_type">out</property>
|
|
|
2c83a8 |
+ <child>
|
|
|
2c83a8 |
+ <object class="GtkTextView" id="tv_steps">
|
|
|
2c83a8 |
+ <property name="visible">True</property>
|
|
|
2c83a8 |
+ <property name="can_focus">True</property>
|
|
|
2c83a8 |
+ </object>
|
|
|
2c83a8 |
+ </child>
|
|
|
2c83a8 |
+ </object>
|
|
|
2c83a8 |
+ <packing>
|
|
|
2c83a8 |
+ <property name="expand">True</property>
|
|
|
2c83a8 |
+ <property name="fill">True</property>
|
|
|
2c83a8 |
+ <property name="position">3</property>
|
|
|
2c83a8 |
+ </packing>
|
|
|
2c83a8 |
+ </child>
|
|
|
2c83a8 |
+ <child>
|
|
|
2c83a8 |
+ <object class="GtkEventBox" id="eb_complex_details">
|
|
|
2c83a8 |
+ <property name="visible">True</property>
|
|
|
2c83a8 |
+ <property name="can_focus">False</property>
|
|
|
2c83a8 |
+ <child>
|
|
|
2c83a8 |
+ <object class="GtkLabel" id="lbl_complex_details_hint">
|
|
|
2c83a8 |
+ <property name="visible">True</property>
|
|
|
2c83a8 |
+ <property name="can_focus">False</property>
|
|
|
2c83a8 |
+ <property name="label" translatable="yes">Please add a comprehensive description of the problem you have. This is a very long place holder.</property>
|
|
|
2c83a8 |
+ <property name="wrap">True</property>
|
|
|
2c83a8 |
+ <property name="xalign">0</property>
|
|
|
2c83a8 |
+ <attributes>
|
|
|
2c83a8 |
+ <attribute name="weight" value="bold"/>
|
|
|
2c83a8 |
+ </attributes>
|
|
|
2c83a8 |
+ </object>
|
|
|
2c83a8 |
+ </child>
|
|
|
2c83a8 |
+ </object>
|
|
|
2c83a8 |
+ <packing>
|
|
|
2c83a8 |
+ <property name="expand">True</property>
|
|
|
2c83a8 |
+ <property name="fill">True</property>
|
|
|
2c83a8 |
+ <property name="position">4</property>
|
|
|
2c83a8 |
+ </packing>
|
|
|
2c83a8 |
</child>
|
|
|
2c83a8 |
</object>
|
|
|
2c83a8 |
<packing>
|
|
|
2c83a8 |
@@ -156,19 +235,67 @@
|
|
|
2c83a8 |
</packing>
|
|
|
2c83a8 |
</child>
|
|
|
2c83a8 |
<child>
|
|
|
2c83a8 |
- <object class="GtkLabel" id="label3">
|
|
|
2c83a8 |
+ <object class="GtkBox" id="vb_simple_details">
|
|
|
2c83a8 |
<property name="visible">True</property>
|
|
|
2c83a8 |
<property name="can_focus">False</property>
|
|
|
2c83a8 |
- <property name="xalign">0</property>
|
|
|
2c83a8 |
- <property name="yalign">0</property>
|
|
|
2c83a8 |
- <property name="label" translatable="yes"><b>Your comments are not private.</b> They may be included into publicly visible problem reports.</property>
|
|
|
2c83a8 |
- <property name="use_markup">True</property>
|
|
|
2c83a8 |
- <property name="wrap">True</property>
|
|
|
2c83a8 |
+ <property name="no_show_all">True</property>
|
|
|
2c83a8 |
+ <property name="orientation">vertical</property>
|
|
|
2c83a8 |
+ <child>
|
|
|
2c83a8 |
+ <object class="GtkEventBox" id="eb_comment">
|
|
|
2c83a8 |
+ <property name="visible">True</property>
|
|
|
2c83a8 |
+ <property name="can_focus">False</property>
|
|
|
2c83a8 |
+ <child>
|
|
|
2c83a8 |
+ <object class="GtkLabel" id="label5">
|
|
|
2c83a8 |
+ <property name="visible">True</property>
|
|
|
2c83a8 |
+ <property name="can_focus">False</property>
|
|
|
2c83a8 |
+ <property name="label" translatable="yes">You need to fill the how to before you can proceed...</property>
|
|
|
2c83a8 |
+ <property name="single_line_mode">True</property>
|
|
|
2c83a8 |
+ </object>
|
|
|
2c83a8 |
+ </child>
|
|
|
2c83a8 |
+ </object>
|
|
|
2c83a8 |
+ <packing>
|
|
|
2c83a8 |
+ <property name="expand">False</property>
|
|
|
2c83a8 |
+ <property name="fill">True</property>
|
|
|
2c83a8 |
+ <property name="position">0</property>
|
|
|
2c83a8 |
+ </packing>
|
|
|
2c83a8 |
+ </child>
|
|
|
2c83a8 |
+ <child>
|
|
|
2c83a8 |
+ <object class="GtkLabel" id="label3">
|
|
|
2c83a8 |
+ <property name="visible">True</property>
|
|
|
2c83a8 |
+ <property name="can_focus">False</property>
|
|
|
2c83a8 |
+ <property name="halign">start</property>
|
|
|
2c83a8 |
+ <property name="valign">start</property>
|
|
|
2c83a8 |
+ <property name="label" translatable="yes"><b>Your comments are not private.</b> They may be included into publicly visible problem reports.</property>
|
|
|
2c83a8 |
+ <property name="use_markup">True</property>
|
|
|
2c83a8 |
+ <property name="wrap">True</property>
|
|
|
2c83a8 |
+ </object>
|
|
|
2c83a8 |
+ <packing>
|
|
|
2c83a8 |
+ <property name="expand">False</property>
|
|
|
2c83a8 |
+ <property name="fill">True</property>
|
|
|
2c83a8 |
+ <property name="position">1</property>
|
|
|
2c83a8 |
+ </packing>
|
|
|
2c83a8 |
+ </child>
|
|
|
2c83a8 |
+ <child>
|
|
|
2c83a8 |
+ <object class="GtkCheckButton" id="cb_no_comment">
|
|
|
2c83a8 |
+ <property name="label" translatable="yes">I don't know what caused this problem</property>
|
|
|
2c83a8 |
+ <property name="visible">True</property>
|
|
|
2c83a8 |
+ <property name="can_focus">True</property>
|
|
|
2c83a8 |
+ <property name="receives_default">False</property>
|
|
|
2c83a8 |
+ <property name="halign">start</property>
|
|
|
2c83a8 |
+ <property name="xalign">0</property>
|
|
|
2c83a8 |
+ <property name="draw_indicator">True</property>
|
|
|
2c83a8 |
+ </object>
|
|
|
2c83a8 |
+ <packing>
|
|
|
2c83a8 |
+ <property name="expand">False</property>
|
|
|
2c83a8 |
+ <property name="fill">True</property>
|
|
|
2c83a8 |
+ <property name="position">2</property>
|
|
|
2c83a8 |
+ </packing>
|
|
|
2c83a8 |
+ </child>
|
|
|
2c83a8 |
</object>
|
|
|
2c83a8 |
<packing>
|
|
|
2c83a8 |
<property name="expand">False</property>
|
|
|
2c83a8 |
<property name="fill">True</property>
|
|
|
2c83a8 |
- <property name="position">3</property>
|
|
|
2c83a8 |
+ <property name="position">4</property>
|
|
|
2c83a8 |
</packing>
|
|
|
2c83a8 |
</child>
|
|
|
2c83a8 |
<child>
|
|
|
2c83a8 |
@@ -208,21 +335,6 @@
|
|
|
2c83a8 |
<packing>
|
|
|
2c83a8 |
<property name="expand">False</property>
|
|
|
2c83a8 |
<property name="fill">True</property>
|
|
|
2c83a8 |
- <property name="position">4</property>
|
|
|
2c83a8 |
- </packing>
|
|
|
2c83a8 |
- </child>
|
|
|
2c83a8 |
- <child>
|
|
|
2c83a8 |
- <object class="GtkCheckButton" id="cb_no_comment">
|
|
|
2c83a8 |
- <property name="label" translatable="yes">I don't know what caused this problem</property>
|
|
|
2c83a8 |
- <property name="visible">True</property>
|
|
|
2c83a8 |
- <property name="can_focus">True</property>
|
|
|
2c83a8 |
- <property name="receives_default">False</property>
|
|
|
2c83a8 |
- <property name="xalign">0</property>
|
|
|
2c83a8 |
- <property name="draw_indicator">True</property>
|
|
|
2c83a8 |
- </object>
|
|
|
2c83a8 |
- <packing>
|
|
|
2c83a8 |
- <property name="expand">False</property>
|
|
|
2c83a8 |
- <property name="fill">True</property>
|
|
|
2c83a8 |
<property name="position">5</property>
|
|
|
2c83a8 |
</packing>
|
|
|
2c83a8 |
</child>
|
|
|
2c83a8 |
diff --git a/src/include/event_config.h b/src/include/event_config.h
|
|
|
2c83a8 |
index 7d137c1..fdcb3b4 100644
|
|
|
2c83a8 |
--- a/src/include/event_config.h
|
|
|
2c83a8 |
+++ b/src/include/event_config.h
|
|
|
2c83a8 |
@@ -82,6 +82,7 @@ typedef struct
|
|
|
2c83a8 |
bool ec_sending_sensitive_data;
|
|
|
2c83a8 |
bool ec_supports_restricted_access;
|
|
|
2c83a8 |
char *ec_restricted_access_option;
|
|
|
2c83a8 |
+ bool ec_requires_details;
|
|
|
2c83a8 |
|
|
|
2c83a8 |
GList *ec_imported_event_names;
|
|
|
2c83a8 |
GList *options;
|
|
|
2c83a8 |
diff --git a/src/include/internal_libreport.h b/src/include/internal_libreport.h
|
|
|
2c83a8 |
index 651e339..c5f899c 100644
|
|
|
2c83a8 |
--- a/src/include/internal_libreport.h
|
|
|
2c83a8 |
+++ b/src/include/internal_libreport.h
|
|
|
2c83a8 |
@@ -952,6 +952,10 @@ struct dump_dir *open_directory_for_writing(
|
|
|
2c83a8 |
#define FILENAME_ABRT_VERSION "abrt_version"
|
|
|
2c83a8 |
#define FILENAME_EXPLOITABLE "exploitable"
|
|
|
2c83a8 |
|
|
|
2c83a8 |
+/* reproducible element is used by functions from problem_data.h */
|
|
|
2c83a8 |
+#define FILENAME_REPRODUCIBLE "reproducible"
|
|
|
2c83a8 |
+#define FILENAME_REPRODUCER "reproducer"
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
// Not stored as files, added "on the fly":
|
|
|
2c83a8 |
#define CD_DUMPDIR "Directory"
|
|
|
2c83a8 |
|
|
|
2c83a8 |
diff --git a/src/include/problem_data.h b/src/include/problem_data.h
|
|
|
2c83a8 |
index 0fc8b78..d75a986 100644
|
|
|
2c83a8 |
--- a/src/include/problem_data.h
|
|
|
2c83a8 |
+++ b/src/include/problem_data.h
|
|
|
2c83a8 |
@@ -142,6 +142,17 @@ problem_data_t *create_problem_data_for_reporting(const char *dump_dir_name);
|
|
|
2c83a8 |
struct dump_dir *create_dump_dir_from_problem_data(problem_data_t *problem_data, const char *base_dir_name);
|
|
|
2c83a8 |
struct dump_dir *create_dump_dir_from_problem_data_ext(problem_data_t *problem_data, const char *base_dir_name, uid_t uid);
|
|
|
2c83a8 |
|
|
|
2c83a8 |
+enum {
|
|
|
2c83a8 |
+ PROBLEM_REPRODUCIBLE_UNKNOWN,
|
|
|
2c83a8 |
+ PROBLEM_REPRODUCIBLE_YES,
|
|
|
2c83a8 |
+ PROBLEM_REPRODUCIBLE_RECURRENT,
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ _PROBLEM_REPRODUCIBLE_MAX_,
|
|
|
2c83a8 |
+};
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+int get_problem_data_reproducible(problem_data_t *problem_data);
|
|
|
2c83a8 |
+const char *get_problem_data_reproducible_name(int reproducible);
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
#ifdef __cplusplus
|
|
|
2c83a8 |
}
|
|
|
2c83a8 |
#endif
|
|
|
2c83a8 |
diff --git a/src/lib/event_xml_parser.c b/src/lib/event_xml_parser.c
|
|
|
2c83a8 |
index a5e3d3e..aec2ba4 100644
|
|
|
2c83a8 |
--- a/src/lib/event_xml_parser.c
|
|
|
2c83a8 |
+++ b/src/lib/event_xml_parser.c
|
|
|
2c83a8 |
@@ -35,6 +35,7 @@
|
|
|
2c83a8 |
#define SENDING_SENSITIVE_DATA_ELEMENT "sending-sensitive-data"
|
|
|
2c83a8 |
#define SUPPORTS_RESTRICTED_ACCESS_ELEMENT "support-restricted-access"
|
|
|
2c83a8 |
#define RESTRICTED_ACCESS_OPTION_ATTR "optionname"
|
|
|
2c83a8 |
+#define REQUIRES_DETAILS "requires-details"
|
|
|
2c83a8 |
|
|
|
2c83a8 |
#define REQUIRES_ELEMENT "requires-items"
|
|
|
2c83a8 |
#define EXCL_BY_DEFAULT_ELEMENT "exclude-items-by-default"
|
|
|
2c83a8 |
@@ -509,6 +510,10 @@ static void text(GMarkupParseContext *context,
|
|
|
2c83a8 |
{
|
|
|
2c83a8 |
ui->ec_supports_restricted_access = string_to_bool(text_copy);
|
|
|
2c83a8 |
}
|
|
|
2c83a8 |
+ else if (strcmp(inner_element, REQUIRES_DETAILS) == 0)
|
|
|
2c83a8 |
+ {
|
|
|
2c83a8 |
+ ui->ec_requires_details = string_to_bool(text_copy);
|
|
|
2c83a8 |
+ }
|
|
|
2c83a8 |
}
|
|
|
2c83a8 |
free(text_copy);
|
|
|
2c83a8 |
}
|
|
|
2c83a8 |
diff --git a/src/lib/problem_data.c b/src/lib/problem_data.c
|
|
|
2c83a8 |
index 9e625bd..2f66fb3 100644
|
|
|
2c83a8 |
--- a/src/lib/problem_data.c
|
|
|
2c83a8 |
+++ b/src/lib/problem_data.c
|
|
|
2c83a8 |
@@ -637,3 +637,38 @@ void problem_data_get_osinfo(problem_data_t *problem_data, map_string_t *osinfo)
|
|
|
2c83a8 |
problem_data_get_osinfo_from_items(problem_data, osinfo,
|
|
|
2c83a8 |
FILENAME_OS_INFO, FILENAME_OS_RELEASE);
|
|
|
2c83a8 |
}
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+static const gchar const* reproducible_names[_PROBLEM_REPRODUCIBLE_MAX_] =
|
|
|
2c83a8 |
+{
|
|
|
2c83a8 |
+ "Not sure how to reproduce the problem",
|
|
|
2c83a8 |
+ "The problem is reproducible",
|
|
|
2c83a8 |
+ "The problem occurs regularly",
|
|
|
2c83a8 |
+};
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+int get_problem_data_reproducible(problem_data_t *problem_data)
|
|
|
2c83a8 |
+{
|
|
|
2c83a8 |
+ const char *reproducible_str = problem_data_get_content_or_NULL(problem_data, FILENAME_REPRODUCIBLE);
|
|
|
2c83a8 |
+ if (reproducible_str == NULL)
|
|
|
2c83a8 |
+ {
|
|
|
2c83a8 |
+ log_info("Cannot return Reproducible type: missing "FILENAME_REPRODUCIBLE);
|
|
|
2c83a8 |
+ return -1;
|
|
|
2c83a8 |
+ }
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ for (int i = 0; i < _PROBLEM_REPRODUCIBLE_MAX_; ++i)
|
|
|
2c83a8 |
+ if (strcmp(reproducible_str, reproducible_names[i]) == 0)
|
|
|
2c83a8 |
+ return i;
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ error_msg("Cannot return Reproducible type: invalid format of '%s'", FILENAME_REPRODUCIBLE);
|
|
|
2c83a8 |
+ return -1;
|
|
|
2c83a8 |
+}
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+const char *get_problem_data_reproducible_name(int reproducible)
|
|
|
2c83a8 |
+{
|
|
|
2c83a8 |
+ if (reproducible < 0 || reproducible >= _PROBLEM_REPRODUCIBLE_MAX_)
|
|
|
2c83a8 |
+ {
|
|
|
2c83a8 |
+ error_msg("Cannot return Reproducible name: invalid code: %d", reproducible);
|
|
|
2c83a8 |
+ return NULL;
|
|
|
2c83a8 |
+ }
|
|
|
2c83a8 |
+
|
|
|
2c83a8 |
+ return reproducible_names[reproducible];
|
|
|
2c83a8 |
+}
|
|
|
2c83a8 |
--
|
|
|
2c83a8 |
1.8.3.1
|
|
|
2c83a8 |
|