Fossil SCM
Move the "Activity Report" controls into drop-down menus.
Commit
4d60469f15833c6d2962a90762a556228e7acd6d
Parent
991f4b98f652213…
1 file changed
+191
-226
+191
-226
| --- src/statrep.c | ||
| +++ src/statrep.c | ||
| @@ -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 ? "&" : "", 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. |
| @@ -255,12 +202,10 @@ | ||
| 255 | 202 | Blob userFilter = empty_blob; /* Optional user=johndoe query string */ |
| 256 | 203 | stats_report_init_view(); |
| 257 | 204 | if( zUserName ){ |
| 258 | 205 | blob_appendf(&userFilter, "user=%s", zUserName); |
| 259 | 206 | } |
| 260 | - stats_report_event_types_menu( includeMonth ? "bymonth" : "byyear", | |
| 261 | - blob_str(&userFilter) ); | |
| 262 | 207 | blob_reset(&userFilter); |
| 263 | 208 | db_prepare(&query, |
| 264 | 209 | "SELECT substr(date(mtime),1,%d) AS timeframe," |
| 265 | 210 | " count(*) AS eventCount" |
| 266 | 211 | " FROM v_reports" |
| @@ -401,11 +346,10 @@ | ||
| 401 | 346 | int rowClass = 0; /* counter for alternating |
| 402 | 347 | row colors */ |
| 403 | 348 | int nMaxEvents = 1; /* max number of events for |
| 404 | 349 | all rows. */ |
| 405 | 350 | stats_report_init_view(); |
| 406 | - stats_report_event_types_menu("byuser", NULL); | |
| 407 | 351 | @ <h1>Timeline Events |
| 408 | 352 | @ (%s(stats_report_label_for_type())) by User</h1> |
| 409 | 353 | db_multi_exec( |
| 410 | 354 | "CREATE TEMP TABLE piechart(amt,label);" |
| 411 | 355 | "INSERT INTO piechart SELECT count(*), ifnull(euser,user) FROM v_reports" |
| @@ -436,20 +380,21 @@ | ||
| 436 | 380 | } |
| 437 | 381 | db_reset(&query); |
| 438 | 382 | while( SQLITE_ROW == db_step(&query) ){ |
| 439 | 383 | const char *zUser = db_column_text(&query, 0); |
| 440 | 384 | const int nCount = db_column_int(&query, 1); |
| 385 | + char y = (char)statsReportType; | |
| 441 | 386 | int nSize = nCount |
| 442 | 387 | ? (int)(100 * nCount / nMaxEvents) |
| 443 | 388 | : 0; |
| 444 | 389 | if(!nCount) continue /* arguable! Possible? */; |
| 445 | 390 | else if(!nSize) nSize = 1; |
| 446 | 391 | rowClass = ++nRowNumber % 2; |
| 447 | 392 | nEventTotal += nCount; |
| 448 | - @<tr class='row%d(rowClass)'> | |
| 393 | + @ <tr class='row%d(rowClass)'> | |
| 449 | 394 | @ <td> |
| 450 | - @ <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> | |
| 451 | 396 | @ </td><td data-sortkey='%08x(-nCount)'>%d(nCount)</td> |
| 452 | 397 | @ <td> |
| 453 | 398 | @ <div class='statistics-report-graph-line' |
| 454 | 399 | @ style='width:%d(nSize)%%;'> </div> |
| 455 | 400 | @ </td> |
| @@ -540,11 +485,10 @@ | ||
| 540 | 485 | |
| 541 | 486 | stats_report_init_view(); |
| 542 | 487 | if( zUserName ){ |
| 543 | 488 | blob_appendf(&userFilter, "user=%s", zUserName); |
| 544 | 489 | } |
| 545 | - stats_report_event_types_menu("byweekday", blob_str(&userFilter)); | |
| 546 | 490 | db_prepare(&query, |
| 547 | 491 | "SELECT cast(mtime %% 7 AS INTEGER) dow," |
| 548 | 492 | " COUNT(*) AS eventCount" |
| 549 | 493 | " FROM v_reports" |
| 550 | 494 | " WHERE ifnull(coalesce(euser,user,'')=%Q,1)" |
| @@ -554,16 +498,22 @@ | ||
| 554 | 498 | @ for user %h(zUserName) |
| 555 | 499 | } |
| 556 | 500 | @ </h1> |
| 557 | 501 | db_multi_exec( |
| 558 | 502 | "CREATE TEMP TABLE piechart(amt,label);" |
| 559 | - "INSERT INTO piechart SELECT count(*), cast(mtime %% 7 AS INT) FROM v_reports" | |
| 560 | - " WHERE ifnull(coalesce(euser,user,'')=%Q,1)" | |
| 561 | - " GROUP BY 2 ORDER BY 2;" | |
| 562 | - "UPDATE piechart SET label = CASE label WHEN 0 THEN 'Monday' WHEN 1 THEN 'Tuesday'" | |
| 563 | - " WHEN 2 THEN 'Wednesday' WHEN 3 THEN 'Thursday' WHEN 4 THEN 'Friday'" | |
| 564 | - " WHEN 5 THEN 'Saturday' ELSE 'Sunday' END;", zUserName | |
| 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 | |
| 565 | 515 | ); |
| 566 | 516 | if( db_int(0, "SELECT count(*) FROM piechart")>=2 ){ |
| 567 | 517 | @ <center><svg width=700 height=400> |
| 568 | 518 | piechart_render(700, 400, PIE_OTHER|PIE_PERCENT); |
| 569 | 519 | @ </svg></centre><hr/> |
| @@ -614,127 +564,116 @@ | ||
| 614 | 564 | ** week numbers. zTimeframe should be either a timeframe in the form YYYY |
| 615 | 565 | ** or YYYY-MM. If zUserName is not NULL then the report is restricted to events |
| 616 | 566 | ** created by the named user account. |
| 617 | 567 | */ |
| 618 | 568 | static void stats_report_year_weeks(const char *zUserName){ |
| 619 | - const char *zYear = P("y"); | |
| 620 | - 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 */ | |
| 621 | 571 | int i = 0; |
| 622 | - Stmt qYears = empty_Stmt; | |
| 623 | - char *zDefaultYear = NULL; | |
| 572 | + Stmt q; | |
| 624 | 573 | int nMaxEvents = 1; /* max number of events for |
| 625 | 574 | all rows. */ |
| 626 | 575 | int iterations = 0; /* # of active time periods. */ |
| 627 | - Blob urlParams = empty_blob; | |
| 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 | + | |
| 628 | 581 | stats_report_init_view(); |
| 629 | - if(4==nYear){ | |
| 630 | - blob_appendf(&urlParams, "y=%T", zYear); | |
| 631 | - } | |
| 632 | - if( zUserName ){ | |
| 633 | - blob_appendf(&urlParams, "%suser=%s", blob_size(&urlParams) ? "&" : "", | |
| 634 | - zUserName); | |
| 635 | - } | |
| 636 | - stats_report_event_types_menu("byweek", blob_str(&urlParams)); | |
| 637 | - blob_reset(&urlParams); | |
| 638 | - db_prepare(&qYears, | |
| 582 | + db_prepare(&q, | |
| 639 | 583 | "SELECT DISTINCT substr(date(mtime),1,4) AS y" |
| 640 | 584 | " FROM v_reports" |
| 641 | 585 | " WHERE ifnull(coalesce(euser,user,'')=%Q,1)" |
| 642 | - " GROUP BY y ORDER BY y", zUserName); | |
| 643 | - cgi_printf("Select year: "); | |
| 644 | - while( SQLITE_ROW == db_step(&qYears) ){ | |
| 645 | - const char *zT = db_column_text(&qYears, 0); | |
| 646 | - if( i++ ){ | |
| 647 | - cgi_printf(" "); | |
| 648 | - } | |
| 649 | - cgi_printf("<a href='?view=byweek&y=%s&type=%c", zT, | |
| 650 | - (char)statsReportType); | |
| 651 | - if( zUserName ){ | |
| 652 | - cgi_printf("&user=%t",zUserName); | |
| 653 | - } | |
| 654 | - cgi_printf("'>%s</a>",zT); | |
| 655 | - } | |
| 656 | - db_finalize(&qYears); | |
| 657 | - cgi_printf("<br/>"); | |
| 658 | - if(!zYear || !*zYear){ | |
| 659 | - zDefaultYear = db_text("????", "SELECT strftime('%%Y')"); | |
| 660 | - zYear = zDefaultYear; | |
| 661 | - nYear = 4; | |
| 662 | - } | |
| 663 | - if(4 == nYear){ | |
| 664 | - Stmt stWeek = empty_Stmt; | |
| 665 | - int rowCount = 0; | |
| 666 | - int total = 0; | |
| 667 | - db_prepare(&stWeek, | |
| 668 | - "SELECT DISTINCT strftime('%%W',mtime) AS wk, " | |
| 669 | - " count(*) AS n " | |
| 670 | - " FROM v_reports " | |
| 671 | - " WHERE %Q=substr(date(mtime),1,4) " | |
| 672 | - " AND mtime < current_timestamp " | |
| 673 | - " AND ifnull(coalesce(euser,user,'')=%Q,1)" | |
| 674 | - " GROUP BY wk ORDER BY wk DESC", zYear, zUserName); | |
| 675 | - @ <h1>Timeline events (%h(stats_report_label_for_type())) | |
| 676 | - @ for the calendar weeks of %h(zYear) | |
| 677 | - if( zUserName ){ | |
| 678 | - @ for user %h(zUserName) | |
| 679 | - } | |
| 680 | - @ </h1> | |
| 681 | - cgi_printf("<table class='statistics-report-table-events' " | |
| 682 | - "border='0' cellpadding='2' width='100%%' " | |
| 683 | - "cellspacing='0' id='statsTable'>"); | |
| 684 | - cgi_printf("<thead><tr>" | |
| 685 | - "<th>Week</th>" | |
| 686 | - "<th>Events</th>" | |
| 687 | - "<th width='90%%'><!-- relative commits graph --></th>" | |
| 688 | - "</tr></thead>" | |
| 689 | - "<tbody>"); | |
| 690 | - while( SQLITE_ROW == db_step(&stWeek) ){ | |
| 691 | - const int nCount = db_column_int(&stWeek, 1); | |
| 692 | - if(nCount>nMaxEvents){ | |
| 693 | - nMaxEvents = nCount; | |
| 694 | - } | |
| 695 | - ++iterations; | |
| 696 | - } | |
| 697 | - db_reset(&stWeek); | |
| 698 | - while( SQLITE_ROW == db_step(&stWeek) ){ | |
| 699 | - const char *zWeek = db_column_text(&stWeek,0); | |
| 700 | - const int nCount = db_column_int(&stWeek,1); | |
| 701 | - int nSize = nCount | |
| 702 | - ? (int)(100 * nCount / nMaxEvents) | |
| 703 | - : 0; | |
| 704 | - if(!nSize) nSize = 1; | |
| 705 | - total += nCount; | |
| 706 | - cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); | |
| 707 | - cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s", | |
| 708 | - zYear, zWeek, nCount, | |
| 709 | - statsReportTimelineYFlag); | |
| 710 | - if( zUserName ){ | |
| 711 | - cgi_printf("&u=%t",zUserName); | |
| 712 | - } | |
| 713 | - cgi_printf("'>%s</a></td>",zWeek); | |
| 714 | - | |
| 715 | - cgi_printf("<td>%d</td>",nCount); | |
| 716 | - cgi_printf("<td>"); | |
| 717 | - if(nCount){ | |
| 718 | - cgi_printf("<div class='statistics-report-graph-line'" | |
| 719 | - "style='width:%d%%;'> </div>", | |
| 720 | - nSize); | |
| 721 | - } | |
| 722 | - cgi_printf("</td></tr>"); | |
| 723 | - } | |
| 724 | - db_finalize(&stWeek); | |
| 725 | - free(zDefaultYear); | |
| 726 | - cgi_printf("</tbody></table>"); | |
| 727 | - if(total){ | |
| 728 | - int nAvg = iterations ? (total/iterations) : 0; | |
| 729 | - cgi_printf("<br><div>Total events: %d<br>" | |
| 730 | - "Average per active week: %d</div>", | |
| 731 | - total, nAvg); | |
| 732 | - } | |
| 733 | - output_table_sorting_javascript("statsTable","tnx",-1); | |
| 734 | - } | |
| 735 | -} | |
| 586 | + " GROUP BY y ORDER BY y DESC", zUserName); | |
| 587 | + while( SQLITE_ROW == db_step(&q) ){ | |
| 588 | + azYear = fossil_realloc(azYear, sizeof(char*)*(n+2)); | |
| 589 | + azYear[n] = fossil_strdup(db_column_text(&q,0)); | |
| 590 | + azYear[n+1] = azYear[n]; | |
| 591 | + if( !isValidYear && fossil_strcmp(zYear,azYear[n])==0 ) isValidYear = 1; | |
| 592 | + n += 2; | |
| 593 | + } | |
| 594 | + db_finalize(&q); | |
| 595 | + if( !isValidYear ) zYear = azYear[0]; | |
| 596 | + style_submenu_multichoice("y", n/2, (const char**)azYear, 0); | |
| 597 | + cgi_printf("<br/>"); | |
| 598 | + db_prepare(&q, | |
| 599 | + "SELECT DISTINCT strftime('%%W',mtime) AS wk, " | |
| 600 | + " count(*) AS n " | |
| 601 | + " FROM v_reports " | |
| 602 | + " WHERE %Q=substr(date(mtime),1,4) " | |
| 603 | + " AND mtime < current_timestamp " | |
| 604 | + " AND ifnull(coalesce(euser,user,'')=%Q,1)" | |
| 605 | + " GROUP BY wk ORDER BY wk DESC", zYear, zUserName); | |
| 606 | + @ <h1>Timeline events (%h(stats_report_label_for_type())) | |
| 607 | + @ for the calendar weeks of %h(zYear) | |
| 608 | + if( zUserName ){ | |
| 609 | + @ for user %h(zUserName) | |
| 610 | + } | |
| 611 | + @ </h1> | |
| 612 | + cgi_printf("<table class='statistics-report-table-events' " | |
| 613 | + "border='0' cellpadding='2' width='100%%' " | |
| 614 | + "cellspacing='0' id='statsTable'>"); | |
| 615 | + cgi_printf("<thead><tr>" | |
| 616 | + "<th>Week</th>" | |
| 617 | + "<th>Events</th>" | |
| 618 | + "<th width='90%%'><!-- relative commits graph --></th>" | |
| 619 | + "</tr></thead>" | |
| 620 | + "<tbody>"); | |
| 621 | + while( SQLITE_ROW == db_step(&q) ){ | |
| 622 | + const int nCount = db_column_int(&q, 1); | |
| 623 | + if(nCount>nMaxEvents){ | |
| 624 | + nMaxEvents = nCount; | |
| 625 | + } | |
| 626 | + ++iterations; | |
| 627 | + } | |
| 628 | + db_reset(&q); | |
| 629 | + while( SQLITE_ROW == db_step(&q) ){ | |
| 630 | + const char *zWeek = db_column_text(&q,0); | |
| 631 | + const int nCount = db_column_int(&q,1); | |
| 632 | + int nSize = nCount | |
| 633 | + ? (int)(100 * nCount / nMaxEvents) | |
| 634 | + : 0; | |
| 635 | + if(!nSize) nSize = 1; | |
| 636 | + total += nCount; | |
| 637 | + cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); | |
| 638 | + cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s", | |
| 639 | + zYear, zWeek, nCount, | |
| 640 | + statsReportTimelineYFlag); | |
| 641 | + if( zUserName ){ | |
| 642 | + cgi_printf("&u=%t",zUserName); | |
| 643 | + } | |
| 644 | + cgi_printf("'>%s</a></td>",zWeek); | |
| 645 | + | |
| 646 | + cgi_printf("<td>%d</td>",nCount); | |
| 647 | + cgi_printf("<td>"); | |
| 648 | + if(nCount){ | |
| 649 | + cgi_printf("<div class='statistics-report-graph-line'" | |
| 650 | + "style='width:%d%%;'> </div>", | |
| 651 | + nSize); | |
| 652 | + } | |
| 653 | + cgi_printf("</td></tr>"); | |
| 654 | + } | |
| 655 | + db_finalize(&q); | |
| 656 | + cgi_printf("</tbody></table>"); | |
| 657 | + if(total){ | |
| 658 | + int nAvg = iterations ? (total/iterations) : 0; | |
| 659 | + cgi_printf("<br><div>Total events: %d<br>" | |
| 660 | + "Average per active week: %d</div>", | |
| 661 | + total, nAvg); | |
| 662 | + } | |
| 663 | + output_table_sorting_javascript("statsTable","tnx",-1); | |
| 664 | +} | |
| 665 | + | |
| 666 | +/* Report types | |
| 667 | +*/ | |
| 668 | +#define RPT_BYFILE 1 | |
| 669 | +#define RPT_BYMONTH 2 | |
| 670 | +#define RPT_BYUSER 3 | |
| 671 | +#define RPT_BYWEEK 4 | |
| 672 | +#define RPT_BYWEEKDAY 5 | |
| 673 | +#define RPT_BYYEAR 6 | |
| 674 | +#define RPT_NONE 0 /* None of the above */ | |
| 736 | 675 | |
| 737 | 676 | /* |
| 738 | 677 | ** WEBPAGE: reports |
| 739 | 678 | ** |
| 740 | 679 | ** Shows activity reports for the repository. |
| @@ -754,62 +693,88 @@ | ||
| 754 | 693 | ** y=YYYY The year to report (default is the server's |
| 755 | 694 | ** current year). |
| 756 | 695 | */ |
| 757 | 696 | void stats_report_page(){ |
| 758 | 697 | HQuery url; /* URL for various branch links */ |
| 759 | - const char *zView = P("view"); /* Which view/report to show. */ | |
| 760 | - const char *zUserName = P("user"); | |
| 761 | - int haveU = !zUserName; | |
| 762 | - | |
| 763 | - login_check_credentials(); | |
| 764 | - if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 765 | - if(!zUserName){ | |
| 766 | - zUserName = P("u"); | |
| 767 | - haveU = !!zUserName; | |
| 768 | - } | |
| 769 | - if(zUserName && !*zUserName){ | |
| 770 | - zUserName = NULL; | |
| 771 | - } | |
| 772 | - url_initialize(&url, "reports"); | |
| 773 | - if(0!=fossil_strcmp(zView,"byuser")){ | |
| 774 | - style_submenu_entry(haveU ? "u" : "user", "User:", 12, 0); | |
| 775 | - if(zUserName){ | |
| 776 | - url_add_parameter(&url, haveU ? "u" : "user", zUserName); | |
| 777 | - statrep_submenu(&url, "(Remove User Flag)", "view", zView, | |
| 778 | - haveU ? "u" : "user"); | |
| 779 | - } | |
| 780 | - } | |
| 781 | - statrep_submenu(&url, "By Year", "view", "byyear", 0); | |
| 782 | - statrep_submenu(&url, "By Month", "view", "bymonth", 0); | |
| 783 | - statrep_submenu(&url, "By Week", "view", "byweek", 0); | |
| 784 | - statrep_submenu(&url, "By Weekday", "view", "byweekday", 0); | |
| 785 | - statrep_submenu(&url, "By User", "view", "byuser", "user"); | |
| 786 | - statrep_submenu(&url, "By File", "view", "byfile", "file"); | |
| 698 | + const char *zView = P("view"); /* Which view/report to show. */ | |
| 699 | + int eType = RPT_NONE; /* Numeric code for view/report to show */ | |
| 700 | + int i; /* Loop counter */ | |
| 701 | + const char *zUserName; /* Name of user */ | |
| 702 | + const struct { | |
| 703 | + const char *zName; /* Name of view= screen type */ | |
| 704 | + const char *zVal; /* Value of view= query parameter */ | |
| 705 | + int eType; /* Corresponding RPT_* define */ | |
| 706 | + } aViewType[] = { | |
| 707 | + { "By File", "byfile", RPT_BYFILE }, | |
| 708 | + { "By Month", "bymonth", RPT_BYMONTH }, | |
| 709 | + { "By User", "byuser", RPT_BYUSER }, | |
| 710 | + { "By Week", "byweek", RPT_BYWEEK }, | |
| 711 | + { "By Weekday", "byweekday", RPT_BYWEEKDAY }, | |
| 712 | + { "By Year", "byyear", RPT_BYYEAR }, | |
| 713 | + }; | |
| 714 | + const char *azType[] = { | |
| 715 | + "a", "Any Type", | |
| 716 | + "ci", "Check-ins", | |
| 717 | + "g", "Tags", | |
| 718 | + "e", "Tech Notes", | |
| 719 | + "t", "Tickets", | |
| 720 | + "w", "Wiki" | |
| 721 | + }; | |
| 722 | + | |
| 723 | + login_check_credentials(); | |
| 724 | + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } | |
| 725 | + zUserName = P("user"); | |
| 726 | + if( zUserName==0 ) zUserName = P("u"); | |
| 727 | + if( zUserName && zUserName[0]==0 ) zUserName = 0; | |
| 728 | + if( zView==0 ){ | |
| 729 | + zView = "byuser"; | |
| 730 | + cgi_replace_query_parameter("view","byuser"); | |
| 731 | + } | |
| 732 | + for(i=0; i<ArraySize(aViewType); i++){ | |
| 733 | + if( fossil_strcmp(zView, aViewType[i].zVal)==0 ){ | |
| 734 | + eType = aViewType[i].eType; | |
| 735 | + break; | |
| 736 | + } | |
| 737 | + } | |
| 738 | + url_initialize(&url, "reports"); | |
| 739 | + cgi_query_parameters_to_url(&url); | |
| 740 | + if( eType!=RPT_NONE ){ | |
| 741 | + int nView = 0; /* Slots used in azView[] */ | |
| 742 | + const char *azView[16]; /* Drop-down menu of view types */ | |
| 743 | + for(i=0; i<ArraySize(aViewType); i++){ | |
| 744 | + azView[nView++] = aViewType[i].zVal; | |
| 745 | + azView[nView++] = aViewType[i].zName; | |
| 746 | + } | |
| 747 | + style_submenu_multichoice("view", nView/2, azView, 0); | |
| 748 | + if( eType!=RPT_BYFILE ){ | |
| 749 | + style_submenu_multichoice("type", ArraySize(azType)/2, azType, 0); | |
| 750 | + } | |
| 751 | + if( eType!=RPT_BYUSER ){ | |
| 752 | + style_submenu_entry("u", "User:", 12, 0); | |
| 753 | + } | |
| 754 | + } | |
| 787 | 755 | style_submenu_element("Stats", "Stats", "%R/stat"); |
| 788 | 756 | url_reset(&url); |
| 789 | 757 | style_header("Activity Reports"); |
| 790 | - if(0==fossil_strcmp(zView,"byyear")){ | |
| 791 | - stats_report_by_month_year(0, 0, zUserName); | |
| 792 | - }else if(0==fossil_strcmp(zView,"bymonth")){ | |
| 793 | - stats_report_by_month_year(1, 0, zUserName); | |
| 794 | - }else if(0==fossil_strcmp(zView,"byweek")){ | |
| 795 | - stats_report_year_weeks(zUserName); | |
| 796 | - }else if(0==fossil_strcmp(zView,"byuser")){ | |
| 797 | - stats_report_by_user(); | |
| 798 | - }else if(0==fossil_strcmp(zView,"byweekday")){ | |
| 799 | - stats_report_day_of_week(zUserName); | |
| 800 | - }else if(0==fossil_strcmp(zView,"byfile")){ | |
| 801 | - stats_report_by_file(zUserName); | |
| 802 | - }else{ | |
| 803 | - @ <h1>Activity Reports:</h1> | |
| 804 | - @ <ul> | |
| 805 | - @ <li>%z(href("?view=byyear"))Events by year</a></li> | |
| 806 | - @ <li>%z(href("?view=bymonth"))Events by month</a></li> | |
| 807 | - @ <li>%z(href("?view=byweek"))Events by calendar week</a></li> | |
| 808 | - @ <li>%z(href("?view=byweekday"))Events by day of the week</a></li> | |
| 809 | - @ <li>%z(href("?view=byuser"))Events by user</a></li> | |
| 810 | - @ <li>%z(href("?view=byfile"))Events by file</a></li> | |
| 811 | - @ </ul> | |
| 812 | - } | |
| 813 | - | |
| 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 | + } | |
| 814 | 779 | style_footer(); |
| 815 | 780 | } |
| 816 | 781 |
| --- src/statrep.c | |
| +++ src/statrep.c | |
| @@ -141,63 +141,10 @@ | |
| 141 | default: |
| 142 | return "all types"; |
| 143 | } |
| 144 | } |
| 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 ? "&" : "", 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 | |
| 200 | /* |
| 201 | ** Helper for stats_report_by_month_year(), which generates a list of |
| 202 | ** week numbers. zTimeframe should be either a timeframe in the form YYYY |
| 203 | ** or YYYY-MM. |
| @@ -255,12 +202,10 @@ | |
| 255 | Blob userFilter = empty_blob; /* Optional user=johndoe query string */ |
| 256 | stats_report_init_view(); |
| 257 | if( zUserName ){ |
| 258 | blob_appendf(&userFilter, "user=%s", zUserName); |
| 259 | } |
| 260 | stats_report_event_types_menu( includeMonth ? "bymonth" : "byyear", |
| 261 | blob_str(&userFilter) ); |
| 262 | blob_reset(&userFilter); |
| 263 | db_prepare(&query, |
| 264 | "SELECT substr(date(mtime),1,%d) AS timeframe," |
| 265 | " count(*) AS eventCount" |
| 266 | " FROM v_reports" |
| @@ -401,11 +346,10 @@ | |
| 401 | int rowClass = 0; /* counter for alternating |
| 402 | row colors */ |
| 403 | int nMaxEvents = 1; /* max number of events for |
| 404 | all rows. */ |
| 405 | stats_report_init_view(); |
| 406 | stats_report_event_types_menu("byuser", NULL); |
| 407 | @ <h1>Timeline Events |
| 408 | @ (%s(stats_report_label_for_type())) by User</h1> |
| 409 | db_multi_exec( |
| 410 | "CREATE TEMP TABLE piechart(amt,label);" |
| 411 | "INSERT INTO piechart SELECT count(*), ifnull(euser,user) FROM v_reports" |
| @@ -436,20 +380,21 @@ | |
| 436 | } |
| 437 | db_reset(&query); |
| 438 | while( SQLITE_ROW == db_step(&query) ){ |
| 439 | const char *zUser = db_column_text(&query, 0); |
| 440 | const int nCount = db_column_int(&query, 1); |
| 441 | int nSize = nCount |
| 442 | ? (int)(100 * nCount / nMaxEvents) |
| 443 | : 0; |
| 444 | if(!nCount) continue /* arguable! Possible? */; |
| 445 | else if(!nSize) nSize = 1; |
| 446 | rowClass = ++nRowNumber % 2; |
| 447 | nEventTotal += nCount; |
| 448 | @<tr class='row%d(rowClass)'> |
| 449 | @ <td> |
| 450 | @ <a href="?view=bymonth&user=%h(zUser)&type=%c((char)statsReportType)">%h(zUser)</a> |
| 451 | @ </td><td data-sortkey='%08x(-nCount)'>%d(nCount)</td> |
| 452 | @ <td> |
| 453 | @ <div class='statistics-report-graph-line' |
| 454 | @ style='width:%d(nSize)%%;'> </div> |
| 455 | @ </td> |
| @@ -540,11 +485,10 @@ | |
| 540 | |
| 541 | stats_report_init_view(); |
| 542 | if( zUserName ){ |
| 543 | blob_appendf(&userFilter, "user=%s", zUserName); |
| 544 | } |
| 545 | stats_report_event_types_menu("byweekday", blob_str(&userFilter)); |
| 546 | db_prepare(&query, |
| 547 | "SELECT cast(mtime %% 7 AS INTEGER) dow," |
| 548 | " COUNT(*) AS eventCount" |
| 549 | " FROM v_reports" |
| 550 | " WHERE ifnull(coalesce(euser,user,'')=%Q,1)" |
| @@ -554,16 +498,22 @@ | |
| 554 | @ for user %h(zUserName) |
| 555 | } |
| 556 | @ </h1> |
| 557 | db_multi_exec( |
| 558 | "CREATE TEMP TABLE piechart(amt,label);" |
| 559 | "INSERT INTO piechart SELECT count(*), cast(mtime %% 7 AS INT) FROM v_reports" |
| 560 | " WHERE ifnull(coalesce(euser,user,'')=%Q,1)" |
| 561 | " GROUP BY 2 ORDER BY 2;" |
| 562 | "UPDATE piechart SET label = CASE label WHEN 0 THEN 'Monday' WHEN 1 THEN 'Tuesday'" |
| 563 | " WHEN 2 THEN 'Wednesday' WHEN 3 THEN 'Thursday' WHEN 4 THEN 'Friday'" |
| 564 | " WHEN 5 THEN 'Saturday' ELSE 'Sunday' END;", zUserName |
| 565 | ); |
| 566 | if( db_int(0, "SELECT count(*) FROM piechart")>=2 ){ |
| 567 | @ <center><svg width=700 height=400> |
| 568 | piechart_render(700, 400, PIE_OTHER|PIE_PERCENT); |
| 569 | @ </svg></centre><hr/> |
| @@ -614,127 +564,116 @@ | |
| 614 | ** week numbers. zTimeframe should be either a timeframe in the form YYYY |
| 615 | ** or YYYY-MM. If zUserName is not NULL then the report is restricted to events |
| 616 | ** created by the named user account. |
| 617 | */ |
| 618 | static void stats_report_year_weeks(const char *zUserName){ |
| 619 | const char *zYear = P("y"); |
| 620 | int nYear = zYear ? strlen(zYear) : 0; |
| 621 | int i = 0; |
| 622 | Stmt qYears = empty_Stmt; |
| 623 | char *zDefaultYear = NULL; |
| 624 | int nMaxEvents = 1; /* max number of events for |
| 625 | all rows. */ |
| 626 | int iterations = 0; /* # of active time periods. */ |
| 627 | Blob urlParams = empty_blob; |
| 628 | stats_report_init_view(); |
| 629 | if(4==nYear){ |
| 630 | blob_appendf(&urlParams, "y=%T", zYear); |
| 631 | } |
| 632 | if( zUserName ){ |
| 633 | blob_appendf(&urlParams, "%suser=%s", blob_size(&urlParams) ? "&" : "", |
| 634 | zUserName); |
| 635 | } |
| 636 | stats_report_event_types_menu("byweek", blob_str(&urlParams)); |
| 637 | blob_reset(&urlParams); |
| 638 | db_prepare(&qYears, |
| 639 | "SELECT DISTINCT substr(date(mtime),1,4) AS y" |
| 640 | " FROM v_reports" |
| 641 | " WHERE ifnull(coalesce(euser,user,'')=%Q,1)" |
| 642 | " GROUP BY y ORDER BY y", zUserName); |
| 643 | cgi_printf("Select year: "); |
| 644 | while( SQLITE_ROW == db_step(&qYears) ){ |
| 645 | const char *zT = db_column_text(&qYears, 0); |
| 646 | if( i++ ){ |
| 647 | cgi_printf(" "); |
| 648 | } |
| 649 | cgi_printf("<a href='?view=byweek&y=%s&type=%c", zT, |
| 650 | (char)statsReportType); |
| 651 | if( zUserName ){ |
| 652 | cgi_printf("&user=%t",zUserName); |
| 653 | } |
| 654 | cgi_printf("'>%s</a>",zT); |
| 655 | } |
| 656 | db_finalize(&qYears); |
| 657 | cgi_printf("<br/>"); |
| 658 | if(!zYear || !*zYear){ |
| 659 | zDefaultYear = db_text("????", "SELECT strftime('%%Y')"); |
| 660 | zYear = zDefaultYear; |
| 661 | nYear = 4; |
| 662 | } |
| 663 | if(4 == nYear){ |
| 664 | Stmt stWeek = empty_Stmt; |
| 665 | int rowCount = 0; |
| 666 | int total = 0; |
| 667 | db_prepare(&stWeek, |
| 668 | "SELECT DISTINCT strftime('%%W',mtime) AS wk, " |
| 669 | " count(*) AS n " |
| 670 | " FROM v_reports " |
| 671 | " WHERE %Q=substr(date(mtime),1,4) " |
| 672 | " AND mtime < current_timestamp " |
| 673 | " AND ifnull(coalesce(euser,user,'')=%Q,1)" |
| 674 | " GROUP BY wk ORDER BY wk DESC", zYear, zUserName); |
| 675 | @ <h1>Timeline events (%h(stats_report_label_for_type())) |
| 676 | @ for the calendar weeks of %h(zYear) |
| 677 | if( zUserName ){ |
| 678 | @ for user %h(zUserName) |
| 679 | } |
| 680 | @ </h1> |
| 681 | cgi_printf("<table class='statistics-report-table-events' " |
| 682 | "border='0' cellpadding='2' width='100%%' " |
| 683 | "cellspacing='0' id='statsTable'>"); |
| 684 | cgi_printf("<thead><tr>" |
| 685 | "<th>Week</th>" |
| 686 | "<th>Events</th>" |
| 687 | "<th width='90%%'><!-- relative commits graph --></th>" |
| 688 | "</tr></thead>" |
| 689 | "<tbody>"); |
| 690 | while( SQLITE_ROW == db_step(&stWeek) ){ |
| 691 | const int nCount = db_column_int(&stWeek, 1); |
| 692 | if(nCount>nMaxEvents){ |
| 693 | nMaxEvents = nCount; |
| 694 | } |
| 695 | ++iterations; |
| 696 | } |
| 697 | db_reset(&stWeek); |
| 698 | while( SQLITE_ROW == db_step(&stWeek) ){ |
| 699 | const char *zWeek = db_column_text(&stWeek,0); |
| 700 | const int nCount = db_column_int(&stWeek,1); |
| 701 | int nSize = nCount |
| 702 | ? (int)(100 * nCount / nMaxEvents) |
| 703 | : 0; |
| 704 | if(!nSize) nSize = 1; |
| 705 | total += nCount; |
| 706 | cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); |
| 707 | cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s", |
| 708 | zYear, zWeek, nCount, |
| 709 | statsReportTimelineYFlag); |
| 710 | if( zUserName ){ |
| 711 | cgi_printf("&u=%t",zUserName); |
| 712 | } |
| 713 | cgi_printf("'>%s</a></td>",zWeek); |
| 714 | |
| 715 | cgi_printf("<td>%d</td>",nCount); |
| 716 | cgi_printf("<td>"); |
| 717 | if(nCount){ |
| 718 | cgi_printf("<div class='statistics-report-graph-line'" |
| 719 | "style='width:%d%%;'> </div>", |
| 720 | nSize); |
| 721 | } |
| 722 | cgi_printf("</td></tr>"); |
| 723 | } |
| 724 | db_finalize(&stWeek); |
| 725 | free(zDefaultYear); |
| 726 | cgi_printf("</tbody></table>"); |
| 727 | if(total){ |
| 728 | int nAvg = iterations ? (total/iterations) : 0; |
| 729 | cgi_printf("<br><div>Total events: %d<br>" |
| 730 | "Average per active week: %d</div>", |
| 731 | total, nAvg); |
| 732 | } |
| 733 | output_table_sorting_javascript("statsTable","tnx",-1); |
| 734 | } |
| 735 | } |
| 736 | |
| 737 | /* |
| 738 | ** WEBPAGE: reports |
| 739 | ** |
| 740 | ** Shows activity reports for the repository. |
| @@ -754,62 +693,88 @@ | |
| 754 | ** y=YYYY The year to report (default is the server's |
| 755 | ** current year). |
| 756 | */ |
| 757 | void stats_report_page(){ |
| 758 | HQuery url; /* URL for various branch links */ |
| 759 | const char *zView = P("view"); /* Which view/report to show. */ |
| 760 | const char *zUserName = P("user"); |
| 761 | int haveU = !zUserName; |
| 762 | |
| 763 | login_check_credentials(); |
| 764 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 765 | if(!zUserName){ |
| 766 | zUserName = P("u"); |
| 767 | haveU = !!zUserName; |
| 768 | } |
| 769 | if(zUserName && !*zUserName){ |
| 770 | zUserName = NULL; |
| 771 | } |
| 772 | url_initialize(&url, "reports"); |
| 773 | if(0!=fossil_strcmp(zView,"byuser")){ |
| 774 | style_submenu_entry(haveU ? "u" : "user", "User:", 12, 0); |
| 775 | if(zUserName){ |
| 776 | url_add_parameter(&url, haveU ? "u" : "user", zUserName); |
| 777 | statrep_submenu(&url, "(Remove User Flag)", "view", zView, |
| 778 | haveU ? "u" : "user"); |
| 779 | } |
| 780 | } |
| 781 | statrep_submenu(&url, "By Year", "view", "byyear", 0); |
| 782 | statrep_submenu(&url, "By Month", "view", "bymonth", 0); |
| 783 | statrep_submenu(&url, "By Week", "view", "byweek", 0); |
| 784 | statrep_submenu(&url, "By Weekday", "view", "byweekday", 0); |
| 785 | statrep_submenu(&url, "By User", "view", "byuser", "user"); |
| 786 | statrep_submenu(&url, "By File", "view", "byfile", "file"); |
| 787 | style_submenu_element("Stats", "Stats", "%R/stat"); |
| 788 | url_reset(&url); |
| 789 | style_header("Activity Reports"); |
| 790 | if(0==fossil_strcmp(zView,"byyear")){ |
| 791 | stats_report_by_month_year(0, 0, zUserName); |
| 792 | }else if(0==fossil_strcmp(zView,"bymonth")){ |
| 793 | stats_report_by_month_year(1, 0, zUserName); |
| 794 | }else if(0==fossil_strcmp(zView,"byweek")){ |
| 795 | stats_report_year_weeks(zUserName); |
| 796 | }else if(0==fossil_strcmp(zView,"byuser")){ |
| 797 | stats_report_by_user(); |
| 798 | }else if(0==fossil_strcmp(zView,"byweekday")){ |
| 799 | stats_report_day_of_week(zUserName); |
| 800 | }else if(0==fossil_strcmp(zView,"byfile")){ |
| 801 | stats_report_by_file(zUserName); |
| 802 | }else{ |
| 803 | @ <h1>Activity Reports:</h1> |
| 804 | @ <ul> |
| 805 | @ <li>%z(href("?view=byyear"))Events by year</a></li> |
| 806 | @ <li>%z(href("?view=bymonth"))Events by month</a></li> |
| 807 | @ <li>%z(href("?view=byweek"))Events by calendar week</a></li> |
| 808 | @ <li>%z(href("?view=byweekday"))Events by day of the week</a></li> |
| 809 | @ <li>%z(href("?view=byuser"))Events by user</a></li> |
| 810 | @ <li>%z(href("?view=byfile"))Events by file</a></li> |
| 811 | @ </ul> |
| 812 | } |
| 813 | |
| 814 | style_footer(); |
| 815 | } |
| 816 |
| --- src/statrep.c | |
| +++ src/statrep.c | |
| @@ -141,63 +141,10 @@ | |
| 141 | default: |
| 142 | return "all types"; |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | |
| 147 | /* |
| 148 | ** Helper for stats_report_by_month_year(), which generates a list of |
| 149 | ** week numbers. zTimeframe should be either a timeframe in the form YYYY |
| 150 | ** or YYYY-MM. |
| @@ -255,12 +202,10 @@ | |
| 202 | Blob userFilter = empty_blob; /* Optional user=johndoe query string */ |
| 203 | stats_report_init_view(); |
| 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" |
| @@ -401,11 +346,10 @@ | |
| 346 | int rowClass = 0; /* counter for alternating |
| 347 | row colors */ |
| 348 | int nMaxEvents = 1; /* max number of events for |
| 349 | all rows. */ |
| 350 | stats_report_init_view(); |
| 351 | @ <h1>Timeline Events |
| 352 | @ (%s(stats_report_label_for_type())) by User</h1> |
| 353 | db_multi_exec( |
| 354 | "CREATE TEMP TABLE piechart(amt,label);" |
| 355 | "INSERT INTO piechart SELECT count(*), ifnull(euser,user) FROM v_reports" |
| @@ -436,20 +380,21 @@ | |
| 380 | } |
| 381 | db_reset(&query); |
| 382 | while( SQLITE_ROW == db_step(&query) ){ |
| 383 | const char *zUser = db_column_text(&query, 0); |
| 384 | const int nCount = db_column_int(&query, 1); |
| 385 | char y = (char)statsReportType; |
| 386 | int nSize = nCount |
| 387 | ? (int)(100 * nCount / nMaxEvents) |
| 388 | : 0; |
| 389 | if(!nCount) continue /* arguable! Possible? */; |
| 390 | else if(!nSize) nSize = 1; |
| 391 | rowClass = ++nRowNumber % 2; |
| 392 | nEventTotal += nCount; |
| 393 | @ <tr class='row%d(rowClass)'> |
| 394 | @ <td> |
| 395 | @ <a href="?view=bymonth&user=%h(zUser)&type=%c(y)">%h(zUser)</a> |
| 396 | @ </td><td data-sortkey='%08x(-nCount)'>%d(nCount)</td> |
| 397 | @ <td> |
| 398 | @ <div class='statistics-report-graph-line' |
| 399 | @ style='width:%d(nSize)%%;'> </div> |
| 400 | @ </td> |
| @@ -540,11 +485,10 @@ | |
| 485 | |
| 486 | stats_report_init_view(); |
| 487 | if( zUserName ){ |
| 488 | blob_appendf(&userFilter, "user=%s", zUserName); |
| 489 | } |
| 490 | db_prepare(&query, |
| 491 | "SELECT cast(mtime %% 7 AS INTEGER) dow," |
| 492 | " COUNT(*) AS eventCount" |
| 493 | " FROM v_reports" |
| 494 | " WHERE ifnull(coalesce(euser,user,'')=%Q,1)" |
| @@ -554,16 +498,22 @@ | |
| 498 | @ for user %h(zUserName) |
| 499 | } |
| 500 | @ </h1> |
| 501 | db_multi_exec( |
| 502 | "CREATE TEMP TABLE piechart(amt,label);" |
| 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 |
| 515 | ); |
| 516 | if( db_int(0, "SELECT count(*) FROM piechart")>=2 ){ |
| 517 | @ <center><svg width=700 height=400> |
| 518 | piechart_render(700, 400, PIE_OTHER|PIE_PERCENT); |
| 519 | @ </svg></centre><hr/> |
| @@ -614,127 +564,116 @@ | |
| 564 | ** week numbers. zTimeframe should be either a timeframe in the form YYYY |
| 565 | ** or YYYY-MM. If zUserName is not NULL then the report is restricted to events |
| 566 | ** created by the named user account. |
| 567 | */ |
| 568 | static void stats_report_year_weeks(const char *zUserName){ |
| 569 | const char *zYear = P("y"); /* Year for which report shown */ |
| 570 | int isValidYear = 0; /* True if a valid year */ |
| 571 | int i = 0; |
| 572 | Stmt q; |
| 573 | int nMaxEvents = 1; /* max number of events for |
| 574 | all rows. */ |
| 575 | int iterations = 0; /* # of active time periods. */ |
| 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 | db_prepare(&q, |
| 583 | "SELECT DISTINCT substr(date(mtime),1,4) AS y" |
| 584 | " FROM v_reports" |
| 585 | " WHERE ifnull(coalesce(euser,user,'')=%Q,1)" |
| 586 | " GROUP BY y ORDER BY y DESC", zUserName); |
| 587 | while( SQLITE_ROW == db_step(&q) ){ |
| 588 | azYear = fossil_realloc(azYear, sizeof(char*)*(n+2)); |
| 589 | azYear[n] = fossil_strdup(db_column_text(&q,0)); |
| 590 | azYear[n+1] = azYear[n]; |
| 591 | if( !isValidYear && fossil_strcmp(zYear,azYear[n])==0 ) isValidYear = 1; |
| 592 | n += 2; |
| 593 | } |
| 594 | db_finalize(&q); |
| 595 | if( !isValidYear ) zYear = azYear[0]; |
| 596 | style_submenu_multichoice("y", n/2, (const char**)azYear, 0); |
| 597 | cgi_printf("<br/>"); |
| 598 | db_prepare(&q, |
| 599 | "SELECT DISTINCT strftime('%%W',mtime) AS wk, " |
| 600 | " count(*) AS n " |
| 601 | " FROM v_reports " |
| 602 | " WHERE %Q=substr(date(mtime),1,4) " |
| 603 | " AND mtime < current_timestamp " |
| 604 | " AND ifnull(coalesce(euser,user,'')=%Q,1)" |
| 605 | " GROUP BY wk ORDER BY wk DESC", zYear, zUserName); |
| 606 | @ <h1>Timeline events (%h(stats_report_label_for_type())) |
| 607 | @ for the calendar weeks of %h(zYear) |
| 608 | if( zUserName ){ |
| 609 | @ for user %h(zUserName) |
| 610 | } |
| 611 | @ </h1> |
| 612 | cgi_printf("<table class='statistics-report-table-events' " |
| 613 | "border='0' cellpadding='2' width='100%%' " |
| 614 | "cellspacing='0' id='statsTable'>"); |
| 615 | cgi_printf("<thead><tr>" |
| 616 | "<th>Week</th>" |
| 617 | "<th>Events</th>" |
| 618 | "<th width='90%%'><!-- relative commits graph --></th>" |
| 619 | "</tr></thead>" |
| 620 | "<tbody>"); |
| 621 | while( SQLITE_ROW == db_step(&q) ){ |
| 622 | const int nCount = db_column_int(&q, 1); |
| 623 | if(nCount>nMaxEvents){ |
| 624 | nMaxEvents = nCount; |
| 625 | } |
| 626 | ++iterations; |
| 627 | } |
| 628 | db_reset(&q); |
| 629 | while( SQLITE_ROW == db_step(&q) ){ |
| 630 | const char *zWeek = db_column_text(&q,0); |
| 631 | const int nCount = db_column_int(&q,1); |
| 632 | int nSize = nCount |
| 633 | ? (int)(100 * nCount / nMaxEvents) |
| 634 | : 0; |
| 635 | if(!nSize) nSize = 1; |
| 636 | total += nCount; |
| 637 | cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); |
| 638 | cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s", |
| 639 | zYear, zWeek, nCount, |
| 640 | statsReportTimelineYFlag); |
| 641 | if( zUserName ){ |
| 642 | cgi_printf("&u=%t",zUserName); |
| 643 | } |
| 644 | cgi_printf("'>%s</a></td>",zWeek); |
| 645 | |
| 646 | cgi_printf("<td>%d</td>",nCount); |
| 647 | cgi_printf("<td>"); |
| 648 | if(nCount){ |
| 649 | cgi_printf("<div class='statistics-report-graph-line'" |
| 650 | "style='width:%d%%;'> </div>", |
| 651 | nSize); |
| 652 | } |
| 653 | cgi_printf("</td></tr>"); |
| 654 | } |
| 655 | db_finalize(&q); |
| 656 | cgi_printf("</tbody></table>"); |
| 657 | if(total){ |
| 658 | int nAvg = iterations ? (total/iterations) : 0; |
| 659 | cgi_printf("<br><div>Total events: %d<br>" |
| 660 | "Average per active week: %d</div>", |
| 661 | total, nAvg); |
| 662 | } |
| 663 | output_table_sorting_javascript("statsTable","tnx",-1); |
| 664 | } |
| 665 | |
| 666 | /* Report types |
| 667 | */ |
| 668 | #define RPT_BYFILE 1 |
| 669 | #define RPT_BYMONTH 2 |
| 670 | #define RPT_BYUSER 3 |
| 671 | #define RPT_BYWEEK 4 |
| 672 | #define RPT_BYWEEKDAY 5 |
| 673 | #define RPT_BYYEAR 6 |
| 674 | #define RPT_NONE 0 /* None of the above */ |
| 675 | |
| 676 | /* |
| 677 | ** WEBPAGE: reports |
| 678 | ** |
| 679 | ** Shows activity reports for the repository. |
| @@ -754,62 +693,88 @@ | |
| 693 | ** y=YYYY The year to report (default is the server's |
| 694 | ** current year). |
| 695 | */ |
| 696 | void stats_report_page(){ |
| 697 | HQuery url; /* URL for various branch links */ |
| 698 | const char *zView = P("view"); /* Which view/report to show. */ |
| 699 | int eType = RPT_NONE; /* Numeric code for view/report to show */ |
| 700 | int i; /* Loop counter */ |
| 701 | const char *zUserName; /* Name of user */ |
| 702 | const struct { |
| 703 | const char *zName; /* Name of view= screen type */ |
| 704 | const char *zVal; /* Value of view= query parameter */ |
| 705 | int eType; /* Corresponding RPT_* define */ |
| 706 | } aViewType[] = { |
| 707 | { "By File", "byfile", RPT_BYFILE }, |
| 708 | { "By Month", "bymonth", RPT_BYMONTH }, |
| 709 | { "By User", "byuser", RPT_BYUSER }, |
| 710 | { "By Week", "byweek", RPT_BYWEEK }, |
| 711 | { "By Weekday", "byweekday", RPT_BYWEEKDAY }, |
| 712 | { "By Year", "byyear", RPT_BYYEAR }, |
| 713 | }; |
| 714 | const char *azType[] = { |
| 715 | "a", "Any Type", |
| 716 | "ci", "Check-ins", |
| 717 | "g", "Tags", |
| 718 | "e", "Tech Notes", |
| 719 | "t", "Tickets", |
| 720 | "w", "Wiki" |
| 721 | }; |
| 722 | |
| 723 | login_check_credentials(); |
| 724 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 725 | zUserName = P("user"); |
| 726 | if( zUserName==0 ) zUserName = P("u"); |
| 727 | if( zUserName && zUserName[0]==0 ) zUserName = 0; |
| 728 | if( zView==0 ){ |
| 729 | zView = "byuser"; |
| 730 | cgi_replace_query_parameter("view","byuser"); |
| 731 | } |
| 732 | for(i=0; i<ArraySize(aViewType); i++){ |
| 733 | if( fossil_strcmp(zView, aViewType[i].zVal)==0 ){ |
| 734 | eType = aViewType[i].eType; |
| 735 | break; |
| 736 | } |
| 737 | } |
| 738 | url_initialize(&url, "reports"); |
| 739 | cgi_query_parameters_to_url(&url); |
| 740 | if( eType!=RPT_NONE ){ |
| 741 | int nView = 0; /* Slots used in azView[] */ |
| 742 | const char *azView[16]; /* Drop-down menu of view types */ |
| 743 | for(i=0; i<ArraySize(aViewType); i++){ |
| 744 | azView[nView++] = aViewType[i].zVal; |
| 745 | azView[nView++] = aViewType[i].zName; |
| 746 | } |
| 747 | style_submenu_multichoice("view", nView/2, azView, 0); |
| 748 | if( eType!=RPT_BYFILE ){ |
| 749 | style_submenu_multichoice("type", ArraySize(azType)/2, azType, 0); |
| 750 | } |
| 751 | if( eType!=RPT_BYUSER ){ |
| 752 | style_submenu_entry("u", "User:", 12, 0); |
| 753 | } |
| 754 | } |
| 755 | style_submenu_element("Stats", "Stats", "%R/stat"); |
| 756 | url_reset(&url); |
| 757 | style_header("Activity Reports"); |
| 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 | } |
| 779 | style_footer(); |
| 780 | } |
| 781 |