You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

995 lines
44 KiB

/*******************************************************************
* reportassistantpages_bugzilla_duplicates.cpp
* Copyright 2009 Dario Andres Rodriguez <andresbajotierra@gmail.com>
*
* 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, see <http://www.gnu.org/licenses/>.
*
******************************************************************/
#include "reportassistantpages_bugzilla_duplicates.h"
#include <QtCore/QDate>
#include <QtCore/QTimer>
#include <QTreeWidgetItem>
#include <QHeaderView>
#include <KColorScheme>
#include <KMessageBox>
#include <QInputDialog>
#include <KLocalizedString>
#include <KWindowConfig>
#include "drkonqi_globals.h"
#include "reportinterface.h"
#include "statuswidget.h"
//BEGIN BugzillaDuplicatesPage
BugzillaDuplicatesPage::BugzillaDuplicatesPage(ReportAssistantDialog * parent):
ReportAssistantPage(parent),
m_searching(false),
m_foundDuplicate(false)
{
resetDates();
connect(bugzillaManager(), &BugzillaManager::searchFinished,
this, &BugzillaDuplicatesPage::searchFinished);
connect(bugzillaManager(), SIGNAL(searchError(QString)),
this, SLOT(searchError(QString)));
ui.setupUi(this);
ui.information->hide();
connect(ui.m_bugListWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)),
this, SLOT(itemClicked(QTreeWidgetItem*,int)));
connect(ui.m_bugListWidget, &QTreeWidget::itemSelectionChanged, this, &BugzillaDuplicatesPage::itemSelectionChanged);
QHeaderView * header = ui.m_bugListWidget->header();
header->setResizeMode(0, QHeaderView::ResizeToContents);
header->setResizeMode(1, QHeaderView::Interactive);
//Create manual bug report entry (first one)
QTreeWidgetItem * customBugItem = new QTreeWidgetItem(
QStringList() << i18nc("@item:intable custom/manaul bug report number", "Manual")
<< i18nc("@item:intable custom bug report number description",
"Manually enter a bug report ID"));
customBugItem->setData(0, Qt::UserRole, QLatin1String("custom"));
customBugItem->setIcon(1, QIcon::fromTheme(QStringLiteral("edit-rename")));
QString helpMessage = i18nc("@info:tooltip / whatsthis",
"Select this option to manually load a specific bug report");
customBugItem->setToolTip(0, helpMessage);
customBugItem->setToolTip(1, helpMessage);
customBugItem->setWhatsThis(0, helpMessage);
customBugItem->setWhatsThis(1, helpMessage);
ui.m_bugListWidget->addTopLevelItem(customBugItem);
m_searchMoreGuiItem = KGuiItem2(i18nc("@action:button", "Search for more reports"),
QIcon::fromTheme(QStringLiteral("edit-find")),
i18nc("@info:tooltip", "Use this button to "
"search for more similar bug reports on an "
"earlier date."));
KGuiItem::assign(ui.m_searchMoreButton, m_searchMoreGuiItem);
connect(ui.m_searchMoreButton, &QAbstractButton::clicked, this, &BugzillaDuplicatesPage::searchMore);
m_retrySearchGuiItem = KGuiItem2(i18nc("@action:button", "Retry search"),
QIcon::fromTheme(QStringLiteral("edit-find")),
i18nc("@info:tooltip", "Use this button to "
"retry the search that previously "
"failed."));
KGuiItem::assign(ui.m_openReportButton, KGuiItem2(i18nc("@action:button", "Open selected report"),
QIcon::fromTheme(QStringLiteral("document-preview")),
i18nc("@info:tooltip", "Use this button to view "
"the information of the selected bug report.")));
connect(ui.m_openReportButton, &QAbstractButton::clicked, this, &BugzillaDuplicatesPage::openSelectedReport);
KGuiItem::assign(ui.m_stopSearchButton, KGuiItem2(i18nc("@action:button", "Stop searching"),
QIcon::fromTheme(QStringLiteral("process-stop")),
i18nc("@info:tooltip", "Use this button to stop "
"the current search.")));
ui.m_stopSearchButton->setText(QString()); //FIXME
connect(ui.m_stopSearchButton, &QAbstractButton::clicked, this, &BugzillaDuplicatesPage::stopCurrentSearch);
//Possible duplicates list and buttons
connect(ui.m_selectedDuplicatesList, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
this, SLOT(itemClicked(QListWidgetItem*)));
connect(ui.m_selectedDuplicatesList, &QListWidget::itemSelectionChanged,
this, &BugzillaDuplicatesPage::possibleDuplicateSelectionChanged);
KGuiItem::assign(ui.m_removeSelectedDuplicateButton,
KGuiItem2(i18nc("@action:button remove the selected item from a list", "Remove"),
QIcon::fromTheme(QStringLiteral("list-remove")),
i18nc("@info:tooltip", "Use this button to remove a selected possible duplicate")));
ui.m_removeSelectedDuplicateButton->setEnabled(false);
connect(ui.m_removeSelectedDuplicateButton, &QAbstractButton::clicked, this,
&BugzillaDuplicatesPage::removeSelectedDuplicate);
ui.m_attachToReportIcon->setPixmap(QIcon::fromTheme(QStringLiteral("mail-attachment")).pixmap(16,16));
ui.m_attachToReportIcon->setFixedSize(16,16);
ui.m_attachToReportIcon->setVisible(false);
ui.m_attachToReportLabel->setVisible(false);
ui.m_attachToReportLabel->setContextMenuPolicy(Qt::NoContextMenu);
connect(ui.m_attachToReportLabel, &QLabel::linkActivated, this,
&BugzillaDuplicatesPage::cancelAttachToBugReport);
ui.information->setContextMenuPolicy(Qt::NoContextMenu);
connect(ui.information, &QLabel::linkActivated, this, &BugzillaDuplicatesPage::informationClicked);
showDuplicatesPanel(false);
}
BugzillaDuplicatesPage::~BugzillaDuplicatesPage()
{
}
void BugzillaDuplicatesPage::aboutToShow()
{
//Perform initial search if we are not currently searching and if there are no results yet
if (!m_searching && ui.m_bugListWidget->topLevelItemCount() == 1 && canSearchMore()) {
searchMore();
}
}
void BugzillaDuplicatesPage::aboutToHide()
{
stopCurrentSearch();
//Save selected possible duplicates by user
QStringList possibleDuplicates;
int count = ui.m_selectedDuplicatesList->count();
for(int i = 0; i<count; i++) {
possibleDuplicates << ui.m_selectedDuplicatesList->item(i)->text();
}
reportInterface()->setPossibleDuplicates(possibleDuplicates);
//Save possible duplicates by query
QStringList duplicatesByQuery;
count = ui.m_bugListWidget->topLevelItemCount();
for(int i = 1; i<count; i++) {
duplicatesByQuery << ui.m_bugListWidget->topLevelItem(i)->text(0);
}
reportInterface()->setPossibleDuplicatesByQuery(duplicatesByQuery);
}
bool BugzillaDuplicatesPage::isComplete()
{
return !m_searching;
}
bool BugzillaDuplicatesPage::showNextPage()
{
//Ask the user to check all the possible duplicates...
if (ui.m_bugListWidget->topLevelItemCount() != 1 && ui.m_selectedDuplicatesList->count() == 0
&& reportInterface()->attachToBugNumber() == 0 && !m_foundDuplicate) {
//The user didn't selected any possible duplicate nor a report to attach the new info.
//Double check this, we need to reduce the duplicate count.
KGuiItem noDuplicatesButton;
noDuplicatesButton.setText(i18n("There are no real duplicates"));
noDuplicatesButton.setWhatsThis(i18n("Press this button to declare that, in your opinion "
"and according to your experience, the reports found "
"as similar do not match the crash you have "
"experienced, and you believe it is unlikely that a "
"better match would be found after further review."));
noDuplicatesButton.setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel")));
KGuiItem letMeCheckMoreReportsButton;
letMeCheckMoreReportsButton.setText(i18n("Let me check more reports"));
letMeCheckMoreReportsButton.setWhatsThis(i18n("Press this button if you would rather "
"review more reports in order to find a "
"match for the crash you have experienced."));
letMeCheckMoreReportsButton.setIcon(QIcon::fromTheme(QStringLiteral("document-preview")));
if (KMessageBox::questionYesNo(this,
i18nc("@info","You have not selected any possible duplicates, or a report to which to attach your "
"crash information. Have you read all the reports, and can you confirm that there are no "
"real duplicates?"),
i18nc("@title:window","No selected possible duplicates"), letMeCheckMoreReportsButton,
noDuplicatesButton)
== KMessageBox::Yes) {
return false;
}
}
return true;
}
//BEGIN Search related methods
void BugzillaDuplicatesPage::searchMore()
{
//1 year back
m_searchingEndDate = m_startDate;
m_searchingStartDate = m_searchingEndDate.addYears(-1);
performSearch();
}
void BugzillaDuplicatesPage::performSearch()
{
markAsSearching(true);
QString startDateStr = m_searchingStartDate.toString(QStringLiteral("yyyy-MM-dd"));
QString endDateStr = m_searchingEndDate.toString(QStringLiteral("yyyy-MM-dd"));
ui.m_statusWidget->setBusy(i18nc("@info:status","Searching for duplicates (from %1 to %2)...",
startDateStr, endDateStr));
//Bugzilla will not search on Today bugs if we send the date.
//we need to send "Now"
if (m_searchingEndDate == QDate::currentDate()) {
endDateStr = QLatin1String("Now");
}
#if 1
BugReport report = reportInterface()->newBugReportTemplate();
bugzillaManager()->searchBugs(reportInterface()->relatedBugzillaProducts(),
report.bugSeverity(), startDateStr, endDateStr,
reportInterface()->firstBacktraceFunctions().join(QStringLiteral(" ")));
#else //Test search
bugzillaManager()->searchBugs(QStringList() << "plasma", "crash", startDateStr, endDateStr,
"QGraphicsScenePrivate::processDirtyItemsRecursive");
#endif
}
void BugzillaDuplicatesPage::stopCurrentSearch()
{
if (m_searching) {
bugzillaManager()->stopCurrentSearch();
markAsSearching(false);
if (m_startDate==m_endDate) { //Never searched
ui.m_statusWidget->setIdle(i18nc("@info:status","Search stopped."));
} else {
ui.m_statusWidget->setIdle(i18nc("@info:status","Search stopped. Showing results from "
"%1 to %2", m_startDate.toString(QStringLiteral("yyyy-MM-dd")),
m_endDate.toString(QStringLiteral("yyyy-MM-dd"))));
}
}
}
void BugzillaDuplicatesPage::markAsSearching(bool searching)
{
m_searching = searching;
emitCompleteChanged();
ui.m_bugListWidget->setEnabled(!searching);
ui.m_searchMoreButton->setEnabled(!searching);
ui.m_searchMoreButton->setVisible(!searching);
ui.m_stopSearchButton->setEnabled(searching);
ui.m_stopSearchButton->setVisible(searching);
ui.m_selectedDuplicatesList->setEnabled(!searching);
ui.m_selectedPossibleDuplicatesLabel->setEnabled(!searching);
ui.m_removeSelectedDuplicateButton->setEnabled(!searching &&
!ui.m_selectedDuplicatesList->selectedItems().isEmpty());
ui.m_attachToReportLabel->setEnabled(!searching);
if (!searching) {
itemSelectionChanged();
} else {
ui.m_openReportButton->setEnabled(false);
}
}
bool BugzillaDuplicatesPage::canSearchMore()
{
return (m_startDate.year() >= 2009);
}
void BugzillaDuplicatesPage::searchFinished(const BugMapList & list)
{
KGuiItem::assign(ui.m_searchMoreButton, m_searchMoreGuiItem);
m_startDate = m_searchingStartDate;
int results = list.count();
if (results > 0) {
markAsSearching(false);
ui.m_statusWidget->setIdle(i18nc("@info:status","Showing results from %1 to %2",
m_startDate.toString(QStringLiteral("yyyy-MM-dd")),
m_endDate.toString(QStringLiteral("yyyy-MM-dd"))));
QList<int> bugIds;
for (int i = 0; i < results; i++) {
BugMap bug = list.at(i);
bool ok;
int bugId = bug.value(QStringLiteral("bug_id")).toInt(&ok);
if (ok) {
bugIds << bugId;
}
QString title;
//Generate a non-geek readable status
QString customStatusString;
BugReport::Status status = BugReport::parseStatus(bug.value(QStringLiteral("bug_status")));
BugReport::Resolution resolution = BugReport::parseResolution(bug.value(QStringLiteral("resolution")));
if (BugReport::isOpen(status)) {
customStatusString = i18nc("@info bug status", "[Open]");
} else if (BugReport::isClosed(status) && status != BugReport::NeedsInfo) {
if (resolution == BugReport::Fixed) {
customStatusString = i18nc("@info bug resolution", "[Fixed]");
} else if (resolution == BugReport::WorksForMe) {
customStatusString = i18nc("@info bug resolution", "[Non-reproducible]");
} else if (resolution == BugReport::Duplicate) {
customStatusString = i18nc("@info bug resolution", "[Duplicate report]");
} else if (resolution == BugReport::Invalid) {
customStatusString = i18nc("@info bug resolution", "[Invalid]");
} else if (resolution == BugReport::Downstream
|| resolution == BugReport::Upstream) {
customStatusString = i18nc("@info bug resolution", "[External problem]");
}
} else if (status == BugReport::NeedsInfo) {
customStatusString = i18nc("@info bug status", "[Incomplete]");
}
title = customStatusString + ' ' + bug[QStringLiteral("short_desc")];
QStringList fields = QStringList() << bug[QStringLiteral("bug_id")] << title;
QTreeWidgetItem * item = new QTreeWidgetItem(fields);
item->setToolTip(0, bug[QStringLiteral("short_desc")]);
item->setToolTip(1, bug[QStringLiteral("short_desc")]);
ui.m_bugListWidget->addTopLevelItem(item);
}
if (!m_foundDuplicate) {
markAsSearching(true);
DuplicateFinderJob *job = new DuplicateFinderJob(bugIds, bugzillaManager(), this);
connect(job, SIGNAL(result(KJob*)), this, SLOT(analyzedDuplicates(KJob*)));
job->start();
}
ui.m_bugListWidget->sortItems(0 , Qt::DescendingOrder);
ui.m_bugListWidget->resizeColumnToContents(1);
if (!canSearchMore()) {
ui.m_searchMoreButton->setEnabled(false);
}
} else {
if (canSearchMore()) {
//We don't call markAsSearching(false) to avoid flicker
//Delayed call to searchMore to avoid unexpected behaviour (signal/slot)
//because we are in a slot, and searchMore() will be ending calling this slot again
QTimer::singleShot(0, this, &BugzillaDuplicatesPage::searchMore);
} else {
markAsSearching(false);
ui.m_statusWidget->setIdle(i18nc("@info:status","Search Finished. "
"No reports found."));
ui.m_searchMoreButton->setEnabled(false);
if (ui.m_bugListWidget->topLevelItemCount() == 0) {
//No reports to mark as possible duplicate
ui.m_selectedDuplicatesList->setEnabled(false);
}
}
}
}
void BugzillaDuplicatesPage::analyzedDuplicates(KJob *j)
{
markAsSearching(false);
DuplicateFinderJob *job = static_cast<DuplicateFinderJob*>(j);
m_result = job->result();
m_foundDuplicate = m_result.parentDuplicate;
reportInterface()->setDuplicateId(m_result.parentDuplicate);
ui.m_searchMoreButton->setEnabled(!m_foundDuplicate);
ui.information->setVisible(m_foundDuplicate);
BugReport::Status status = m_result.status;
const int duplicate = m_result.duplicate;
const int parentDuplicate = m_result.parentDuplicate;
if (m_foundDuplicate) {
const QList<QTreeWidgetItem*> items = ui.m_bugListWidget->findItems(QString::number(parentDuplicate), Qt::MatchExactly, 0);
const QBrush brush = KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NeutralBackground);
Q_FOREACH (QTreeWidgetItem* item, items) {
for (int i = 0; i < item->columnCount(); ++i) {
item->setBackground(i, brush);
}
}
QString text;
if (BugReport::isOpen(status) || (BugReport::isClosed(status) && status == BugReport::NeedsInfo)) {
text = (parentDuplicate == duplicate ? i18nc("@label", "Your crash is a <strong>duplicate</strong> and has already been reported as <a href=\"%1\">Bug %1</a>.", QString::number(duplicate)) :
i18nc("@label", "Your crash has already been reported as <a href=\"%1\">Bug %1</a>, which is a <strong>duplicate</strong> of <a href=\"%2\">Bug %2</a>", QString::number(duplicate), QString::number(parentDuplicate))) +
'\n' + i18nc("@label", "Only <strong><a href=\"%1\">attach</a></strong> if you can add needed information to the bug report.", QStringLiteral("attach"));
} else if (BugReport::isClosed(status)) {
text = (parentDuplicate == duplicate ? i18nc("@label", "Your crash has already been reported as <a href=\"%1\">Bug %1</a> which has been <strong>closed</strong>.", QString::number(duplicate)) :
i18nc("@label", "Your crash has already been reported as <a href=\"%1\">Bug %1</a>, which is a duplicate of the <strong>closed</strong> <a href=\"%2\">Bug %2</a>.", QString::number(duplicate), QString::number(parentDuplicate)));
}
ui.information->setText(text);
}
}
void BugzillaDuplicatesPage::informationClicked(const QString &activatedLink)
{
if (activatedLink == QLatin1String("attach")) {
attachToBugReport(m_result.parentDuplicate);
} else {
int number = activatedLink.toInt();
if (number) {
showReportInformationDialog(number, false);
}
}
}
void BugzillaDuplicatesPage::searchError(QString err)
{
KGuiItem::assign(ui.m_searchMoreButton, m_retrySearchGuiItem);
markAsSearching(false);
ui.m_statusWidget->setIdle(i18nc("@info:status","Error fetching the bug report list"));
KMessageBox::error(this , xi18nc("@info/rich","Error fetching the bug report list<nl/>"
"<message>%1.</message><nl/>"
"Please wait some time and try again.", err));
}
void BugzillaDuplicatesPage::resetDates()
{
m_endDate = QDate::currentDate();
m_startDate = m_endDate;
}
//END Search related methods
//BEGIN Duplicates list related methods
void BugzillaDuplicatesPage::openSelectedReport()
{
QList<QTreeWidgetItem*> selected = ui.m_bugListWidget->selectedItems();
if (selected.count() == 1) {
itemClicked(selected.at(0), 0);
}
}
void BugzillaDuplicatesPage::itemClicked(QTreeWidgetItem * item, int col)
{
Q_UNUSED(col);
int bugNumber = 0;
if (item->data(0, Qt::UserRole) == QLatin1String("custom")) {
bool ok = false;
bugNumber = QInputDialog::getInteger(this,
i18nc("@title:window", "Enter a custom bug report number"),
i18nc("@label", "Enter the number of the bug report you want to check"),
0, 0, 1000000, 1, &ok);
} else {
bugNumber = item->text(0).toInt();
}
showReportInformationDialog(bugNumber);
}
void BugzillaDuplicatesPage::itemClicked(QListWidgetItem * item)
{
showReportInformationDialog(item->text().toInt());
}
void BugzillaDuplicatesPage::showReportInformationDialog(int bugNumber, bool relatedButtonEnabled)
{
if (bugNumber <= 0) {
return;
}
BugzillaReportInformationDialog * infoDialog = new BugzillaReportInformationDialog(this);
connect(infoDialog, &BugzillaReportInformationDialog::possibleDuplicateSelected, this,
&BugzillaDuplicatesPage::addPossibleDuplicateNumber);
connect(infoDialog, &BugzillaReportInformationDialog::attachToBugReportSelected, this, &BugzillaDuplicatesPage::attachToBugReport);
infoDialog->showBugReport(bugNumber, relatedButtonEnabled);
}
void BugzillaDuplicatesPage::itemSelectionChanged()
{
ui.m_openReportButton->setEnabled(ui.m_bugListWidget->selectedItems().count() == 1);
}
//END Duplicates list related methods
//BEGIN Selected duplicates list related methods
void BugzillaDuplicatesPage::addPossibleDuplicateNumber(int bugNumber)
{
QString stringNumber = QString::number(bugNumber);
if (ui.m_selectedDuplicatesList->findItems(stringNumber, Qt::MatchExactly).isEmpty()) {
ui.m_selectedDuplicatesList->addItem(stringNumber);
}
showDuplicatesPanel(true);
}
void BugzillaDuplicatesPage::removeSelectedDuplicate()
{
QList<QListWidgetItem*> items = ui.m_selectedDuplicatesList->selectedItems();
if (items.length() > 0) {
delete ui.m_selectedDuplicatesList->takeItem(ui.m_selectedDuplicatesList->row(items.at(0)));
}
if (ui.m_selectedDuplicatesList->count() == 0) {
showDuplicatesPanel(false);
}
}
void BugzillaDuplicatesPage::showDuplicatesPanel(bool show)
{
ui.m_removeSelectedDuplicateButton->setVisible(show);
ui.m_selectedDuplicatesList->setVisible(show);
ui.m_selectedPossibleDuplicatesLabel->setVisible(show);
}
void BugzillaDuplicatesPage::possibleDuplicateSelectionChanged()
{
ui.m_removeSelectedDuplicateButton->setEnabled(
!ui.m_selectedDuplicatesList->selectedItems().isEmpty());
}
//END Selected duplicates list related methods
//BEGIN Attach to bug related methods
void BugzillaDuplicatesPage::attachToBugReport(int bugNumber)
{
ui.m_attachToReportLabel->setText(xi18nc("@label", "The report is going to be "
"<strong>attached</strong> to bug %1. "
"<a href=\"#\">Cancel</a>", QString::number(bugNumber)));
ui.m_attachToReportLabel->setVisible(true);
ui.m_attachToReportIcon->setVisible(true);
reportInterface()->setAttachToBugNumber(bugNumber);
}
void BugzillaDuplicatesPage::cancelAttachToBugReport()
{
ui.m_attachToReportLabel->setVisible(false);
ui.m_attachToReportIcon->setVisible(false);
reportInterface()->setAttachToBugNumber(0);
}
//END Attach to bug related methods
//END BugzillaDuplicatesPage
//BEGIN BugzillaReportInformationDialog
BugzillaReportInformationDialog::BugzillaReportInformationDialog(BugzillaDuplicatesPage * parent) :
QDialog(parent),
m_relatedButtonEnabled(true),
m_parent(parent),
m_bugNumber(0),
m_duplicatesCount(0)
{
setWindowTitle(i18nc("@title:window","Bug Description"));
ui.setupUi(this);
KGuiItem::assign(ui.m_retryButton, KGuiItem2(i18nc("@action:button", "Retry..."),
QIcon::fromTheme(QStringLiteral("view-refresh")),
i18nc("@info:tooltip", "Use this button to retry "
"loading the bug report.")));
connect(ui.m_retryButton, &QPushButton::clicked, this, &BugzillaReportInformationDialog::reloadReport);
m_suggestButton = new QPushButton(this);
ui.buttonBox->addButton(m_suggestButton, QDialogButtonBox::ActionRole);
KGuiItem::assign(m_suggestButton,
KGuiItem2(i18nc("@action:button", "Suggest this crash is related"),
QIcon::fromTheme(QStringLiteral("list-add")), i18nc("@info:tooltip", "Use this button to suggest that "
"the crash you experienced is related to this bug "
"report")));
connect(m_suggestButton, &QPushButton::clicked, this, &BugzillaReportInformationDialog::relatedReportClicked);
connect(ui.m_showOwnBacktraceCheckBox, &QAbstractButton::toggled, this, &BugzillaReportInformationDialog::toggleShowOwnBacktrace);
//Connect bugzillalib signals
connect(m_parent->bugzillaManager(), &BugzillaManager::bugReportFetched,
this, &BugzillaReportInformationDialog::bugFetchFinished);
connect(m_parent->bugzillaManager(), SIGNAL(bugReportError(QString,QObject*)),
this, SLOT(bugFetchError(QString,QObject*)));
resize(QSize(800, 600));
KConfigGroup config(KSharedConfig::openConfig(), "BugzillaReportInformationDialog");
KWindowConfig::restoreWindowSize(windowHandle(), config);
}
BugzillaReportInformationDialog::~BugzillaReportInformationDialog()
{
disconnect(m_parent->bugzillaManager(), &BugzillaManager::bugReportFetched,
this, &BugzillaReportInformationDialog::bugFetchFinished);
disconnect(m_parent->bugzillaManager(), SIGNAL(bugReportError(QString,QObject*)),
this, SLOT(bugFetchError(QString,QObject*)));
KConfigGroup config(KSharedConfig::openConfig(), "BugzillaReportInformationDialog");
KWindowConfig::saveWindowSize(windowHandle(), config);
}
void BugzillaReportInformationDialog::reloadReport()
{
showBugReport(m_bugNumber);
}
void BugzillaReportInformationDialog::showBugReport(int bugNumber, bool relatedButtonEnabled)
{
m_relatedButtonEnabled = relatedButtonEnabled;
ui.m_retryButton->setVisible(false);
m_closedStateString.clear();
m_bugNumber = bugNumber;
m_parent->bugzillaManager()->fetchBugReport(m_bugNumber, this);
m_suggestButton->setEnabled(false);
m_suggestButton->setVisible(m_relatedButtonEnabled);
ui.m_infoBrowser->setText(i18nc("@info:status","Loading..."));
ui.m_infoBrowser->setEnabled(false);
ui.m_linkLabel->setText(xi18nc("@info","<link url='%1'>Report's webpage</link>",
m_parent->bugzillaManager()->urlForBug(m_bugNumber)));
ui.m_statusWidget->setBusy(xi18nc("@info:status","Loading information about bug "
"%1 from %2....",
QString::number(m_bugNumber),
QLatin1String(KDE_BUGZILLA_SHORT_URL)));
ui.m_backtraceBrowser->setPlainText(
i18nc("@info","Backtrace of the crash I experienced:\n\n") +
m_parent->reportInterface()->backtrace());
KConfigGroup config(KSharedConfig::openConfig(), "BugzillaReportInformationDialog");
bool showOwnBacktrace = config.readEntry("ShowOwnBacktrace", false);
ui.m_showOwnBacktraceCheckBox->setChecked(showOwnBacktrace);
if (!showOwnBacktrace) { //setChecked(false) will not emit toggled(false)
toggleShowOwnBacktrace(false);
}
show();
}
void BugzillaReportInformationDialog::bugFetchFinished(BugReport report, QObject * jobOwner)
{
if (jobOwner == this && isVisible()) {
if (report.isValid()) {
//Handle duplicate state
QString duplicate = report.markedAsDuplicateOf();
if (!duplicate.isEmpty()) {
bool ok = false;
int dupId = duplicate.toInt(&ok);
if (ok && dupId > 0) {
ui.m_statusWidget->setIdle(QString());
KGuiItem yesItem = KStandardGuiItem::yes();
yesItem.setText(i18nc("@action:button let the user to choose to read the "
"main report", "Yes, read the main report"));
KGuiItem noItem = KStandardGuiItem::no();
noItem.setText(i18nc("@action:button let the user choose to read the original "
"report", "No, let me read the report I selected"));
if (KMessageBox::questionYesNo(this,
xi18nc("@info","The report you selected (bug %1) is already "
"marked as duplicate of bug %2. "
"Do you want to read that report instead? (recommended)",
report.bugNumber(), QString::number(dupId)),
i18nc("@title:window","Nested duplicate detected"), yesItem, noItem)
== KMessageBox::Yes) {
showBugReport(dupId);
return;
}
}
}
//Generate html for comments (with proper numbering)
QLatin1String duplicatesMark = QLatin1String("has been marked as a duplicate of this bug.");
QString comments;
QStringList commentList = report.comments();
for (int i = 0; i < commentList.count(); i++) {
QString comment = commentList.at(i);
//Don't add duplicates mark comments
if (!comment.contains(duplicatesMark)) {
comment.replace('\n', QLatin1String("<br />"));
comments += i18nc("comment $number to use as subtitle", "<h4>Comment %1:</h4>", (i+1))
+ "<p>" + comment + "</p><hr />";
//Count the inline attached crashes (DrKonqi feature)
QLatin1String attachedCrashMark =
QLatin1String("New crash information added by DrKonqi");
if (comment.contains(attachedCrashMark)) {
m_duplicatesCount++;
}
} else {
//Count duplicate
m_duplicatesCount++;
}
}
//Generate a non-geek readable status
QString customStatusString;
BugReport::Status status = report.statusValue();
BugReport::Resolution resolution = report.resolutionValue();
if (status == BugReport::Unconfirmed) {
customStatusString = i18nc("@info bug status", "Opened (Unconfirmed)");
} else if (report.isOpen()) {
customStatusString = i18nc("@info bug status", "Opened (Unfixed)");
} else if (report.isClosed() && status != BugReport::NeedsInfo) {
QString customResolutionString;
if (resolution == BugReport::Fixed) {
if (!report.versionFixedIn().isEmpty()) {
customResolutionString = i18nc("@info bug resolution, fixed in version",
"Fixed in version \"%1\"",
report.versionFixedIn());
m_closedStateString = i18nc("@info bug resolution, fixed by kde devs in version",
"the bug was fixed by KDE developers in version \"%1\"",
report.versionFixedIn());
} else {
customResolutionString = i18nc("@info bug resolution", "Fixed");
m_closedStateString = i18nc("@info bug resolution", "the bug was fixed by KDE developers");
}
} else if (resolution == BugReport::WorksForMe) {
customResolutionString = i18nc("@info bug resolution", "Non-reproducible");
} else if (resolution == BugReport::Duplicate) {
customResolutionString = i18nc("@info bug resolution", "Duplicate report "
"(Already reported before)");
} else if (resolution == BugReport::Invalid) {
customResolutionString = i18nc("@info bug resolution", "Not a valid report/crash");
} else if (resolution == BugReport::Downstream || resolution == BugReport::Upstream) {
customResolutionString = i18nc("@info bug resolution", "Not caused by a problem "
"in the KDE's Applications or libraries");
m_closedStateString = i18nc("@info bug resolution", "the bug is caused by a "
"problem in an external application or library, or "
"by a distribution or packaging issue");
} else {
customResolutionString = report.resolution();
}
customStatusString = i18nc("@info bug status, %1 is the resolution", "Closed (%1)",
customResolutionString);
} else if (status == BugReport::NeedsInfo) {
customStatusString = i18nc("@info bug status", "Temporarily closed, because of a lack "
"of information");
} else { //Fallback to other raw values
customStatusString = QStringLiteral("%1 (%2)").arg(report.bugStatus(), report.resolution());
}
//Generate notes
QString notes = xi18n("<p><note>The bug report's title is often written by its reporter "
"and may not reflect the bug's nature, root cause or other visible "
"symptoms you could use to compare to your crash. Please read the "
"complete report and all the comments below.</note></p>");
if (m_duplicatesCount >= 10) { //Consider a possible mass duplicate crash
notes += xi18np("<p><note>This bug report has %1 duplicate report. That means this "
"is probably a <strong>common crash</strong>. <i>Please consider only "
"adding a comment or a note if you can provide new valuable "
"information which was not already mentioned.</i></note></p>",
"<p><note>This bug report has %1 duplicate reports. That means this "
"is probably a <strong>common crash</strong>. <i>Please consider only "
"adding a comment or a note if you can provide new valuable "
"information which was not already mentioned.</i></note></p>",
m_duplicatesCount);
}
//A manually entered bug ID could represent a normal bug
if (report.bugSeverity() != QLatin1String("crash")
&& report.bugSeverity() != QLatin1String("major")
&& report.bugSeverity() != QLatin1String("grave")
&& report.bugSeverity() != QLatin1String("critical"))
{
notes += xi18n("<p><note>This bug report is not about a crash or about any other "
"critical bug.</note></p>");
}
//Generate HTML text
QString text =
i18nc("@info bug report title (quoted)",
"<h3>\"%1\"</h3>", report.shortDescription()) +
notes +
i18nc("@info bug report status",
"<h4>Bug Report Status: %1</h4>", customStatusString) +
i18nc("@info bug report product and component",
"<h4>Affected Component: %1 (%2)</h4>",
report.product(), report.component()) +
i18nc("@info bug report description",
"<h3>Description of the bug</h3><p>%1</p>",
report.description().replace('\n', QLatin1String("<br />")));
if (!comments.isEmpty()) {
text += i18nc("@label:textbox bug report comments (already formatted)",
"<h2>Additional Comments</h2>%1", comments);
}
ui.m_infoBrowser->setText(text);
ui.m_infoBrowser->setEnabled(true);
m_suggestButton->setEnabled(m_relatedButtonEnabled);
m_suggestButton->setVisible(m_relatedButtonEnabled);
ui.m_statusWidget->setIdle(xi18nc("@info:status", "Showing bug %1",
QString::number(report.bugNumberAsInt())));
} else {
bugFetchError(i18nc("@info", "Invalid report information (malformed data). This could "
"mean that the bug report does not exist, or the bug tracking site "
"is experiencing a problem."), this);
}
}
}
void BugzillaReportInformationDialog::markAsDuplicate()
{
emit possibleDuplicateSelected(m_bugNumber);
hide();
}
void BugzillaReportInformationDialog::attachToBugReport()
{
emit attachToBugReportSelected(m_bugNumber);
hide();
}
void BugzillaReportInformationDialog::cancelAssistant()
{
m_parent->assistant()->close();
hide();
}
void BugzillaReportInformationDialog::relatedReportClicked()
{
BugzillaReportConfirmationDialog * confirmation =
new BugzillaReportConfirmationDialog(m_bugNumber, (m_duplicatesCount >= 10),
m_closedStateString, this);
confirmation->show();
}
void BugzillaReportInformationDialog::bugFetchError(QString err, QObject * jobOwner)
{
if (jobOwner == this && isVisible()) {
KMessageBox::error(this , xi18nc("@info/rich","Error fetching the bug report<nl/>"
"<message>%1.</message><nl/>"
"Please wait some time and try again.", err));
m_suggestButton->setEnabled(false);
ui.m_infoBrowser->setText(i18nc("@info","Error fetching the bug report"));
ui.m_statusWidget->setIdle(i18nc("@info:status","Error fetching the bug report"));
ui.m_retryButton->setVisible(true);
}
}
void BugzillaReportInformationDialog::toggleShowOwnBacktrace(bool show)
{
QList<int> sizes;
if (show) {
int size = (ui.m_reportSplitter->sizeHint().width()-ui.m_reportSplitter->handleWidth())/2;
sizes << size << size;
} else {
sizes << ui.m_reportSplitter->sizeHint().width() << 0; //Hide backtrace
}
ui.m_reportSplitter->setSizes(sizes);
//Save the current show value
KConfigGroup config(KSharedConfig::openConfig(), "BugzillaReportInformationDialog");
config.writeEntry("ShowOwnBacktrace", show);
}
//END BugzillaReportInformationDialog
//BEGIN BugzillaReportConfirmationDialog
BugzillaReportConfirmationDialog::BugzillaReportConfirmationDialog(int bugNumber, bool commonCrash,
QString closedState, BugzillaReportInformationDialog * parent)
: QDialog(parent),
m_parent(parent),
m_showProceedQuestion(false),
m_bugNumber(bugNumber)
{
setAttribute(Qt::WA_DeleteOnClose, true);
setModal(true);
ui.setupUi(this);
//Setup dialog
setWindowTitle(i18nc("@title:window", "Related Bug Report"));
//Setup buttons
ui.buttonBox->button(QDialogButtonBox::Cancel)->setText(i18nc("@action:button", "Cancel (Go back to the report)"));
ui.buttonBox->button(QDialogButtonBox::Ok)->setText(i18nc("@action:button continue with the selected option "
"and close the dialog", "Continue"));
ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
connect(this, SIGNAL(okClicked()) , this, SLOT(proceedClicked()));
connect(this, SIGNAL(cancelClicked()) , this, SLOT(hide()));
//Set introduction text
ui.introLabel->setText(i18n("You are going to mark your crash as related to bug %1",
QString::number(m_bugNumber)));
if (commonCrash) { //Common ("massive") crash
m_showProceedQuestion = true;
ui.commonCrashIcon->setPixmap(QIcon::fromTheme(QStringLiteral("edit-bomb")).pixmap(22,22));
} else {
ui.commonCrashLabel->setVisible(false);
ui.commonCrashIcon->setVisible(false);
}
if (!closedState.isEmpty()) { //Bug report closed
ui.closedReportLabel->setText(
i18nc("@info", "The report is closed because %1. "
"<i>If the crash is the same, adding further information will be useless "
"and will consume developers' time.</i>",
closedState));
ui.closedReportIcon->setPixmap(QIcon::fromTheme(QStringLiteral("document-close")).pixmap(22,22));
m_showProceedQuestion = true;
} else {
ui.closedReportLabel->setVisible(false);
ui.closedReportIcon->setVisible(false);
}
//Disable all the radio buttons
ui.proceedRadioYes->setChecked(false);
ui.proceedRadioNo->setChecked(false);
ui.markAsDuplicateCheck->setChecked(false);
ui.attachToBugReportCheck->setChecked(false);
connect(ui.buttonGroupProceed, SIGNAL(buttonClicked(int)), this, SLOT(checkProceed()));
connect(ui.buttonGroupProceedQuestion, SIGNAL(buttonClicked(int)), this, SLOT(checkProceed()));
if (!m_showProceedQuestion) {
ui.proceedLabel->setEnabled(false);
ui.proceedRadioYes->setEnabled(false);
ui.proceedRadioNo->setEnabled(false);
ui.proceedLabel->setVisible(false);
ui.proceedRadioYes->setVisible(false);
ui.proceedRadioNo->setVisible(false);
ui.proceedRadioYes->setChecked(true);
}
checkProceed();
setMinimumSize(QSize(600, 350));
resize(QSize(600, 350));
}
BugzillaReportConfirmationDialog::~BugzillaReportConfirmationDialog()
{
}
void BugzillaReportConfirmationDialog::checkProceed()
{
bool yes = ui.proceedRadioYes->isChecked();
bool no = ui.proceedRadioNo->isChecked();
//Enable/disable labels and controls
ui.areYouSureLabel->setEnabled(yes);
ui.markAsDuplicateCheck->setEnabled(yes);
ui.attachToBugReportCheck->setEnabled(yes);
//Enable Continue button if valid options are selected
bool possibleDupe = ui.markAsDuplicateCheck->isChecked();
bool attach = ui.attachToBugReportCheck->isChecked();
bool enableContinueButton = yes ? (possibleDupe || attach) : no;
ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enableContinueButton);
}
void BugzillaReportConfirmationDialog::proceedClicked()
{
if (ui.proceedRadioYes->isChecked()) {
if (ui.markAsDuplicateCheck->isChecked()) {
m_parent->markAsDuplicate();
hide();
} else {
m_parent->attachToBugReport();
hide();
}
} else {
hide();
m_parent->cancelAssistant();
}
}
//END BugzillaReportConfirmationDialog