1/*******************************************************************
2* reportinterface.cpp
3* Copyright 2009,2010, 2011 Dario Andres Rodriguez <andresbajotierra@gmail.com>
4* Copyright 2009 George Kiagiadakis <gkiagia@users.sourceforge.net>
5*
6* This program is free software; you can redistribute it and/or
7* modify it under the terms of the GNU General Public License as
8* published by the Free Software Foundation; either version 2 of
9* the License, or (at your option) any later version.
10*
11* This program is distributed in the hope that it will be useful,
12* but WITHOUT ANY WARRANTY; without even the implied warranty of
13* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14* GNU General Public License for more details.
15*
16* You should have received a copy of the GNU General Public License
17* along with this program. If not, see <http://www.gnu.org/licenses/>.
18*
19******************************************************************/
20
21#include "reportinterface.h"
22
23#include <KDebug>
24#include <KLocalizedString>
25
26#include "drkonqi.h"
27#include "bugzillalib.h"
28#include "productmapping.h"
29#include "systeminformation.h"
30#include "crashedapplication.h"
31#include "debuggermanager.h"
32#include "parser/backtraceparser.h"
33#include "backtracegenerator.h"
34#include "applicationdetailsexamples.h"
35
36ReportInterface::ReportInterface(QObject *parent)
37 : QObject(parent),
38 m_duplicate(0)
39{
40 m_bugzillaManager = new BugzillaManager(KDE_BUGZILLA_URL, this);
41
42 m_productMapping = new ProductMapping(DrKonqi::crashedApplication(), m_bugzillaManager, this);
43
44 m_appDetailsExamples = new ApplicationDetailsExamples(this);
45
46 //Information the user can provide about the crash
47 m_userRememberCrashSituation = false;
48 m_reproducible = ReproducibleUnsure;
49 m_provideActionsApplicationDesktop = false;
50 m_provideUnusualBehavior = false;
51 m_provideApplicationConfigurationDetails = false;
52
53 //Do not attach the bug report to any other existent report (create a new one)
54 m_attachToBugNumber = 0;
55}
56
57void ReportInterface::setBugAwarenessPageData(bool rememberSituation,
58 Reproducible reproducible, bool actions,
59 bool unusual, bool configuration)
60{
61 //Save the information the user can provide about the crash from the assistant page
62 m_userRememberCrashSituation = rememberSituation;
63 m_reproducible = reproducible;
64 m_provideActionsApplicationDesktop = actions;
65 m_provideUnusualBehavior = unusual;
66 m_provideApplicationConfigurationDetails = configuration;
67}
68
69bool ReportInterface::isBugAwarenessPageDataUseful() const
70{
71 //Determine if the assistant should proceed, considering the amount of information
72 //the user can provide
73 int rating = selectedOptionsRating();
74
75 //Minimum information required even for a good backtrace.
76 bool useful = m_userRememberCrashSituation &&
77 (rating >= 2 || (m_reproducible==ReproducibleSometimes ||
78 m_reproducible==ReproducibleEverytime));
79 return useful;
80}
81
82int ReportInterface::selectedOptionsRating() const
83{
84 //Check how many information the user can provide and generate a rating
85 int rating = 0;
86 if (m_provideActionsApplicationDesktop) {
87 rating += 3;
88 }
89 if (m_provideApplicationConfigurationDetails) {
90 rating += 2;
91 }
92 if (m_provideUnusualBehavior) {
93 rating += 1;
94 }
95 return rating;
96}
97
98QString ReportInterface::backtrace() const
99{
100 return m_backtrace;
101}
102
103void ReportInterface::setBacktrace(const QString & backtrace)
104{
105 m_backtrace = backtrace;
106}
107
108QStringList ReportInterface::firstBacktraceFunctions() const
109{
110 return m_firstBacktraceFunctions;
111}
112
113void ReportInterface::setFirstBacktraceFunctions(const QStringList & functions)
114{
115 m_firstBacktraceFunctions = functions;
116}
117
118QString ReportInterface::title() const
119{
120 return m_reportTitle;
121}
122
123void ReportInterface::setTitle(const QString & text)
124{
125 m_reportTitle = text;
126}
127
128void ReportInterface::setDetailText(const QString & text)
129{
130 m_reportDetailText = text;
131}
132
133void ReportInterface::setPossibleDuplicates(const QStringList & list)
134{
135 m_possibleDuplicates = list;
136}
137
138QString ReportInterface::generateReportFullText(bool drKonqiStamp) const
139{
140 //Note: no translations should be done in this function's strings
141
142 const CrashedApplication * crashedApp = DrKonqi::crashedApplication();
143 const SystemInformation * sysInfo = DrKonqi::systemInformation();
144
145 QString report;
146
147 //Program name and versions
148 report.append(QString("Application: %1 (%2)\n").arg(crashedApp->fakeExecutableBaseName(),
149 crashedApp->version()));
150 report.append(QString("KDE Platform Version: %1").arg(sysInfo->kdeVersion()));
151 if ( sysInfo->compiledSources() ) {
152 report.append(QString(" (Compiled from sources)\n"));
153 } else {
154 report.append(QString("\n"));
155 }
156 report.append(QString("Qt Version: %1\n").arg(sysInfo->qtVersion()));
157 report.append(QString("Operating System: %1\n").arg(sysInfo->operatingSystem()));
158
159 //LSB output or manually selected distro
160 if ( !sysInfo->lsbRelease().isEmpty() ) {
161 report.append(QString("Distribution: %1\n").arg(sysInfo->lsbRelease()));
162 } else if ( !sysInfo->bugzillaPlatform().isEmpty() &&
163 sysInfo->bugzillaPlatform() != QLatin1String("unspecified")) {
164 report.append(QString("Distribution (Platform): %1\n").arg(
165 sysInfo->bugzillaPlatform()));
166 }
167 report.append(QLatin1String("\n"));
168
169 //Details of the crash situation
170 if (isBugAwarenessPageDataUseful()) {
171 report.append(QString("-- Information about the crash:\n"));
172 if (!m_reportDetailText.isEmpty()) {
173 report.append(m_reportDetailText.trimmed());
174 } else {
175 //If the user manual reports this crash, he/she should know what to put in here.
176 //This message is the only one translated in this function
177 report.append(i18nc("@info/plain","<placeholder>In detail, tell us what you were doing "
178 " when the application crashed.</placeholder>"));
179 }
180 report.append(QLatin1String("\n\n"));
181 }
182
183 //Crash reproducibility (only if useful)
184 if (m_reproducible != ReproducibleUnsure) {
185 if (m_reproducible == ReproducibleEverytime) {
186 report.append(QString("The crash can be reproduced every time.\n\n"));
187 } else if (m_reproducible == ReproducibleSometimes) {
188 report.append(QString("The crash can be reproduced sometimes.\n\n"));
189 } else if (m_reproducible == ReproducibleNever) {
190 report.append(QString("The crash does not seem to be reproducible.\n\n"));
191 }
192 }
193
194 //Backtrace
195 report.append(QString("-- Backtrace:\n"));
196 if (!m_backtrace.isEmpty()) {
197 QString formattedBacktrace = m_backtrace.trimmed();
198 report.append(formattedBacktrace + QLatin1String("\n"));
199 } else {
200 report.append(QString("A useful backtrace could not be generated\n"));
201 }
202
203 //Possible duplicates (selected by the user)
204 if (!m_possibleDuplicates.isEmpty()) {
205 report.append(QLatin1String("\n"));
206 QString duplicatesString;
207 Q_FOREACH(const QString & dupe, m_possibleDuplicates) {
208 duplicatesString += QLatin1String("bug ") + dupe + QLatin1String(", ");
209 }
210 duplicatesString = duplicatesString.left(duplicatesString.length()-2) + '.';
211 report.append(QString("The reporter indicates this bug may be a duplicate of or related to %1\n")
212 .arg(duplicatesString));
213 }
214
215 //Several possible duplicates (by bugzilla query)
216 if (!m_allPossibleDuplicatesByQuery.isEmpty()) {
217 report.append(QLatin1String("\n"));
218 QString duplicatesString;
219 int count = m_allPossibleDuplicatesByQuery.count();
220 for(int i=0; i < count && i < 5; i++) {
221 duplicatesString += QLatin1String("bug ") + m_allPossibleDuplicatesByQuery.at(i) +
222 QLatin1String(", ");
223 }
224 duplicatesString = duplicatesString.left(duplicatesString.length()-2) + '.';
225 report.append(QString("Possible duplicates by query: %1\n").arg(duplicatesString));
226 }
227
228 if (drKonqiStamp) {
229 report.append(QLatin1String("\nReported using DrKonqi"));
230 }
231
232 return report;
233}
234
235QString ReportInterface::generateAttachmentComment() const
236{
237 //Note: no translations should be done in this function's strings
238
239 const CrashedApplication * crashedApp = DrKonqi::crashedApplication();
240 const SystemInformation * sysInfo = DrKonqi::systemInformation();
241
242 QString comment;
243
244 //Program name and versions
245 comment.append(QString("%1 (%2) on KDE Platform %3 using Qt %4\n\n")
246 .arg(crashedApp->fakeExecutableBaseName())
247 .arg(crashedApp->version())
248 .arg(sysInfo->kdeVersion())
249 .arg(sysInfo->qtVersion()));
250
251 //Details of the crash situation
252 if (isBugAwarenessPageDataUseful()) {
253 comment.append(QString("%1\n\n").arg(m_reportDetailText.trimmed()));
254 }
255
256 //Backtrace (only 6 lines)
257 comment.append(QString("-- Backtrace (Reduced):\n"));
258 QString reducedBacktrace =
259 DrKonqi::debuggerManager()->backtraceGenerator()->parser()->simplifiedBacktrace();
260 comment.append(reducedBacktrace.trimmed());
261
262 return comment;
263}
264
265BugReport ReportInterface::newBugReportTemplate() const
266{
267 //Generate a new bug report template with some values on it
268 BugReport report;
269
270 const SystemInformation * sysInfo = DrKonqi::systemInformation();
271
272 report.setProduct(m_productMapping->bugzillaProduct());
273 report.setComponent(m_productMapping->bugzillaComponent());
274 report.setVersion(m_productMapping->bugzillaVersion());
275 report.setOperatingSystem(sysInfo->bugzillaOperatingSystem());
276 if (sysInfo->compiledSources()) {
277 report.setPlatform(QLatin1String("Compiled Sources"));
278 } else {
279 report.setPlatform(sysInfo->bugzillaPlatform());
280 }
281 report.setKeywords(QStringList() << "drkonqi");
282 report.setPriority(QLatin1String("NOR"));
283 report.setBugSeverity(QLatin1String("crash"));
284
285 /*
286 Disable the backtrace functions on title for RELEASE.
287 It also needs a bit of polishment
288
289 QString title = m_reportTitle;
290
291 //If there are not too much possible duplicates by query then there are more possibilities
292 //that this report is unique. Let's add the backtrace functions to the title
293 if (m_allPossibleDuplicatesByQuery.count() <= 2) {
294 if (!m_firstBacktraceFunctions.isEmpty()) {
295 title += (QLatin1String(" [") + m_firstBacktraceFunctions.join(", ").trimmed()
296 + QLatin1String("]"));
297 }
298 }
299 */
300
301 report.setShortDescription(m_reportTitle);
302 return report;
303}
304
305void ReportInterface::sendBugReport() const
306{
307 if (m_attachToBugNumber > 0)
308 {
309 //We are going to attach the report to an existent one
310 connect(m_bugzillaManager, SIGNAL(addMeToCCFinished(int)), this, SLOT(addedToCC()));
311 connect(m_bugzillaManager, SIGNAL(addMeToCCError(QString,QString)),
312 this, SIGNAL(sendReportError(QString,QString)));
313 //First add the user to the CC list, then attach
314 m_bugzillaManager->addMeToCC(m_attachToBugNumber);
315 } else {
316 //Creating a new bug report
317 BugReport report = newBugReportTemplate();
318 report.setDescription(generateReportFullText(true));
319 report.setValid(true);
320
321 connect(m_bugzillaManager, SIGNAL(sendReportErrorInvalidValues()), this, SLOT(sendUsingDefaultProduct()));
322 connect(m_bugzillaManager, SIGNAL(reportSent(int)), this, SIGNAL(reportSent(int)));
323 connect(m_bugzillaManager, SIGNAL(sendReportError(QString,QString)),
324 this, SIGNAL(sendReportError(QString,QString)));
325 m_bugzillaManager->sendReport(report);
326 }
327}
328
329void ReportInterface::sendUsingDefaultProduct() const
330{
331 //Fallback function: if some of the custom values fail, we need to reset all the fields to the default
332 //(and valid) bugzilla values; and try to resend
333 BugReport report = newBugReportTemplate();
334 report.setProduct(QLatin1String("kde"));
335 report.setComponent(QLatin1String("general"));
336 report.setPlatform(QLatin1String("unspecified"));
337 report.setDescription(generateReportFullText(true));
338 report.setValid(true);
339 m_bugzillaManager->sendReport(report);
340}
341
342void ReportInterface::addedToCC()
343{
344 //The user was added to the CC list, proceed with the attachment
345 connect(m_bugzillaManager, SIGNAL(attachToReportSent(int)), this, SLOT(attachSent(int)));
346 connect(m_bugzillaManager, SIGNAL(attachToReportError(QString,QString)),
347 this, SIGNAL(sendReportError(QString,QString)));
348
349 QString reportText = generateReportFullText(true);
350 QString comment = generateAttachmentComment();
351 QString filename = getSuggestedKCrashFilename(DrKonqi::crashedApplication());
352 QLatin1String summary("New crash information added by DrKonqi");
353
354 //Attach the report. The comment of the attachment also includes the bug description
355 m_bugzillaManager->attachTextToReport(reportText, filename, summary,
356 m_attachToBugNumber, comment);
357}
358
359void ReportInterface::attachSent(int attachId)
360{
361 Q_UNUSED(attachId);
362
363 //The bug was attached, consider it "sent"
364 emit reportSent(m_attachToBugNumber);
365}
366
367QStringList ReportInterface::relatedBugzillaProducts() const
368{
369 return m_productMapping->relatedBugzillaProducts();
370}
371
372bool ReportInterface::isWorthReporting() const
373{
374 //Evaluate if the provided information is useful enough to enable the automatic report
375 bool needToReport = false;
376
377 if (!m_userRememberCrashSituation) {
378 //This should never happen... but...
379 return false;
380 }
381
382 int rating = selectedOptionsRating();
383
384 BacktraceParser::Usefulness use =
385 DrKonqi::debuggerManager()->backtraceGenerator()->parser()->backtraceUsefulness();
386
387 switch (use) {
388 case BacktraceParser::ReallyUseful: {
389 //Perfect backtrace: require at least one option or a 100%-50% reproducible crash
390 needToReport = (rating >=2) ||
391 (m_reproducible == ReproducibleEverytime || m_reproducible == ReproducibleSometimes);
392 break;
393 }
394 case BacktraceParser::MayBeUseful: {
395 //Not perfect backtrace: require at least two options or a 100% reproducible crash
396 needToReport = (rating >=3) || (m_reproducible == ReproducibleEverytime);
397 break;
398 }
399 case BacktraceParser::ProbablyUseless:
400 //Bad backtrace: require at least two options and always reproducible (strict)
401 needToReport = (rating >=5) && (m_reproducible == ReproducibleEverytime);
402 break;
403 case BacktraceParser::Useless:
404 case BacktraceParser::InvalidUsefulness: {
405 needToReport = false;
406 }
407 }
408
409 return needToReport;
410}
411
412void ReportInterface::setAttachToBugNumber(uint bugNumber)
413{
414 //If bugNumber>0, the report is going to be attached to bugNumber
415 m_attachToBugNumber = bugNumber;
416}
417
418uint ReportInterface::attachToBugNumber() const
419{
420 return m_attachToBugNumber;
421}
422
423void ReportInterface::setDuplicateId(uint duplicate)
424{
425 m_duplicate = duplicate;
426}
427
428uint ReportInterface::duplicateId() const
429{
430 return m_duplicate;
431}
432
433void ReportInterface::setPossibleDuplicatesByQuery(const QStringList & list)
434{
435 m_allPossibleDuplicatesByQuery = list;
436}
437
438BugzillaManager * ReportInterface::bugzillaManager() const
439{
440 return m_bugzillaManager;
441}
442
443ApplicationDetailsExamples * ReportInterface::appDetailsExamples() const
444{
445 return m_appDetailsExamples;
446}
447
448#include "reportinterface.moc"
449