Fossil SCM
Permit filtering weekday and file reports by user. Also ensure the user parameter is preserved when changing types. Lots of general cleanup to make this possible.
Commit
60018f9d8ab75b675b60274d387a885b449c5cc7
Parent
48499514cc69c77…
2 files changed
+95
-85
+2
+95
-85
| --- src/statrep.c | ||
| +++ src/statrep.c | ||
| @@ -157,11 +157,11 @@ | ||
| 157 | 157 | char *zTop; |
| 158 | 158 | if(zParam && !*zParam){ |
| 159 | 159 | zParam = NULL; |
| 160 | 160 | } |
| 161 | 161 | zTop = mprintf("%s/reports?view=%s%s%s", g.zTop, zCurrentViewName, |
| 162 | - zParam ? "&" : "", zParam); | |
| 162 | + zParam&&*zParam ? "&" : "", zParam); | |
| 163 | 163 | cgi_printf("<div>"); |
| 164 | 164 | cgi_printf("<span>Types:</span> "); |
| 165 | 165 | if('*' == statsReportType){ |
| 166 | 166 | cgi_printf(" <strong>all</strong>", zTop); |
| 167 | 167 | }else{ |
| @@ -227,23 +227,21 @@ | ||
| 227 | 227 | } |
| 228 | 228 | |
| 229 | 229 | /* |
| 230 | 230 | ** Implements the "byyear" and "bymonth" reports for /reports. |
| 231 | 231 | ** 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. | |
| 232 | +** else the "byyear" report. If zUserName is not NULL then the report is | |
| 233 | +** restricted to events created by the named user account. | |
| 235 | 234 | */ |
| 236 | 235 | static void stats_report_by_month_year(char includeMonth, |
| 237 | 236 | char includeWeeks, |
| 238 | 237 | const char *zUserName){ |
| 239 | 238 | Stmt query = empty_Stmt; |
| 240 | 239 | int nRowNumber = 0; /* current TR number */ |
| 241 | 240 | int nEventTotal = 0; /* Total event count */ |
| 242 | 241 | int rowClass = 0; /* counter for alternating |
| 243 | 242 | row colors */ |
| 244 | - Blob sql = empty_blob; /* SQL */ | |
| 245 | 243 | const char *zTimeLabel = includeMonth ? "Year/Month" : "Year"; |
| 246 | 244 | char zPrevYear[5] = {0}; /* For keeping track of when |
| 247 | 245 | we change years while looping */ |
| 248 | 246 | int nEventsPerYear = 0; /* Total event count for the |
| 249 | 247 | current year */ |
| @@ -252,39 +250,39 @@ | ||
| 252 | 250 | Blob header = empty_blob; /* Page header text */ |
| 253 | 251 | int nMaxEvents = 1; /* for calculating length of graph |
| 254 | 252 | bars. */ |
| 255 | 253 | int iterations = 0; /* number of weeks/months we iterate |
| 256 | 254 | over */ |
| 255 | + Blob userFilter = empty_blob; /* Optional user=johndoe query string */ | |
| 257 | 256 | 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> | |
| 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" | |
| 267 | + " WHERE ifnull(user=%Q,1)" | |
| 268 | + " GROUP BY timeframe" | |
| 269 | + " ORDER BY timeframe DESC", | |
| 270 | + includeMonth ? 7 : 4, zUserName); | |
| 271 | + @ <h1>Timeline Events (%s(stats_report_label_for_type())) | |
| 272 | + @ by year%s(includeMonth ? "/month" : "") | |
| 273 | + if( zUserName ){ | |
| 274 | + @ for user %h(zUserName) | |
| 275 | + } | |
| 276 | + @ </h1> | |
| 278 | 277 | @ <table class='statistics-report-table-events' border='0' cellpadding='2' |
| 279 | 278 | @ cellspacing='0' id='statsTable'> |
| 280 | 279 | @ <thead> |
| 281 | 280 | @ <th>%s(zTimeLabel)</th> |
| 282 | 281 | @ <th>Events</th> |
| 283 | 282 | @ <th width='90%%'><!-- relative commits graph --></th> |
| 284 | 283 | @ </thead><tbody> |
| 285 | - blob_reset(&header); | |
| 286 | 284 | /* |
| 287 | 285 | Run the query twice. The first time we calculate the maximum |
| 288 | 286 | number of events for a given row. Maybe someone with better SQL |
| 289 | 287 | Fu can re-implement this with a single query. |
| 290 | 288 | */ |
| @@ -335,18 +333,18 @@ | ||
| 335 | 333 | zTimeframe, nCount, |
| 336 | 334 | statsReportTimelineYFlag ); |
| 337 | 335 | /* Reminder: n=nCount is not actually correct for bymonth unless |
| 338 | 336 | that was the only user who caused events. |
| 339 | 337 | */ |
| 340 | - if( zUserName && *zUserName ){ | |
| 338 | + if( zUserName ){ | |
| 341 | 339 | cgi_printf("&u=%t", zUserName); |
| 342 | 340 | } |
| 343 | 341 | cgi_printf("' target='_new'>%s</a>",zTimeframe); |
| 344 | 342 | }else { |
| 345 | 343 | cgi_printf("<a href='?view=byweek&y=%s&type=%c", |
| 346 | 344 | zTimeframe, (char)statsReportType); |
| 347 | - if(zUserName && *zUserName){ | |
| 345 | + if( zUserName ){ | |
| 348 | 346 | cgi_printf("&u=%t", zUserName); |
| 349 | 347 | } |
| 350 | 348 | cgi_printf("'>%s</a>", zTimeframe); |
| 351 | 349 | } |
| 352 | 350 | @ </td><td>%d(nCount)</td> |
| @@ -465,30 +463,37 @@ | ||
| 465 | 463 | db_finalize(&query); |
| 466 | 464 | output_table_sorting_javascript("statsTable","tkx",2); |
| 467 | 465 | } |
| 468 | 466 | |
| 469 | 467 | /* |
| 470 | -** Implements the "byfile" view for /reports. | |
| 468 | +** Implements the "byfile" view for /reports. If zUserName is not NULL then the | |
| 469 | +** report is restricted to events created by the named user account. | |
| 471 | 470 | */ |
| 472 | -static void stats_report_by_file(){ | |
| 471 | +static void stats_report_by_file(const char *zUserName){ | |
| 473 | 472 | Stmt query; |
| 474 | 473 | int mxEvent = 1; /* max number of events across all rows */ |
| 475 | 474 | int nRowNumber = 0; |
| 476 | 475 | |
| 477 | 476 | db_multi_exec( |
| 478 | 477 | "CREATE TEMP TABLE statrep(filename, cnt);" |
| 479 | 478 | "INSERT INTO statrep(filename, cnt)" |
| 480 | 479 | " SELECT filename.name, count(distinct mlink.mid)" |
| 481 | - " FROM filename, mlink" | |
| 480 | + " FROM filename, mlink, event" | |
| 482 | 481 | " WHERE filename.fnid=mlink.fnid" |
| 483 | - " GROUP BY 1;" | |
| 482 | + " AND mlink.mid=event.objid" | |
| 483 | + " AND ifnull(ifnull(euser,user)=%Q,1)" | |
| 484 | + " GROUP BY 1", zUserName | |
| 484 | 485 | ); |
| 485 | 486 | db_prepare(&query, |
| 486 | 487 | "SELECT filename, cnt FROM statrep ORDER BY cnt DESC, filename /*sort*/" |
| 487 | 488 | ); |
| 488 | 489 | mxEvent = db_int(1, "SELECT max(cnt) FROM statrep"); |
| 489 | - @ <h1>Check-ins Per File</h1> | |
| 490 | + @ <h1>Check-ins Per File | |
| 491 | + if( zUserName ){ | |
| 492 | + @ for user %h(zUserName) | |
| 493 | + } | |
| 494 | + @ </h1> | |
| 490 | 495 | @ <table class='statistics-report-table-events' border='0' |
| 491 | 496 | @ cellpadding='2' cellspacing='0' id='statsTable'> |
| 492 | 497 | @ <thead><tr> |
| 493 | 498 | @ <th>File</th> |
| 494 | 499 | @ <th>Check-ins</th> |
| @@ -514,41 +519,51 @@ | ||
| 514 | 519 | db_finalize(&query); |
| 515 | 520 | output_table_sorting_javascript("statsTable","tNx",2); |
| 516 | 521 | } |
| 517 | 522 | |
| 518 | 523 | /* |
| 519 | -** Implements the "byweekday" view for /reports. | |
| 524 | +** Implements the "byweekday" view for /reports. If zUserName is not NULL then | |
| 525 | +** the report is restricted to events created by the named user account. | |
| 520 | 526 | */ |
| 521 | -static void stats_report_day_of_week(){ | |
| 527 | +static void stats_report_day_of_week(const char *zUserName){ | |
| 522 | 528 | Stmt query = empty_Stmt; |
| 523 | 529 | int nRowNumber = 0; /* current TR number */ |
| 524 | 530 | int nEventTotal = 0; /* Total event count */ |
| 525 | 531 | int rowClass = 0; /* counter for alternating |
| 526 | 532 | row colors */ |
| 527 | 533 | int nMaxEvents = 1; /* max number of events for |
| 528 | 534 | all rows. */ |
| 535 | + Blob userFilter = empty_blob; /* Optional user=johndoe query string */ | |
| 529 | 536 | static const char *const daysOfWeek[] = { |
| 530 | 537 | "Monday", "Tuesday", "Wednesday", "Thursday", |
| 531 | 538 | "Friday", "Saturday", "Sunday" |
| 532 | 539 | }; |
| 533 | 540 | |
| 534 | 541 | stats_report_init_view(); |
| 535 | - stats_report_event_types_menu("byweekday", NULL); | |
| 542 | + if( zUserName ){ | |
| 543 | + blob_appendf(&userFilter, "user=%s", zUserName); | |
| 544 | + } | |
| 545 | + stats_report_event_types_menu("byweekday", blob_str(&userFilter)); | |
| 536 | 546 | 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> | |
| 547 | + "SELECT cast(mtime %% 7 AS INTEGER) dow," | |
| 548 | + " COUNT(*) AS eventCount" | |
| 549 | + " FROM v_reports" | |
| 550 | + " WHERE ifnull(ifnull(euser,user)=%Q,1)" | |
| 551 | + " GROUP BY dow ORDER BY dow", zUserName); | |
| 552 | + @ <h1>Timeline Events (%h(stats_report_label_for_type())) by Day of the Week | |
| 553 | + if( zUserName ){ | |
| 554 | + @ for user %h(zUserName) | |
| 555 | + } | |
| 556 | + @ </h1> | |
| 543 | 557 | db_multi_exec( |
| 544 | 558 | "CREATE TEMP TABLE piechart(amt,label);" |
| 545 | 559 | "INSERT INTO piechart SELECT count(*), cast(mtime %% 7 AS INT) FROM v_reports" |
| 560 | + " WHERE ifnull(ifnull(euser,user)=%Q,1)" | |
| 546 | 561 | " GROUP BY 2 ORDER BY 2;" |
| 547 | 562 | "UPDATE piechart SET label = CASE label WHEN 0 THEN 'Monday' WHEN 1 THEN 'Tuesday'" |
| 548 | 563 | " WHEN 2 THEN 'Wednesday' WHEN 3 THEN 'Thursday' WHEN 4 THEN 'Friday'" |
| 549 | - " WHEN 5 THEN 'Saturday' ELSE 'Sunday' END;" | |
| 564 | + " WHEN 5 THEN 'Saturday' ELSE 'Sunday' END;", zUserName | |
| 550 | 565 | ); |
| 551 | 566 | if( db_int(0, "SELECT count(*) FROM piechart")>=2 ){ |
| 552 | 567 | @ <center><svg width=700 height=400> |
| 553 | 568 | piechart_render(700, 400, PIE_OTHER|PIE_PERCENT); |
| 554 | 569 | @ </svg></centre><hr/> |
| @@ -595,49 +610,47 @@ | ||
| 595 | 610 | |
| 596 | 611 | |
| 597 | 612 | /* |
| 598 | 613 | ** Helper for stats_report_by_month_year(), which generates a list of |
| 599 | 614 | ** week numbers. zTimeframe should be either a timeframe in the form YYYY |
| 600 | -** or YYYY-MM. | |
| 615 | +** or YYYY-MM. If zUserName is not NULL then the report is restricted to events | |
| 616 | +** created by the named user account. | |
| 601 | 617 | */ |
| 602 | 618 | static void stats_report_year_weeks(const char *zUserName){ |
| 603 | 619 | const char *zYear = P("y"); |
| 604 | 620 | int nYear = zYear ? strlen(zYear) : 0; |
| 605 | 621 | int i = 0; |
| 606 | 622 | Stmt qYears = empty_Stmt; |
| 607 | 623 | char *zDefaultYear = NULL; |
| 608 | - Blob sql = empty_blob; | |
| 609 | 624 | int nMaxEvents = 1; /* max number of events for |
| 610 | 625 | all rows. */ |
| 611 | 626 | int iterations = 0; /* # of active time periods. */ |
| 627 | + Blob urlParams = empty_blob; | |
| 612 | 628 | stats_report_init_view(); |
| 613 | 629 | if(4==nYear){ |
| 614 | - Blob urlParams = empty_blob; | |
| 615 | 630 | 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); | |
| 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(ifnull(euser,user)=%Q,1)" | |
| 642 | + " GROUP BY y ORDER BY y", zUserName); | |
| 630 | 643 | cgi_printf("Select year: "); |
| 631 | 644 | while( SQLITE_ROW == db_step(&qYears) ){ |
| 632 | 645 | const char *zT = db_column_text(&qYears, 0); |
| 633 | 646 | if( i++ ){ |
| 634 | 647 | cgi_printf(" "); |
| 635 | 648 | } |
| 636 | 649 | cgi_printf("<a href='?view=byweek&y=%s&type=%c", zT, |
| 637 | 650 | (char)statsReportType); |
| 638 | - if(zUserName && *zUserName){ | |
| 651 | + if( zUserName ){ | |
| 639 | 652 | cgi_printf("&user=%t",zUserName); |
| 640 | 653 | } |
| 641 | 654 | cgi_printf("'>%s</a>",zT); |
| 642 | 655 | } |
| 643 | 656 | db_finalize(&qYears); |
| @@ -649,39 +662,33 @@ | ||
| 649 | 662 | } |
| 650 | 663 | if(4 == nYear){ |
| 651 | 664 | Stmt stWeek = empty_Stmt; |
| 652 | 665 | int rowCount = 0; |
| 653 | 666 | 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); | |
| 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(ifnull(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> | |
| 672 | 681 | cgi_printf("<table class='statistics-report-table-events' " |
| 673 | 682 | "border='0' cellpadding='2' width='100%%' " |
| 674 | 683 | "cellspacing='0' id='statsTable'>"); |
| 675 | 684 | cgi_printf("<thead><tr>" |
| 676 | 685 | "<th>Week</th>" |
| 677 | 686 | "<th>Events</th>" |
| 678 | 687 | "<th width='90%%'><!-- relative commits graph --></th>" |
| 679 | 688 | "</tr></thead>" |
| 680 | 689 | "<tbody>"); |
| 681 | - db_prepare(&stWeek, "%s", blob_sql_text(&sql)); | |
| 682 | - blob_reset(&sql); | |
| 683 | 690 | while( SQLITE_ROW == db_step(&stWeek) ){ |
| 684 | 691 | const int nCount = db_column_int(&stWeek, 1); |
| 685 | 692 | if(nCount>nMaxEvents){ |
| 686 | 693 | nMaxEvents = nCount; |
| 687 | 694 | } |
| @@ -698,11 +705,11 @@ | ||
| 698 | 705 | total += nCount; |
| 699 | 706 | cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); |
| 700 | 707 | cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s", |
| 701 | 708 | zYear, zWeek, nCount, |
| 702 | 709 | statsReportTimelineYFlag); |
| 703 | - if(zUserName && *zUserName){ | |
| 710 | + if( zUserName ){ | |
| 704 | 711 | cgi_printf("&u=%t",zUserName); |
| 705 | 712 | } |
| 706 | 713 | cgi_printf("'>%s</a></td>",zWeek); |
| 707 | 714 | |
| 708 | 715 | cgi_printf("<td>%d</td>",nCount); |
| @@ -753,12 +760,15 @@ | ||
| 753 | 760 | const char *zUserName = P("user"); |
| 754 | 761 | |
| 755 | 762 | login_check_credentials(); |
| 756 | 763 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 757 | 764 | if(!zUserName) zUserName = P("u"); |
| 765 | + if(zUserName && !*zUserName){ | |
| 766 | + zUserName = NULL; | |
| 767 | + } | |
| 758 | 768 | url_initialize(&url, "reports"); |
| 759 | - if(zUserName && *zUserName){ | |
| 769 | + if(zUserName){ | |
| 760 | 770 | url_add_parameter(&url,"user", zUserName); |
| 761 | 771 | statrep_submenu(&url, "(Remove User Flag)", "view", zView, "user"); |
| 762 | 772 | } |
| 763 | 773 | statrep_submenu(&url, "By Year", "view", "byyear", 0); |
| 764 | 774 | statrep_submenu(&url, "By Month", "view", "bymonth", 0); |
| @@ -776,13 +786,13 @@ | ||
| 776 | 786 | }else if(0==fossil_strcmp(zView,"byweek")){ |
| 777 | 787 | stats_report_year_weeks(zUserName); |
| 778 | 788 | }else if(0==fossil_strcmp(zView,"byuser")){ |
| 779 | 789 | stats_report_by_user(); |
| 780 | 790 | }else if(0==fossil_strcmp(zView,"byweekday")){ |
| 781 | - stats_report_day_of_week(); | |
| 791 | + stats_report_day_of_week(zUserName); | |
| 782 | 792 | }else if(0==fossil_strcmp(zView,"byfile")){ |
| 783 | - stats_report_by_file(); | |
| 793 | + stats_report_by_file(zUserName); | |
| 784 | 794 | }else{ |
| 785 | 795 | @ <h1>Activity Reports:</h1> |
| 786 | 796 | @ <ul> |
| 787 | 797 | @ <li>%z(href("?view=byyear"))Events by year</a></li> |
| 788 | 798 | @ <li>%z(href("?view=bymonth"))Events by month</a></li> |
| 789 | 799 |
| --- src/statrep.c | |
| +++ src/statrep.c | |
| @@ -157,11 +157,11 @@ | |
| 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{ |
| @@ -227,23 +227,21 @@ | |
| 227 | } |
| 228 | |
| 229 | /* |
| 230 | ** Implements the "byyear" and "bymonth" reports for /reports. |
| 231 | ** 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. |
| 235 | */ |
| 236 | static void stats_report_by_month_year(char includeMonth, |
| 237 | char includeWeeks, |
| 238 | const char *zUserName){ |
| 239 | Stmt query = empty_Stmt; |
| 240 | int nRowNumber = 0; /* current TR number */ |
| 241 | int nEventTotal = 0; /* Total event count */ |
| 242 | int rowClass = 0; /* counter for alternating |
| 243 | row colors */ |
| 244 | Blob sql = empty_blob; /* SQL */ |
| 245 | const char *zTimeLabel = includeMonth ? "Year/Month" : "Year"; |
| 246 | char zPrevYear[5] = {0}; /* For keeping track of when |
| 247 | we change years while looping */ |
| 248 | int nEventsPerYear = 0; /* Total event count for the |
| 249 | current year */ |
| @@ -252,39 +250,39 @@ | |
| 252 | Blob header = empty_blob; /* Page header text */ |
| 253 | int nMaxEvents = 1; /* for calculating length of graph |
| 254 | bars. */ |
| 255 | int iterations = 0; /* number of weeks/months we iterate |
| 256 | over */ |
| 257 | 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> |
| 278 | @ <table class='statistics-report-table-events' border='0' cellpadding='2' |
| 279 | @ cellspacing='0' id='statsTable'> |
| 280 | @ <thead> |
| 281 | @ <th>%s(zTimeLabel)</th> |
| 282 | @ <th>Events</th> |
| 283 | @ <th width='90%%'><!-- relative commits graph --></th> |
| 284 | @ </thead><tbody> |
| 285 | blob_reset(&header); |
| 286 | /* |
| 287 | Run the query twice. The first time we calculate the maximum |
| 288 | number of events for a given row. Maybe someone with better SQL |
| 289 | Fu can re-implement this with a single query. |
| 290 | */ |
| @@ -335,18 +333,18 @@ | |
| 335 | zTimeframe, nCount, |
| 336 | statsReportTimelineYFlag ); |
| 337 | /* Reminder: n=nCount is not actually correct for bymonth unless |
| 338 | that was the only user who caused events. |
| 339 | */ |
| 340 | if( zUserName && *zUserName ){ |
| 341 | cgi_printf("&u=%t", zUserName); |
| 342 | } |
| 343 | cgi_printf("' target='_new'>%s</a>",zTimeframe); |
| 344 | }else { |
| 345 | cgi_printf("<a href='?view=byweek&y=%s&type=%c", |
| 346 | zTimeframe, (char)statsReportType); |
| 347 | if(zUserName && *zUserName){ |
| 348 | cgi_printf("&u=%t", zUserName); |
| 349 | } |
| 350 | cgi_printf("'>%s</a>", zTimeframe); |
| 351 | } |
| 352 | @ </td><td>%d(nCount)</td> |
| @@ -465,30 +463,37 @@ | |
| 465 | db_finalize(&query); |
| 466 | output_table_sorting_javascript("statsTable","tkx",2); |
| 467 | } |
| 468 | |
| 469 | /* |
| 470 | ** Implements the "byfile" view for /reports. |
| 471 | */ |
| 472 | static void stats_report_by_file(){ |
| 473 | Stmt query; |
| 474 | int mxEvent = 1; /* max number of events across all rows */ |
| 475 | int nRowNumber = 0; |
| 476 | |
| 477 | db_multi_exec( |
| 478 | "CREATE TEMP TABLE statrep(filename, cnt);" |
| 479 | "INSERT INTO statrep(filename, cnt)" |
| 480 | " SELECT filename.name, count(distinct mlink.mid)" |
| 481 | " FROM filename, mlink" |
| 482 | " WHERE filename.fnid=mlink.fnid" |
| 483 | " GROUP BY 1;" |
| 484 | ); |
| 485 | db_prepare(&query, |
| 486 | "SELECT filename, cnt FROM statrep ORDER BY cnt DESC, filename /*sort*/" |
| 487 | ); |
| 488 | mxEvent = db_int(1, "SELECT max(cnt) FROM statrep"); |
| 489 | @ <h1>Check-ins Per File</h1> |
| 490 | @ <table class='statistics-report-table-events' border='0' |
| 491 | @ cellpadding='2' cellspacing='0' id='statsTable'> |
| 492 | @ <thead><tr> |
| 493 | @ <th>File</th> |
| 494 | @ <th>Check-ins</th> |
| @@ -514,41 +519,51 @@ | |
| 514 | db_finalize(&query); |
| 515 | output_table_sorting_javascript("statsTable","tNx",2); |
| 516 | } |
| 517 | |
| 518 | /* |
| 519 | ** Implements the "byweekday" view for /reports. |
| 520 | */ |
| 521 | static void stats_report_day_of_week(){ |
| 522 | Stmt query = empty_Stmt; |
| 523 | int nRowNumber = 0; /* current TR number */ |
| 524 | int nEventTotal = 0; /* Total event count */ |
| 525 | int rowClass = 0; /* counter for alternating |
| 526 | row colors */ |
| 527 | int nMaxEvents = 1; /* max number of events for |
| 528 | all rows. */ |
| 529 | static const char *const daysOfWeek[] = { |
| 530 | "Monday", "Tuesday", "Wednesday", "Thursday", |
| 531 | "Friday", "Saturday", "Sunday" |
| 532 | }; |
| 533 | |
| 534 | stats_report_init_view(); |
| 535 | stats_report_event_types_menu("byweekday", NULL); |
| 536 | 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> |
| 543 | db_multi_exec( |
| 544 | "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;" |
| 550 | ); |
| 551 | if( db_int(0, "SELECT count(*) FROM piechart")>=2 ){ |
| 552 | @ <center><svg width=700 height=400> |
| 553 | piechart_render(700, 400, PIE_OTHER|PIE_PERCENT); |
| 554 | @ </svg></centre><hr/> |
| @@ -595,49 +610,47 @@ | |
| 595 | |
| 596 | |
| 597 | /* |
| 598 | ** Helper for stats_report_by_month_year(), which generates a list of |
| 599 | ** week numbers. zTimeframe should be either a timeframe in the form YYYY |
| 600 | ** or YYYY-MM. |
| 601 | */ |
| 602 | static void stats_report_year_weeks(const char *zUserName){ |
| 603 | const char *zYear = P("y"); |
| 604 | int nYear = zYear ? strlen(zYear) : 0; |
| 605 | int i = 0; |
| 606 | Stmt qYears = empty_Stmt; |
| 607 | char *zDefaultYear = NULL; |
| 608 | Blob sql = empty_blob; |
| 609 | int nMaxEvents = 1; /* max number of events for |
| 610 | all rows. */ |
| 611 | 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); |
| @@ -649,39 +662,33 @@ | |
| 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 | } |
| @@ -698,11 +705,11 @@ | |
| 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); |
| @@ -753,12 +760,15 @@ | |
| 753 | const char *zUserName = P("user"); |
| 754 | |
| 755 | login_check_credentials(); |
| 756 | 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); |
| @@ -776,13 +786,13 @@ | |
| 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 |
| --- src/statrep.c | |
| +++ src/statrep.c | |
| @@ -157,11 +157,11 @@ | |
| 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{ |
| @@ -227,23 +227,21 @@ | |
| 227 | } |
| 228 | |
| 229 | /* |
| 230 | ** Implements the "byyear" and "bymonth" reports for /reports. |
| 231 | ** If includeMonth is true then it generates the "bymonth" report, |
| 232 | ** else the "byyear" report. If zUserName is not NULL then the report is |
| 233 | ** restricted to events created by the named user account. |
| 234 | */ |
| 235 | static void stats_report_by_month_year(char includeMonth, |
| 236 | char includeWeeks, |
| 237 | const char *zUserName){ |
| 238 | Stmt query = empty_Stmt; |
| 239 | int nRowNumber = 0; /* current TR number */ |
| 240 | int nEventTotal = 0; /* Total event count */ |
| 241 | int rowClass = 0; /* counter for alternating |
| 242 | row colors */ |
| 243 | const char *zTimeLabel = includeMonth ? "Year/Month" : "Year"; |
| 244 | char zPrevYear[5] = {0}; /* For keeping track of when |
| 245 | we change years while looping */ |
| 246 | int nEventsPerYear = 0; /* Total event count for the |
| 247 | current year */ |
| @@ -252,39 +250,39 @@ | |
| 250 | Blob header = empty_blob; /* Page header text */ |
| 251 | int nMaxEvents = 1; /* for calculating length of graph |
| 252 | bars. */ |
| 253 | int iterations = 0; /* number of weeks/months we iterate |
| 254 | over */ |
| 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" |
| 267 | " WHERE ifnull(user=%Q,1)" |
| 268 | " GROUP BY timeframe" |
| 269 | " ORDER BY timeframe DESC", |
| 270 | includeMonth ? 7 : 4, zUserName); |
| 271 | @ <h1>Timeline Events (%s(stats_report_label_for_type())) |
| 272 | @ by year%s(includeMonth ? "/month" : "") |
| 273 | if( zUserName ){ |
| 274 | @ for user %h(zUserName) |
| 275 | } |
| 276 | @ </h1> |
| 277 | @ <table class='statistics-report-table-events' border='0' cellpadding='2' |
| 278 | @ cellspacing='0' id='statsTable'> |
| 279 | @ <thead> |
| 280 | @ <th>%s(zTimeLabel)</th> |
| 281 | @ <th>Events</th> |
| 282 | @ <th width='90%%'><!-- relative commits graph --></th> |
| 283 | @ </thead><tbody> |
| 284 | /* |
| 285 | Run the query twice. The first time we calculate the maximum |
| 286 | number of events for a given row. Maybe someone with better SQL |
| 287 | Fu can re-implement this with a single query. |
| 288 | */ |
| @@ -335,18 +333,18 @@ | |
| 333 | zTimeframe, nCount, |
| 334 | statsReportTimelineYFlag ); |
| 335 | /* Reminder: n=nCount is not actually correct for bymonth unless |
| 336 | that was the only user who caused events. |
| 337 | */ |
| 338 | if( zUserName ){ |
| 339 | cgi_printf("&u=%t", zUserName); |
| 340 | } |
| 341 | cgi_printf("' target='_new'>%s</a>",zTimeframe); |
| 342 | }else { |
| 343 | cgi_printf("<a href='?view=byweek&y=%s&type=%c", |
| 344 | zTimeframe, (char)statsReportType); |
| 345 | if( zUserName ){ |
| 346 | cgi_printf("&u=%t", zUserName); |
| 347 | } |
| 348 | cgi_printf("'>%s</a>", zTimeframe); |
| 349 | } |
| 350 | @ </td><td>%d(nCount)</td> |
| @@ -465,30 +463,37 @@ | |
| 463 | db_finalize(&query); |
| 464 | output_table_sorting_javascript("statsTable","tkx",2); |
| 465 | } |
| 466 | |
| 467 | /* |
| 468 | ** Implements the "byfile" view for /reports. If zUserName is not NULL then the |
| 469 | ** report is restricted to events created by the named user account. |
| 470 | */ |
| 471 | static void stats_report_by_file(const char *zUserName){ |
| 472 | Stmt query; |
| 473 | int mxEvent = 1; /* max number of events across all rows */ |
| 474 | int nRowNumber = 0; |
| 475 | |
| 476 | db_multi_exec( |
| 477 | "CREATE TEMP TABLE statrep(filename, cnt);" |
| 478 | "INSERT INTO statrep(filename, cnt)" |
| 479 | " SELECT filename.name, count(distinct mlink.mid)" |
| 480 | " FROM filename, mlink, event" |
| 481 | " WHERE filename.fnid=mlink.fnid" |
| 482 | " AND mlink.mid=event.objid" |
| 483 | " AND ifnull(ifnull(euser,user)=%Q,1)" |
| 484 | " GROUP BY 1", zUserName |
| 485 | ); |
| 486 | db_prepare(&query, |
| 487 | "SELECT filename, cnt FROM statrep ORDER BY cnt DESC, filename /*sort*/" |
| 488 | ); |
| 489 | mxEvent = db_int(1, "SELECT max(cnt) FROM statrep"); |
| 490 | @ <h1>Check-ins Per File |
| 491 | if( zUserName ){ |
| 492 | @ for user %h(zUserName) |
| 493 | } |
| 494 | @ </h1> |
| 495 | @ <table class='statistics-report-table-events' border='0' |
| 496 | @ cellpadding='2' cellspacing='0' id='statsTable'> |
| 497 | @ <thead><tr> |
| 498 | @ <th>File</th> |
| 499 | @ <th>Check-ins</th> |
| @@ -514,41 +519,51 @@ | |
| 519 | db_finalize(&query); |
| 520 | output_table_sorting_javascript("statsTable","tNx",2); |
| 521 | } |
| 522 | |
| 523 | /* |
| 524 | ** Implements the "byweekday" view for /reports. If zUserName is not NULL then |
| 525 | ** the report is restricted to events created by the named user account. |
| 526 | */ |
| 527 | static void stats_report_day_of_week(const char *zUserName){ |
| 528 | Stmt query = empty_Stmt; |
| 529 | int nRowNumber = 0; /* current TR number */ |
| 530 | int nEventTotal = 0; /* Total event count */ |
| 531 | int rowClass = 0; /* counter for alternating |
| 532 | row colors */ |
| 533 | int nMaxEvents = 1; /* max number of events for |
| 534 | all rows. */ |
| 535 | Blob userFilter = empty_blob; /* Optional user=johndoe query string */ |
| 536 | static const char *const daysOfWeek[] = { |
| 537 | "Monday", "Tuesday", "Wednesday", "Thursday", |
| 538 | "Friday", "Saturday", "Sunday" |
| 539 | }; |
| 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(ifnull(euser,user)=%Q,1)" |
| 551 | " GROUP BY dow ORDER BY dow", zUserName); |
| 552 | @ <h1>Timeline Events (%h(stats_report_label_for_type())) by Day of the Week |
| 553 | if( zUserName ){ |
| 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(ifnull(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/> |
| @@ -595,49 +610,47 @@ | |
| 610 | |
| 611 | |
| 612 | /* |
| 613 | ** Helper for stats_report_by_month_year(), which generates a list of |
| 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(ifnull(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); |
| @@ -649,39 +662,33 @@ | |
| 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(ifnull(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 | } |
| @@ -698,11 +705,11 @@ | |
| 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); |
| @@ -753,12 +760,15 @@ | |
| 760 | const char *zUserName = P("user"); |
| 761 | |
| 762 | login_check_credentials(); |
| 763 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 764 | if(!zUserName) zUserName = P("u"); |
| 765 | if(zUserName && !*zUserName){ |
| 766 | zUserName = NULL; |
| 767 | } |
| 768 | url_initialize(&url, "reports"); |
| 769 | if(zUserName){ |
| 770 | url_add_parameter(&url,"user", zUserName); |
| 771 | statrep_submenu(&url, "(Remove User Flag)", "view", zView, "user"); |
| 772 | } |
| 773 | statrep_submenu(&url, "By Year", "view", "byyear", 0); |
| 774 | statrep_submenu(&url, "By Month", "view", "bymonth", 0); |
| @@ -776,13 +786,13 @@ | |
| 786 | }else if(0==fossil_strcmp(zView,"byweek")){ |
| 787 | stats_report_year_weeks(zUserName); |
| 788 | }else if(0==fossil_strcmp(zView,"byuser")){ |
| 789 | stats_report_by_user(); |
| 790 | }else if(0==fossil_strcmp(zView,"byweekday")){ |
| 791 | stats_report_day_of_week(zUserName); |
| 792 | }else if(0==fossil_strcmp(zView,"byfile")){ |
| 793 | stats_report_by_file(zUserName); |
| 794 | }else{ |
| 795 | @ <h1>Activity Reports:</h1> |
| 796 | @ <ul> |
| 797 | @ <li>%z(href("?view=byyear"))Events by year</a></li> |
| 798 | @ <li>%z(href("?view=bymonth"))Events by month</a></li> |
| 799 |
+2
| --- www/changes.wiki | ||
| +++ www/changes.wiki | ||
| @@ -40,10 +40,12 @@ | ||
| 40 | 40 | symlink. Additionally show the UUID for files whose types have changed |
| 41 | 41 | without changing contents or symlink target. |
| 42 | 42 | * Have [/help?cmd=changes|fossil changes] and |
| 43 | 43 | [/help?cmd=status|fossil status] report when executable or symlink status |
| 44 | 44 | changes on otherwise unmodified files. |
| 45 | + * Permit filtering weekday and file [/help?cmd=/reports|reports] by user. | |
| 46 | + Also ensure the user parameter is preserved when changing types. | |
| 45 | 47 | |
| 46 | 48 | <h2>Changes for Version 1.32 (2015-03-14)</h2> |
| 47 | 49 | * When creating a new repository using [/help?cmd=init|fossil init], ensure |
| 48 | 50 | that the new repository is fully compatible with historical versions of |
| 49 | 51 | Fossil by having a valid manifest as RID 1. |
| 50 | 52 |
| --- www/changes.wiki | |
| +++ www/changes.wiki | |
| @@ -40,10 +40,12 @@ | |
| 40 | symlink. Additionally show the UUID for files whose types have changed |
| 41 | without changing contents or symlink target. |
| 42 | * Have [/help?cmd=changes|fossil changes] and |
| 43 | [/help?cmd=status|fossil status] report when executable or symlink status |
| 44 | changes on otherwise unmodified files. |
| 45 | |
| 46 | <h2>Changes for Version 1.32 (2015-03-14)</h2> |
| 47 | * When creating a new repository using [/help?cmd=init|fossil init], ensure |
| 48 | that the new repository is fully compatible with historical versions of |
| 49 | Fossil by having a valid manifest as RID 1. |
| 50 |
| --- www/changes.wiki | |
| +++ www/changes.wiki | |
| @@ -40,10 +40,12 @@ | |
| 40 | symlink. Additionally show the UUID for files whose types have changed |
| 41 | without changing contents or symlink target. |
| 42 | * Have [/help?cmd=changes|fossil changes] and |
| 43 | [/help?cmd=status|fossil status] report when executable or symlink status |
| 44 | changes on otherwise unmodified files. |
| 45 | * Permit filtering weekday and file [/help?cmd=/reports|reports] by user. |
| 46 | Also ensure the user parameter is preserved when changing types. |
| 47 | |
| 48 | <h2>Changes for Version 1.32 (2015-03-14)</h2> |
| 49 | * When creating a new repository using [/help?cmd=init|fossil init], ensure |
| 50 | that the new repository is fully compatible with historical versions of |
| 51 | Fossil by having a valid manifest as RID 1. |
| 52 |