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