| | @@ -141,63 +141,10 @@ |
| 141 | 141 | default: |
| 142 | 142 | return "all types"; |
| 143 | 143 | } |
| 144 | 144 | } |
| 145 | 145 | |
| 146 | | -/* |
| 147 | | -** A helper for the /reports family of pages which prints out a menu |
| 148 | | -** of links for the various type=XXX flags. zCurrentViewName must be |
| 149 | | -** the name/value of the 'view' parameter which is in effect at the |
| 150 | | -** time this is called. e.g. if called from the 'byuser' view then |
| 151 | | -** zCurrentViewName must be "byuser". Any URL parameters which need to |
| 152 | | -** be added to the generated URLs should be passed in zParam. The |
| 153 | | -** caller is expected to have already encoded any zParam in the %T or |
| 154 | | -** %t encoding. */ |
| 155 | | -static void stats_report_event_types_menu(const char *zCurrentViewName, |
| 156 | | - const char *zParam){ |
| 157 | | - char *zTop; |
| 158 | | - if(zParam && !*zParam){ |
| 159 | | - zParam = NULL; |
| 160 | | - } |
| 161 | | - zTop = mprintf("%s/reports?view=%s%s%s", g.zTop, zCurrentViewName, |
| 162 | | - zParam ? "&" : "", zParam); |
| 163 | | - cgi_printf("<div>"); |
| 164 | | - cgi_printf("<span>Types:</span> "); |
| 165 | | - if('*' == statsReportType){ |
| 166 | | - cgi_printf(" <strong>all</strong>", zTop); |
| 167 | | - }else{ |
| 168 | | - cgi_printf(" <a href='%s'>all</a>", zTop); |
| 169 | | - } |
| 170 | | - if('c' == statsReportType){ |
| 171 | | - cgi_printf(" <strong>check-ins</strong>", zTop); |
| 172 | | - }else{ |
| 173 | | - cgi_printf(" <a href='%s&type=ci'>check-ins</a>", zTop); |
| 174 | | - } |
| 175 | | - if('e' == statsReportType){ |
| 176 | | - cgi_printf(" <strong>technotes</strong>", zTop); |
| 177 | | - }else{ |
| 178 | | - cgi_printf(" <a href='%s&type=e'>technotes</a>", zTop); |
| 179 | | - } |
| 180 | | - if( 't' == statsReportType ){ |
| 181 | | - cgi_printf(" <strong>tickets</strong>", zTop); |
| 182 | | - }else{ |
| 183 | | - cgi_printf(" <a href='%s&type=t'>tickets</a>", zTop); |
| 184 | | - } |
| 185 | | - if( 'g' == statsReportType ){ |
| 186 | | - cgi_printf(" <strong>tags</strong>", zTop); |
| 187 | | - }else{ |
| 188 | | - cgi_printf(" <a href='%s&type=g'>tags</a>", zTop); |
| 189 | | - } |
| 190 | | - if( 'w' == statsReportType ){ |
| 191 | | - cgi_printf(" <strong>wiki</strong>", zTop); |
| 192 | | - }else{ |
| 193 | | - cgi_printf(" <a href='%s&type=w'>wiki</a>", zTop); |
| 194 | | - } |
| 195 | | - fossil_free(zTop); |
| 196 | | - cgi_printf("</div>"); |
| 197 | | -} |
| 198 | | - |
| 199 | 146 | |
| 200 | 147 | /* |
| 201 | 148 | ** Helper for stats_report_by_month_year(), which generates a list of |
| 202 | 149 | ** week numbers. zTimeframe should be either a timeframe in the form YYYY |
| 203 | 150 | ** or YYYY-MM. |
| | @@ -227,23 +174,21 @@ |
| 227 | 174 | } |
| 228 | 175 | |
| 229 | 176 | /* |
| 230 | 177 | ** Implements the "byyear" and "bymonth" reports for /reports. |
| 231 | 178 | ** If includeMonth is true then it generates the "bymonth" report, |
| 232 | | -** else the "byyear" report. If zUserName is not NULL and not empty |
| 233 | | -** then the report is restricted to events created by the named user |
| 234 | | -** account. |
| 179 | +** else the "byyear" report. If zUserName is not NULL then the report is |
| 180 | +** restricted to events created by the named user account. |
| 235 | 181 | */ |
| 236 | 182 | static void stats_report_by_month_year(char includeMonth, |
| 237 | 183 | char includeWeeks, |
| 238 | 184 | const char *zUserName){ |
| 239 | 185 | Stmt query = empty_Stmt; |
| 240 | 186 | int nRowNumber = 0; /* current TR number */ |
| 241 | 187 | int nEventTotal = 0; /* Total event count */ |
| 242 | 188 | int rowClass = 0; /* counter for alternating |
| 243 | 189 | row colors */ |
| 244 | | - Blob sql = empty_blob; /* SQL */ |
| 245 | 190 | const char *zTimeLabel = includeMonth ? "Year/Month" : "Year"; |
| 246 | 191 | char zPrevYear[5] = {0}; /* For keeping track of when |
| 247 | 192 | we change years while looping */ |
| 248 | 193 | int nEventsPerYear = 0; /* Total event count for the |
| 249 | 194 | current year */ |
| | @@ -252,39 +197,37 @@ |
| 252 | 197 | Blob header = empty_blob; /* Page header text */ |
| 253 | 198 | int nMaxEvents = 1; /* for calculating length of graph |
| 254 | 199 | bars. */ |
| 255 | 200 | int iterations = 0; /* number of weeks/months we iterate |
| 256 | 201 | over */ |
| 202 | + Blob userFilter = empty_blob; /* Optional user=johndoe query string */ |
| 257 | 203 | stats_report_init_view(); |
| 258 | | - stats_report_event_types_menu( includeMonth ? "bymonth" : "byyear", NULL ); |
| 259 | | - blob_appendf(&header, "Timeline Events (%s) by year%s", |
| 260 | | - stats_report_label_for_type(), |
| 261 | | - (includeMonth ? "/month" : "")); |
| 262 | | - blob_append_sql(&sql, |
| 263 | | - "SELECT substr(date(mtime),1,%d) AS timeframe, " |
| 264 | | - "count(*) AS eventCount " |
| 265 | | - "FROM v_reports ", |
| 266 | | - includeMonth ? 7 : 4); |
| 267 | | - if(zUserName&&*zUserName){ |
| 268 | | - blob_append_sql(&sql, " WHERE user=%Q ", zUserName); |
| 269 | | - blob_appendf(&header," for user %q", zUserName); |
| 270 | | - } |
| 271 | | - blob_append(&sql, |
| 272 | | - " GROUP BY timeframe" |
| 273 | | - " ORDER BY timeframe DESC", |
| 274 | | - -1); |
| 275 | | - db_prepare(&query, "%s", blob_sql_text(&sql)); |
| 276 | | - blob_reset(&sql); |
| 277 | | - @ <h1>%b(&header)</h1> |
| 204 | + if( zUserName ){ |
| 205 | + blob_appendf(&userFilter, "user=%s", zUserName); |
| 206 | + } |
| 207 | + blob_reset(&userFilter); |
| 208 | + db_prepare(&query, |
| 209 | + "SELECT substr(date(mtime),1,%d) AS timeframe," |
| 210 | + " count(*) AS eventCount" |
| 211 | + " FROM v_reports" |
| 212 | + " WHERE ifnull(coalesce(euser,user,'')=%Q,1)" |
| 213 | + " GROUP BY timeframe" |
| 214 | + " ORDER BY timeframe DESC", |
| 215 | + includeMonth ? 7 : 4, zUserName); |
| 216 | + @ <h1>Timeline Events (%s(stats_report_label_for_type())) |
| 217 | + @ by year%s(includeMonth ? "/month" : "") |
| 218 | + if( zUserName ){ |
| 219 | + @ for user %h(zUserName) |
| 220 | + } |
| 221 | + @ </h1> |
| 278 | 222 | @ <table class='statistics-report-table-events' border='0' cellpadding='2' |
| 279 | 223 | @ cellspacing='0' id='statsTable'> |
| 280 | 224 | @ <thead> |
| 281 | 225 | @ <th>%s(zTimeLabel)</th> |
| 282 | 226 | @ <th>Events</th> |
| 283 | 227 | @ <th width='90%%'><!-- relative commits graph --></th> |
| 284 | 228 | @ </thead><tbody> |
| 285 | | - blob_reset(&header); |
| 286 | 229 | /* |
| 287 | 230 | Run the query twice. The first time we calculate the maximum |
| 288 | 231 | number of events for a given row. Maybe someone with better SQL |
| 289 | 232 | Fu can re-implement this with a single query. |
| 290 | 233 | */ |
| | @@ -335,18 +278,18 @@ |
| 335 | 278 | zTimeframe, nCount, |
| 336 | 279 | statsReportTimelineYFlag ); |
| 337 | 280 | /* Reminder: n=nCount is not actually correct for bymonth unless |
| 338 | 281 | that was the only user who caused events. |
| 339 | 282 | */ |
| 340 | | - if( zUserName && *zUserName ){ |
| 283 | + if( zUserName ){ |
| 341 | 284 | cgi_printf("&u=%t", zUserName); |
| 342 | 285 | } |
| 343 | 286 | cgi_printf("' target='_new'>%s</a>",zTimeframe); |
| 344 | 287 | }else { |
| 345 | 288 | cgi_printf("<a href='?view=byweek&y=%s&type=%c", |
| 346 | 289 | zTimeframe, (char)statsReportType); |
| 347 | | - if(zUserName && *zUserName){ |
| 290 | + if( zUserName ){ |
| 348 | 291 | cgi_printf("&u=%t", zUserName); |
| 349 | 292 | } |
| 350 | 293 | cgi_printf("'>%s</a>", zTimeframe); |
| 351 | 294 | } |
| 352 | 295 | @ </td><td>%d(nCount)</td> |
| | @@ -403,17 +346,16 @@ |
| 403 | 346 | int rowClass = 0; /* counter for alternating |
| 404 | 347 | row colors */ |
| 405 | 348 | int nMaxEvents = 1; /* max number of events for |
| 406 | 349 | all rows. */ |
| 407 | 350 | stats_report_init_view(); |
| 408 | | - stats_report_event_types_menu("byuser", NULL); |
| 409 | 351 | @ <h1>Timeline Events |
| 410 | 352 | @ (%s(stats_report_label_for_type())) by User</h1> |
| 411 | 353 | db_multi_exec( |
| 412 | 354 | "CREATE TEMP TABLE piechart(amt,label);" |
| 413 | | - "INSERT INTO piechart SELECT count(*), user FROM v_reports" |
| 414 | | - " GROUP BY user ORDER BY count(*) DESC;" |
| 355 | + "INSERT INTO piechart SELECT count(*), ifnull(euser,user) FROM v_reports" |
| 356 | + " GROUP BY ifnull(euser,user) ORDER BY count(*) DESC;" |
| 415 | 357 | ); |
| 416 | 358 | if( db_int(0, "SELECT count(*) FROM piechart")>=2 ){ |
| 417 | 359 | @ <center><svg width=700 height=400> |
| 418 | 360 | piechart_render(700, 400, PIE_OTHER|PIE_PERCENT); |
| 419 | 361 | @ </svg></centre><hr/> |
| | @@ -424,14 +366,14 @@ |
| 424 | 366 | @ <th>User</th> |
| 425 | 367 | @ <th>Events</th> |
| 426 | 368 | @ <th width='90%%'><!-- relative commits graph --></th> |
| 427 | 369 | @ </tr></thead><tbody> |
| 428 | 370 | db_prepare(&query, |
| 429 | | - "SELECT user, " |
| 371 | + "SELECT ifnull(euser,user), " |
| 430 | 372 | "COUNT(*) AS eventCount " |
| 431 | 373 | "FROM v_reports " |
| 432 | | - "GROUP BY user ORDER BY eventCount DESC"); |
| 374 | + "GROUP BY ifnull(euser,user) ORDER BY eventCount DESC"); |
| 433 | 375 | while( SQLITE_ROW == db_step(&query) ){ |
| 434 | 376 | const int nCount = db_column_int(&query, 1); |
| 435 | 377 | if(nCount>nMaxEvents){ |
| 436 | 378 | nMaxEvents = nCount; |
| 437 | 379 | } |
| | @@ -438,20 +380,21 @@ |
| 438 | 380 | } |
| 439 | 381 | db_reset(&query); |
| 440 | 382 | while( SQLITE_ROW == db_step(&query) ){ |
| 441 | 383 | const char *zUser = db_column_text(&query, 0); |
| 442 | 384 | const int nCount = db_column_int(&query, 1); |
| 385 | + char y = (char)statsReportType; |
| 443 | 386 | int nSize = nCount |
| 444 | 387 | ? (int)(100 * nCount / nMaxEvents) |
| 445 | 388 | : 0; |
| 446 | 389 | if(!nCount) continue /* arguable! Possible? */; |
| 447 | 390 | else if(!nSize) nSize = 1; |
| 448 | 391 | rowClass = ++nRowNumber % 2; |
| 449 | 392 | nEventTotal += nCount; |
| 450 | | - @<tr class='row%d(rowClass)'> |
| 393 | + @ <tr class='row%d(rowClass)'> |
| 451 | 394 | @ <td> |
| 452 | | - @ <a href="?view=bymonth&user=%h(zUser)&type=%c((char)statsReportType)">%h(zUser)</a> |
| 395 | + @ <a href="?view=bymonth&user=%h(zUser)&type=%c(y)">%h(zUser)</a> |
| 453 | 396 | @ </td><td data-sortkey='%08x(-nCount)'>%d(nCount)</td> |
| 454 | 397 | @ <td> |
| 455 | 398 | @ <div class='statistics-report-graph-line' |
| 456 | 399 | @ style='width:%d(nSize)%%;'> </div> |
| 457 | 400 | @ </td> |
| | @@ -465,30 +408,37 @@ |
| 465 | 408 | db_finalize(&query); |
| 466 | 409 | output_table_sorting_javascript("statsTable","tkx",2); |
| 467 | 410 | } |
| 468 | 411 | |
| 469 | 412 | /* |
| 470 | | -** Implements the "byfile" view for /reports. |
| 413 | +** Implements the "byfile" view for /reports. If zUserName is not NULL then the |
| 414 | +** report is restricted to events created by the named user account. |
| 471 | 415 | */ |
| 472 | | -static void stats_report_by_file(){ |
| 416 | +static void stats_report_by_file(const char *zUserName){ |
| 473 | 417 | Stmt query; |
| 474 | 418 | int mxEvent = 1; /* max number of events across all rows */ |
| 475 | 419 | int nRowNumber = 0; |
| 476 | 420 | |
| 477 | 421 | db_multi_exec( |
| 478 | 422 | "CREATE TEMP TABLE statrep(filename, cnt);" |
| 479 | 423 | "INSERT INTO statrep(filename, cnt)" |
| 480 | 424 | " SELECT filename.name, count(distinct mlink.mid)" |
| 481 | | - " FROM filename, mlink" |
| 425 | + " FROM filename, mlink, event" |
| 482 | 426 | " WHERE filename.fnid=mlink.fnid" |
| 483 | | - " GROUP BY 1;" |
| 427 | + " AND mlink.mid=event.objid" |
| 428 | + " AND ifnull(coalesce(euser,user,'')=%Q,1)" |
| 429 | + " GROUP BY 1", zUserName |
| 484 | 430 | ); |
| 485 | 431 | db_prepare(&query, |
| 486 | 432 | "SELECT filename, cnt FROM statrep ORDER BY cnt DESC, filename /*sort*/" |
| 487 | 433 | ); |
| 488 | 434 | mxEvent = db_int(1, "SELECT max(cnt) FROM statrep"); |
| 489 | | - @ <h1>Check-ins Per File</h1> |
| 435 | + @ <h1>Check-ins Per File |
| 436 | + if( zUserName ){ |
| 437 | + @ for user %h(zUserName) |
| 438 | + } |
| 439 | + @ </h1> |
| 490 | 440 | @ <table class='statistics-report-table-events' border='0' |
| 491 | 441 | @ cellpadding='2' cellspacing='0' id='statsTable'> |
| 492 | 442 | @ <thead><tr> |
| 493 | 443 | @ <th>File</th> |
| 494 | 444 | @ <th>Check-ins</th> |
| | @@ -514,41 +464,56 @@ |
| 514 | 464 | db_finalize(&query); |
| 515 | 465 | output_table_sorting_javascript("statsTable","tNx",2); |
| 516 | 466 | } |
| 517 | 467 | |
| 518 | 468 | /* |
| 519 | | -** Implements the "byweekday" view for /reports. |
| 469 | +** Implements the "byweekday" view for /reports. If zUserName is not NULL then |
| 470 | +** the report is restricted to events created by the named user account. |
| 520 | 471 | */ |
| 521 | | -static void stats_report_day_of_week(){ |
| 472 | +static void stats_report_day_of_week(const char *zUserName){ |
| 522 | 473 | Stmt query = empty_Stmt; |
| 523 | 474 | int nRowNumber = 0; /* current TR number */ |
| 524 | 475 | int nEventTotal = 0; /* Total event count */ |
| 525 | 476 | int rowClass = 0; /* counter for alternating |
| 526 | 477 | row colors */ |
| 527 | 478 | int nMaxEvents = 1; /* max number of events for |
| 528 | 479 | all rows. */ |
| 480 | + Blob userFilter = empty_blob; /* Optional user=johndoe query string */ |
| 529 | 481 | static const char *const daysOfWeek[] = { |
| 530 | 482 | "Monday", "Tuesday", "Wednesday", "Thursday", |
| 531 | 483 | "Friday", "Saturday", "Sunday" |
| 532 | 484 | }; |
| 533 | 485 | |
| 534 | 486 | stats_report_init_view(); |
| 535 | | - stats_report_event_types_menu("byweekday", NULL); |
| 487 | + if( zUserName ){ |
| 488 | + blob_appendf(&userFilter, "user=%s", zUserName); |
| 489 | + } |
| 536 | 490 | db_prepare(&query, |
| 537 | | - "SELECT cast(mtime %% 7 AS INTEGER) dow, " |
| 538 | | - "COUNT(*) AS eventCount " |
| 539 | | - "FROM v_reports " |
| 540 | | - "GROUP BY dow ORDER BY dow"); |
| 541 | | - @ <h1>Timeline Events |
| 542 | | - @ (%s(stats_report_label_for_type())) by Day of the Week</h1> |
| 491 | + "SELECT cast(mtime %% 7 AS INTEGER) dow," |
| 492 | + " COUNT(*) AS eventCount" |
| 493 | + " FROM v_reports" |
| 494 | + " WHERE ifnull(coalesce(euser,user,'')=%Q,1)" |
| 495 | + " GROUP BY dow ORDER BY dow", zUserName); |
| 496 | + @ <h1>Timeline Events (%h(stats_report_label_for_type())) by Day of the Week |
| 497 | + if( zUserName ){ |
| 498 | + @ for user %h(zUserName) |
| 499 | + } |
| 500 | + @ </h1> |
| 543 | 501 | db_multi_exec( |
| 544 | 502 | "CREATE TEMP TABLE piechart(amt,label);" |
| 545 | | - "INSERT INTO piechart SELECT count(*), cast(mtime %% 7 AS INT) FROM v_reports" |
| 546 | | - " GROUP BY 2 ORDER BY 2;" |
| 547 | | - "UPDATE piechart SET label = CASE label WHEN 0 THEN 'Monday' WHEN 1 THEN 'Tuesday'" |
| 548 | | - " WHEN 2 THEN 'Wednesday' WHEN 3 THEN 'Thursday' WHEN 4 THEN 'Friday'" |
| 549 | | - " WHEN 5 THEN 'Saturday' ELSE 'Sunday' END;" |
| 503 | + "INSERT INTO piechart" |
| 504 | + " SELECT count(*), cast(mtime %% 7 AS INT) FROM v_reports" |
| 505 | + " WHERE ifnull(coalesce(euser,user,'')=%Q,1)" |
| 506 | + " GROUP BY 2 ORDER BY 2;" |
| 507 | + "UPDATE piechart SET label = CASE label" |
| 508 | + " WHEN 0 THEN 'Monday'" |
| 509 | + " WHEN 1 THEN 'Tuesday'" |
| 510 | + " WHEN 2 THEN 'Wednesday'" |
| 511 | + " WHEN 3 THEN 'Thursday'" |
| 512 | + " WHEN 4 THEN 'Friday'" |
| 513 | + " WHEN 5 THEN 'Saturday'" |
| 514 | + " ELSE 'Sunday' END;", zUserName |
| 550 | 515 | ); |
| 551 | 516 | if( db_int(0, "SELECT count(*) FROM piechart")>=2 ){ |
| 552 | 517 | @ <center><svg width=700 height=400> |
| 553 | 518 | piechart_render(700, 400, PIE_OTHER|PIE_PERCENT); |
| 554 | 519 | @ </svg></centre><hr/> |
| | @@ -595,139 +560,114 @@ |
| 595 | 560 | |
| 596 | 561 | |
| 597 | 562 | /* |
| 598 | 563 | ** Helper for stats_report_by_month_year(), which generates a list of |
| 599 | 564 | ** week numbers. zTimeframe should be either a timeframe in the form YYYY |
| 600 | | -** or YYYY-MM. |
| 565 | +** or YYYY-MM. If zUserName is not NULL then the report is restricted to events |
| 566 | +** created by the named user account. |
| 601 | 567 | */ |
| 602 | 568 | static void stats_report_year_weeks(const char *zUserName){ |
| 603 | | - const char *zYear = P("y"); |
| 604 | | - int nYear = zYear ? strlen(zYear) : 0; |
| 569 | + const char *zYear = P("y"); /* Year for which report shown */ |
| 570 | + int isValidYear = 0; /* True if a valid year */ |
| 605 | 571 | int i = 0; |
| 606 | | - Stmt qYears = empty_Stmt; |
| 607 | | - char *zDefaultYear = NULL; |
| 608 | | - Blob sql = empty_blob; |
| 572 | + Stmt q; |
| 609 | 573 | int nMaxEvents = 1; /* max number of events for |
| 610 | 574 | all rows. */ |
| 611 | 575 | int iterations = 0; /* # of active time periods. */ |
| 612 | | - stats_report_init_view(); |
| 613 | | - if(4==nYear){ |
| 614 | | - Blob urlParams = empty_blob; |
| 615 | | - blob_appendf(&urlParams, "y=%T", zYear); |
| 616 | | - stats_report_event_types_menu("byweek", blob_str(&urlParams)); |
| 617 | | - blob_reset(&urlParams); |
| 618 | | - }else{ |
| 619 | | - stats_report_event_types_menu("byweek", NULL); |
| 620 | | - } |
| 621 | | - blob_append(&sql, |
| 622 | | - "SELECT DISTINCT substr(date(mtime),1,4) AS y " |
| 623 | | - "FROM v_reports WHERE 1 ", -1); |
| 624 | | - if(zUserName&&*zUserName){ |
| 625 | | - blob_append_sql(&sql,"AND user=%Q ", zUserName); |
| 626 | | - } |
| 627 | | - blob_append(&sql,"GROUP BY y ORDER BY y", -1); |
| 628 | | - db_prepare(&qYears, "%s", blob_sql_text(&sql)); |
| 629 | | - blob_reset(&sql); |
| 630 | | - cgi_printf("Select year: "); |
| 631 | | - while( SQLITE_ROW == db_step(&qYears) ){ |
| 632 | | - const char *zT = db_column_text(&qYears, 0); |
| 633 | | - if( i++ ){ |
| 634 | | - cgi_printf(" "); |
| 635 | | - } |
| 636 | | - cgi_printf("<a href='?view=byweek&y=%s&type=%c", zT, |
| 637 | | - (char)statsReportType); |
| 638 | | - if(zUserName && *zUserName){ |
| 639 | | - cgi_printf("&user=%t",zUserName); |
| 640 | | - } |
| 641 | | - cgi_printf("'>%s</a>",zT); |
| 642 | | - } |
| 643 | | - db_finalize(&qYears); |
| 644 | | - cgi_printf("<br/>"); |
| 645 | | - if(!zYear || !*zYear){ |
| 646 | | - zDefaultYear = db_text("????", "SELECT strftime('%%Y')"); |
| 647 | | - zYear = zDefaultYear; |
| 648 | | - nYear = 4; |
| 649 | | - } |
| 650 | | - if(4 == nYear){ |
| 651 | | - Stmt stWeek = empty_Stmt; |
| 652 | | - int rowCount = 0; |
| 653 | | - int total = 0; |
| 654 | | - Blob header = empty_blob; |
| 655 | | - blob_appendf(&header, "Timeline events (%s) for the calendar weeks " |
| 656 | | - "of %h", stats_report_label_for_type(), |
| 657 | | - zYear); |
| 658 | | - blob_append_sql(&sql, |
| 659 | | - "SELECT DISTINCT strftime('%%W',mtime) AS wk, " |
| 660 | | - "count(*) AS n " |
| 661 | | - "FROM v_reports " |
| 662 | | - "WHERE %Q=substr(date(mtime),1,4) " |
| 663 | | - "AND mtime < current_timestamp ", |
| 664 | | - zYear); |
| 665 | | - if(zUserName&&*zUserName){ |
| 666 | | - blob_append_sql(&sql, " AND user=%Q ", zUserName); |
| 667 | | - blob_appendf(&header," for user %h", zUserName); |
| 668 | | - } |
| 669 | | - blob_append_sql(&sql, "GROUP BY wk ORDER BY wk DESC"); |
| 670 | | - cgi_printf("<h1>%h</h1>", blob_str(&header)); |
| 671 | | - blob_reset(&header); |
| 672 | | - cgi_printf("<table class='statistics-report-table-events' " |
| 673 | | - "border='0' cellpadding='2' width='100%%' " |
| 674 | | - "cellspacing='0' id='statsTable'>"); |
| 675 | | - cgi_printf("<thead><tr>" |
| 676 | | - "<th>Week</th>" |
| 677 | | - "<th>Events</th>" |
| 678 | | - "<th width='90%%'><!-- relative commits graph --></th>" |
| 679 | | - "</tr></thead>" |
| 680 | | - "<tbody>"); |
| 681 | | - db_prepare(&stWeek, "%s", blob_sql_text(&sql)); |
| 682 | | - blob_reset(&sql); |
| 683 | | - while( SQLITE_ROW == db_step(&stWeek) ){ |
| 684 | | - const int nCount = db_column_int(&stWeek, 1); |
| 685 | | - if(nCount>nMaxEvents){ |
| 686 | | - nMaxEvents = nCount; |
| 687 | | - } |
| 688 | | - ++iterations; |
| 689 | | - } |
| 690 | | - db_reset(&stWeek); |
| 691 | | - while( SQLITE_ROW == db_step(&stWeek) ){ |
| 692 | | - const char *zWeek = db_column_text(&stWeek,0); |
| 693 | | - const int nCount = db_column_int(&stWeek,1); |
| 694 | | - int nSize = nCount |
| 695 | | - ? (int)(100 * nCount / nMaxEvents) |
| 696 | | - : 0; |
| 697 | | - if(!nSize) nSize = 1; |
| 698 | | - total += nCount; |
| 699 | | - cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); |
| 700 | | - cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s", |
| 701 | | - zYear, zWeek, nCount, |
| 702 | | - statsReportTimelineYFlag); |
| 703 | | - if(zUserName && *zUserName){ |
| 704 | | - cgi_printf("&u=%t",zUserName); |
| 705 | | - } |
| 706 | | - cgi_printf("'>%s</a></td>",zWeek); |
| 707 | | - |
| 708 | | - cgi_printf("<td>%d</td>",nCount); |
| 709 | | - cgi_printf("<td>"); |
| 710 | | - if(nCount){ |
| 711 | | - cgi_printf("<div class='statistics-report-graph-line'" |
| 712 | | - "style='width:%d%%;'> </div>", |
| 713 | | - nSize); |
| 714 | | - } |
| 715 | | - cgi_printf("</td></tr>"); |
| 716 | | - } |
| 717 | | - db_finalize(&stWeek); |
| 718 | | - free(zDefaultYear); |
| 719 | | - cgi_printf("</tbody></table>"); |
| 720 | | - if(total){ |
| 721 | | - int nAvg = iterations ? (total/iterations) : 0; |
| 722 | | - cgi_printf("<br><div>Total events: %d<br>" |
| 723 | | - "Average per active week: %d</div>", |
| 724 | | - total, nAvg); |
| 725 | | - } |
| 726 | | - output_table_sorting_javascript("statsTable","tnx",-1); |
| 727 | | - } |
| 728 | | -} |
| 576 | + int n = 0; /* Number of entries in azYear */ |
| 577 | + char **azYear = 0; /* Year dropdown menu */ |
| 578 | + int rowCount = 0; |
| 579 | + int total = 0; |
| 580 | + |
| 581 | + stats_report_init_view(); |
| 582 | + style_submenu_sql("y", "Year:", |
| 583 | + "WITH RECURSIVE a(b) AS (" |
| 584 | + " SELECT substr(date('now'),1,4) UNION ALL" |
| 585 | + " SELECT b-1 FROM a" |
| 586 | + " WHERE b>0+(SELECT substr(date(min(mtime)),1,4) FROM event)" |
| 587 | + ") SELECT b, b FROM a ORDER BY b DESC"); |
| 588 | + if( zYear==0 || strlen(zYear)!=4 ){ |
| 589 | + zYear = db_text("1970","SELECT substr(date('now'),1,4);"); |
| 590 | + } |
| 591 | + cgi_printf("<br/>"); |
| 592 | + db_prepare(&q, |
| 593 | + "SELECT DISTINCT strftime('%%W',mtime) AS wk, " |
| 594 | + " count(*) AS n " |
| 595 | + " FROM v_reports " |
| 596 | + " WHERE %Q=substr(date(mtime),1,4) " |
| 597 | + " AND mtime < current_timestamp " |
| 598 | + " AND ifnull(coalesce(euser,user,'')=%Q,1)" |
| 599 | + " GROUP BY wk ORDER BY wk DESC", zYear, zUserName); |
| 600 | + @ <h1>Timeline events (%h(stats_report_label_for_type())) |
| 601 | + @ for the calendar weeks of %h(zYear) |
| 602 | + if( zUserName ){ |
| 603 | + @ for user %h(zUserName) |
| 604 | + } |
| 605 | + @ </h1> |
| 606 | + cgi_printf("<table class='statistics-report-table-events' " |
| 607 | + "border='0' cellpadding='2' width='100%%' " |
| 608 | + "cellspacing='0' id='statsTable'>"); |
| 609 | + cgi_printf("<thead><tr>" |
| 610 | + "<th>Week</th>" |
| 611 | + "<th>Events</th>" |
| 612 | + "<th width='90%%'><!-- relative commits graph --></th>" |
| 613 | + "</tr></thead>" |
| 614 | + "<tbody>"); |
| 615 | + while( SQLITE_ROW == db_step(&q) ){ |
| 616 | + const int nCount = db_column_int(&q, 1); |
| 617 | + if(nCount>nMaxEvents){ |
| 618 | + nMaxEvents = nCount; |
| 619 | + } |
| 620 | + ++iterations; |
| 621 | + } |
| 622 | + db_reset(&q); |
| 623 | + while( SQLITE_ROW == db_step(&q) ){ |
| 624 | + const char *zWeek = db_column_text(&q,0); |
| 625 | + const int nCount = db_column_int(&q,1); |
| 626 | + int nSize = nCount |
| 627 | + ? (int)(100 * nCount / nMaxEvents) |
| 628 | + : 0; |
| 629 | + if(!nSize) nSize = 1; |
| 630 | + total += nCount; |
| 631 | + cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); |
| 632 | + cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s", |
| 633 | + zYear, zWeek, nCount, |
| 634 | + statsReportTimelineYFlag); |
| 635 | + if( zUserName ){ |
| 636 | + cgi_printf("&u=%t",zUserName); |
| 637 | + } |
| 638 | + cgi_printf("'>%s</a></td>",zWeek); |
| 639 | + |
| 640 | + cgi_printf("<td>%d</td>",nCount); |
| 641 | + cgi_printf("<td>"); |
| 642 | + if(nCount){ |
| 643 | + cgi_printf("<div class='statistics-report-graph-line'" |
| 644 | + "style='width:%d%%;'> </div>", |
| 645 | + nSize); |
| 646 | + } |
| 647 | + cgi_printf("</td></tr>"); |
| 648 | + } |
| 649 | + db_finalize(&q); |
| 650 | + cgi_printf("</tbody></table>"); |
| 651 | + if(total){ |
| 652 | + int nAvg = iterations ? (total/iterations) : 0; |
| 653 | + cgi_printf("<br><div>Total events: %d<br>" |
| 654 | + "Average per active week: %d</div>", |
| 655 | + total, nAvg); |
| 656 | + } |
| 657 | + output_table_sorting_javascript("statsTable","tnx",-1); |
| 658 | +} |
| 659 | + |
| 660 | +/* Report types |
| 661 | +*/ |
| 662 | +#define RPT_BYFILE 1 |
| 663 | +#define RPT_BYMONTH 2 |
| 664 | +#define RPT_BYUSER 3 |
| 665 | +#define RPT_BYWEEK 4 |
| 666 | +#define RPT_BYWEEKDAY 5 |
| 667 | +#define RPT_BYYEAR 6 |
| 668 | +#define RPT_NONE 0 /* None of the above */ |
| 729 | 669 | |
| 730 | 670 | /* |
| 731 | 671 | ** WEBPAGE: reports |
| 732 | 672 | ** |
| 733 | 673 | ** Shows activity reports for the repository. |
| | @@ -747,51 +687,94 @@ |
| 747 | 687 | ** y=YYYY The year to report (default is the server's |
| 748 | 688 | ** current year). |
| 749 | 689 | */ |
| 750 | 690 | void stats_report_page(){ |
| 751 | 691 | HQuery url; /* URL for various branch links */ |
| 752 | | - const char *zView = P("view"); /* Which view/report to show. */ |
| 753 | | - const char *zUserName = P("user"); |
| 754 | | - |
| 692 | + const char *zView = P("view"); /* Which view/report to show. */ |
| 693 | + int eType = RPT_NONE; /* Numeric code for view/report to show */ |
| 694 | + int i; /* Loop counter */ |
| 695 | + const char *zUserName; /* Name of user */ |
| 696 | + const struct { |
| 697 | + const char *zName; /* Name of view= screen type */ |
| 698 | + const char *zVal; /* Value of view= query parameter */ |
| 699 | + int eType; /* Corresponding RPT_* define */ |
| 700 | + } aViewType[] = { |
| 701 | + { "File Changes","byfile", RPT_BYFILE }, |
| 702 | + { "By Month", "bymonth", RPT_BYMONTH }, |
| 703 | + { "By User", "byuser", RPT_BYUSER }, |
| 704 | + { "By Week", "byweek", RPT_BYWEEK }, |
| 705 | + { "By Weekday", "byweekday", RPT_BYWEEKDAY }, |
| 706 | + { "By Year", "byyear", RPT_BYYEAR }, |
| 707 | + }; |
| 708 | + const char *azType[] = { |
| 709 | + "a", "All Changes", |
| 710 | + "ci", "Check-ins", |
| 711 | + "g", "Tags", |
| 712 | + "e", "Tech Notes", |
| 713 | + "t", "Tickets", |
| 714 | + "w", "Wiki" |
| 715 | + }; |
| 716 | + |
| 755 | 717 | login_check_credentials(); |
| 756 | 718 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 757 | | - if(!zUserName) zUserName = P("u"); |
| 758 | | - url_initialize(&url, "reports"); |
| 759 | | - if(zUserName && *zUserName){ |
| 760 | | - url_add_parameter(&url,"user", zUserName); |
| 761 | | - statrep_submenu(&url, "(Remove User Flag)", "view", zView, "user"); |
| 762 | | - } |
| 763 | | - statrep_submenu(&url, "By Year", "view", "byyear", 0); |
| 764 | | - statrep_submenu(&url, "By Month", "view", "bymonth", 0); |
| 765 | | - statrep_submenu(&url, "By Week", "view", "byweek", 0); |
| 766 | | - statrep_submenu(&url, "By Weekday", "view", "byweekday", 0); |
| 767 | | - statrep_submenu(&url, "By User", "view", "byuser", "user"); |
| 768 | | - statrep_submenu(&url, "By File", "view", "byfile", "file"); |
| 719 | + zUserName = P("user"); |
| 720 | + if( zUserName==0 ) zUserName = P("u"); |
| 721 | + if( zUserName && zUserName[0]==0 ) zUserName = 0; |
| 722 | + if( zView==0 ){ |
| 723 | + zView = "byuser"; |
| 724 | + cgi_replace_query_parameter("view","byuser"); |
| 725 | + } |
| 726 | + for(i=0; i<ArraySize(aViewType); i++){ |
| 727 | + if( fossil_strcmp(zView, aViewType[i].zVal)==0 ){ |
| 728 | + eType = aViewType[i].eType; |
| 729 | + break; |
| 730 | + } |
| 731 | + } |
| 732 | + url_initialize(&url, "reports"); |
| 733 | + cgi_query_parameters_to_url(&url); |
| 734 | + if( eType!=RPT_NONE ){ |
| 735 | + int nView = 0; /* Slots used in azView[] */ |
| 736 | + const char *azView[16]; /* Drop-down menu of view types */ |
| 737 | + for(i=0; i<ArraySize(aViewType); i++){ |
| 738 | + azView[nView++] = aViewType[i].zVal; |
| 739 | + azView[nView++] = aViewType[i].zName; |
| 740 | + } |
| 741 | + if( eType!=RPT_BYFILE ){ |
| 742 | + style_submenu_multichoice("type", ArraySize(azType)/2, azType, 0); |
| 743 | + } |
| 744 | + style_submenu_multichoice("view", nView/2, azView, 0); |
| 745 | + if( eType!=RPT_BYUSER ){ |
| 746 | + style_submenu_sql("u","User:", |
| 747 | + "SELECT '', 'All Users' UNION ALL " |
| 748 | + "SELECT x, x FROM (" |
| 749 | + " SELECT DISTINCT trim(coalesce(euser,user)) AS x FROM event %s" |
| 750 | + " ORDER BY 1 COLLATE nocase) WHERE x!=''", |
| 751 | + eType==RPT_BYFILE ? "WHERE type='ci'" : "" |
| 752 | + ); |
| 753 | + } |
| 754 | + } |
| 769 | 755 | style_submenu_element("Stats", "Stats", "%R/stat"); |
| 770 | 756 | url_reset(&url); |
| 771 | 757 | style_header("Activity Reports"); |
| 772 | | - if(0==fossil_strcmp(zView,"byyear")){ |
| 773 | | - stats_report_by_month_year(0, 0, zUserName); |
| 774 | | - }else if(0==fossil_strcmp(zView,"bymonth")){ |
| 775 | | - stats_report_by_month_year(1, 0, zUserName); |
| 776 | | - }else if(0==fossil_strcmp(zView,"byweek")){ |
| 777 | | - stats_report_year_weeks(zUserName); |
| 778 | | - }else if(0==fossil_strcmp(zView,"byuser")){ |
| 779 | | - stats_report_by_user(); |
| 780 | | - }else if(0==fossil_strcmp(zView,"byweekday")){ |
| 781 | | - stats_report_day_of_week(); |
| 782 | | - }else if(0==fossil_strcmp(zView,"byfile")){ |
| 783 | | - stats_report_by_file(); |
| 784 | | - }else{ |
| 785 | | - @ <h1>Activity Reports:</h1> |
| 786 | | - @ <ul> |
| 787 | | - @ <li>%z(href("?view=byyear"))Events by year</a></li> |
| 788 | | - @ <li>%z(href("?view=bymonth"))Events by month</a></li> |
| 789 | | - @ <li>%z(href("?view=byweek"))Events by calendar week</a></li> |
| 790 | | - @ <li>%z(href("?view=byweekday"))Events by day of the week</a></li> |
| 791 | | - @ <li>%z(href("?view=byuser"))Events by user</a></li> |
| 792 | | - @ <li>%z(href("?view=byfile"))Events by file</a></li> |
| 793 | | - @ </ul> |
| 794 | | - } |
| 795 | | - |
| 758 | + switch( eType ){ |
| 759 | + case RPT_BYYEAR: |
| 760 | + stats_report_by_month_year(0, 0, zUserName); |
| 761 | + break; |
| 762 | + case RPT_BYMONTH: |
| 763 | + stats_report_by_month_year(1, 0, zUserName); |
| 764 | + break; |
| 765 | + case RPT_BYWEEK: |
| 766 | + stats_report_year_weeks(zUserName); |
| 767 | + break; |
| 768 | + default: |
| 769 | + case RPT_BYUSER: |
| 770 | + stats_report_by_user(); |
| 771 | + break; |
| 772 | + case RPT_BYWEEKDAY: |
| 773 | + stats_report_day_of_week(zUserName); |
| 774 | + break; |
| 775 | + case RPT_BYFILE: |
| 776 | + stats_report_by_file(zUserName); |
| 777 | + break; |
| 778 | + } |
| 796 | 779 | style_footer(); |
| 797 | 780 | } |
| 798 | 781 | |