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.

andygoth 2015-05-18 02:22 UTC trunk
Commit 60018f9d8ab75b675b60274d387a885b449c5cc7
2 files changed +95 -85 +2
+95 -85
--- src/statrep.c
+++ src/statrep.c
@@ -157,11 +157,11 @@
157157
char *zTop;
158158
if(zParam && !*zParam){
159159
zParam = NULL;
160160
}
161161
zTop = mprintf("%s/reports?view=%s%s%s", g.zTop, zCurrentViewName,
162
- zParam ? "&" : "", zParam);
162
+ zParam&&*zParam ? "&" : "", zParam);
163163
cgi_printf("<div>");
164164
cgi_printf("<span>Types:</span> ");
165165
if('*' == statsReportType){
166166
cgi_printf(" <strong>all</strong>", zTop);
167167
}else{
@@ -227,23 +227,21 @@
227227
}
228228
229229
/*
230230
** Implements the "byyear" and "bymonth" reports for /reports.
231231
** 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.
235234
*/
236235
static void stats_report_by_month_year(char includeMonth,
237236
char includeWeeks,
238237
const char *zUserName){
239238
Stmt query = empty_Stmt;
240239
int nRowNumber = 0; /* current TR number */
241240
int nEventTotal = 0; /* Total event count */
242241
int rowClass = 0; /* counter for alternating
243242
row colors */
244
- Blob sql = empty_blob; /* SQL */
245243
const char *zTimeLabel = includeMonth ? "Year/Month" : "Year";
246244
char zPrevYear[5] = {0}; /* For keeping track of when
247245
we change years while looping */
248246
int nEventsPerYear = 0; /* Total event count for the
249247
current year */
@@ -252,39 +250,39 @@
252250
Blob header = empty_blob; /* Page header text */
253251
int nMaxEvents = 1; /* for calculating length of graph
254252
bars. */
255253
int iterations = 0; /* number of weeks/months we iterate
256254
over */
255
+ Blob userFilter = empty_blob; /* Optional user=johndoe query string */
257256
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>
278277
@ <table class='statistics-report-table-events' border='0' cellpadding='2'
279278
@ cellspacing='0' id='statsTable'>
280279
@ <thead>
281280
@ <th>%s(zTimeLabel)</th>
282281
@ <th>Events</th>
283282
@ <th width='90%%'><!-- relative commits graph --></th>
284283
@ </thead><tbody>
285
- blob_reset(&header);
286284
/*
287285
Run the query twice. The first time we calculate the maximum
288286
number of events for a given row. Maybe someone with better SQL
289287
Fu can re-implement this with a single query.
290288
*/
@@ -335,18 +333,18 @@
335333
zTimeframe, nCount,
336334
statsReportTimelineYFlag );
337335
/* Reminder: n=nCount is not actually correct for bymonth unless
338336
that was the only user who caused events.
339337
*/
340
- if( zUserName && *zUserName ){
338
+ if( zUserName ){
341339
cgi_printf("&u=%t", zUserName);
342340
}
343341
cgi_printf("' target='_new'>%s</a>",zTimeframe);
344342
}else {
345343
cgi_printf("<a href='?view=byweek&y=%s&type=%c",
346344
zTimeframe, (char)statsReportType);
347
- if(zUserName && *zUserName){
345
+ if( zUserName ){
348346
cgi_printf("&u=%t", zUserName);
349347
}
350348
cgi_printf("'>%s</a>", zTimeframe);
351349
}
352350
@ </td><td>%d(nCount)</td>
@@ -465,30 +463,37 @@
465463
db_finalize(&query);
466464
output_table_sorting_javascript("statsTable","tkx",2);
467465
}
468466
469467
/*
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.
471470
*/
472
-static void stats_report_by_file(){
471
+static void stats_report_by_file(const char *zUserName){
473472
Stmt query;
474473
int mxEvent = 1; /* max number of events across all rows */
475474
int nRowNumber = 0;
476475
477476
db_multi_exec(
478477
"CREATE TEMP TABLE statrep(filename, cnt);"
479478
"INSERT INTO statrep(filename, cnt)"
480479
" SELECT filename.name, count(distinct mlink.mid)"
481
- " FROM filename, mlink"
480
+ " FROM filename, mlink, event"
482481
" 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
484485
);
485486
db_prepare(&query,
486487
"SELECT filename, cnt FROM statrep ORDER BY cnt DESC, filename /*sort*/"
487488
);
488489
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>
490495
@ <table class='statistics-report-table-events' border='0'
491496
@ cellpadding='2' cellspacing='0' id='statsTable'>
492497
@ <thead><tr>
493498
@ <th>File</th>
494499
@ <th>Check-ins</th>
@@ -514,41 +519,51 @@
514519
db_finalize(&query);
515520
output_table_sorting_javascript("statsTable","tNx",2);
516521
}
517522
518523
/*
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.
520526
*/
521
-static void stats_report_day_of_week(){
527
+static void stats_report_day_of_week(const char *zUserName){
522528
Stmt query = empty_Stmt;
523529
int nRowNumber = 0; /* current TR number */
524530
int nEventTotal = 0; /* Total event count */
525531
int rowClass = 0; /* counter for alternating
526532
row colors */
527533
int nMaxEvents = 1; /* max number of events for
528534
all rows. */
535
+ Blob userFilter = empty_blob; /* Optional user=johndoe query string */
529536
static const char *const daysOfWeek[] = {
530537
"Monday", "Tuesday", "Wednesday", "Thursday",
531538
"Friday", "Saturday", "Sunday"
532539
};
533540
534541
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));
536546
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>
543557
db_multi_exec(
544558
"CREATE TEMP TABLE piechart(amt,label);"
545559
"INSERT INTO piechart SELECT count(*), cast(mtime %% 7 AS INT) FROM v_reports"
560
+ " WHERE ifnull(ifnull(euser,user)=%Q,1)"
546561
" GROUP BY 2 ORDER BY 2;"
547562
"UPDATE piechart SET label = CASE label WHEN 0 THEN 'Monday' WHEN 1 THEN 'Tuesday'"
548563
" 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
550565
);
551566
if( db_int(0, "SELECT count(*) FROM piechart")>=2 ){
552567
@ <center><svg width=700 height=400>
553568
piechart_render(700, 400, PIE_OTHER|PIE_PERCENT);
554569
@ </svg></centre><hr/>
@@ -595,49 +610,47 @@
595610
596611
597612
/*
598613
** Helper for stats_report_by_month_year(), which generates a list of
599614
** 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.
601617
*/
602618
static void stats_report_year_weeks(const char *zUserName){
603619
const char *zYear = P("y");
604620
int nYear = zYear ? strlen(zYear) : 0;
605621
int i = 0;
606622
Stmt qYears = empty_Stmt;
607623
char *zDefaultYear = NULL;
608
- Blob sql = empty_blob;
609624
int nMaxEvents = 1; /* max number of events for
610625
all rows. */
611626
int iterations = 0; /* # of active time periods. */
627
+ Blob urlParams = empty_blob;
612628
stats_report_init_view();
613629
if(4==nYear){
614
- Blob urlParams = empty_blob;
615630
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);
630643
cgi_printf("Select year: ");
631644
while( SQLITE_ROW == db_step(&qYears) ){
632645
const char *zT = db_column_text(&qYears, 0);
633646
if( i++ ){
634647
cgi_printf(" ");
635648
}
636649
cgi_printf("<a href='?view=byweek&y=%s&type=%c", zT,
637650
(char)statsReportType);
638
- if(zUserName && *zUserName){
651
+ if( zUserName ){
639652
cgi_printf("&user=%t",zUserName);
640653
}
641654
cgi_printf("'>%s</a>",zT);
642655
}
643656
db_finalize(&qYears);
@@ -649,39 +662,33 @@
649662
}
650663
if(4 == nYear){
651664
Stmt stWeek = empty_Stmt;
652665
int rowCount = 0;
653666
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>
672681
cgi_printf("<table class='statistics-report-table-events' "
673682
"border='0' cellpadding='2' width='100%%' "
674683
"cellspacing='0' id='statsTable'>");
675684
cgi_printf("<thead><tr>"
676685
"<th>Week</th>"
677686
"<th>Events</th>"
678687
"<th width='90%%'><!-- relative commits graph --></th>"
679688
"</tr></thead>"
680689
"<tbody>");
681
- db_prepare(&stWeek, "%s", blob_sql_text(&sql));
682
- blob_reset(&sql);
683690
while( SQLITE_ROW == db_step(&stWeek) ){
684691
const int nCount = db_column_int(&stWeek, 1);
685692
if(nCount>nMaxEvents){
686693
nMaxEvents = nCount;
687694
}
@@ -698,11 +705,11 @@
698705
total += nCount;
699706
cgi_printf("<tr class='row%d'>", ++rowCount % 2 );
700707
cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s",
701708
zYear, zWeek, nCount,
702709
statsReportTimelineYFlag);
703
- if(zUserName && *zUserName){
710
+ if( zUserName ){
704711
cgi_printf("&u=%t",zUserName);
705712
}
706713
cgi_printf("'>%s</a></td>",zWeek);
707714
708715
cgi_printf("<td>%d</td>",nCount);
@@ -753,12 +760,15 @@
753760
const char *zUserName = P("user");
754761
755762
login_check_credentials();
756763
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
757764
if(!zUserName) zUserName = P("u");
765
+ if(zUserName && !*zUserName){
766
+ zUserName = NULL;
767
+ }
758768
url_initialize(&url, "reports");
759
- if(zUserName && *zUserName){
769
+ if(zUserName){
760770
url_add_parameter(&url,"user", zUserName);
761771
statrep_submenu(&url, "(Remove User Flag)", "view", zView, "user");
762772
}
763773
statrep_submenu(&url, "By Year", "view", "byyear", 0);
764774
statrep_submenu(&url, "By Month", "view", "bymonth", 0);
@@ -776,13 +786,13 @@
776786
}else if(0==fossil_strcmp(zView,"byweek")){
777787
stats_report_year_weeks(zUserName);
778788
}else if(0==fossil_strcmp(zView,"byuser")){
779789
stats_report_by_user();
780790
}else if(0==fossil_strcmp(zView,"byweekday")){
781
- stats_report_day_of_week();
791
+ stats_report_day_of_week(zUserName);
782792
}else if(0==fossil_strcmp(zView,"byfile")){
783
- stats_report_by_file();
793
+ stats_report_by_file(zUserName);
784794
}else{
785795
@ <h1>Activity Reports:</h1>
786796
@ <ul>
787797
@ <li>%z(href("?view=byyear"))Events by year</a></li>
788798
@ <li>%z(href("?view=bymonth"))Events by month</a></li>
789799
--- 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
--- www/changes.wiki
+++ www/changes.wiki
@@ -40,10 +40,12 @@
4040
symlink. Additionally show the UUID for files whose types have changed
4141
without changing contents or symlink target.
4242
* Have [/help?cmd=changes|fossil changes] and
4343
[/help?cmd=status|fossil status] report when executable or symlink status
4444
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.
4547
4648
<h2>Changes for Version 1.32 (2015-03-14)</h2>
4749
* When creating a new repository using [/help?cmd=init|fossil init], ensure
4850
that the new repository is fully compatible with historical versions of
4951
Fossil by having a valid manifest as RID 1.
5052
--- 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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button