Index: src/style.c
==================================================================
--- src/style.c
+++ src/style.c
@@ -1033,10 +1033,23 @@
@ padding: 0.1em 1em 0.1em 1em;
},
{ ".statistics-report-row-year",
"",
@ text-align: left;
+ },
+ { ".statistics-report-graph-line",
+ "for the /stats_report views",
+ @ background-color: #446979;
+ },
+ { ".statistics-report-week-number-label",
+ "for the /stats_report views",
+ @ text-align: right;
+ @ font-size: 0.8em;
+ },
+ { ".statistics-report-week-of-year-list",
+ "for the /stats_report views",
+ @ font-size: 0.8em;
},
{ "tr.row0",
"even table row color",
@ /* use default */
},
Index: src/timeline.c
==================================================================
--- src/timeline.c
+++ src/timeline.c
@@ -1032,10 +1032,11 @@
const char *zTagName = P("t"); /* Show events with this tag */
const char *zBrName = P("r"); /* Show events related to this tag */
const char *zSearch = P("s"); /* Search string */
const char *zUses = P("uf"); /* Only show checkins hold this file */
const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */
+ const char *zYearWeek = P("yw"); /* Show checkins for the given YYYY-WW (weak-of-year) */
int useDividers = P("nd")==0; /* Show dividers if "nd" is missing */
int renameOnly = P("namechng")!=0; /* Show only checkins that rename files */
int tagid; /* Tag ID */
int tmFlags; /* Timeline flags */
const char *zThisTag = 0; /* Suppress links to this tag */
@@ -1218,10 +1219,14 @@
}
if( zYearMonth ){
blob_appendf(&sql, " AND %Q=strftime('%%Y-%%m',event.mtime) ",
zYearMonth);
}
+ else if( zYearWeek ){
+ blob_appendf(&sql, " AND %Q=strftime('%%Y-%%W',event.mtime) ",
+ zYearWeek);
+ }
if( tagid>0 ){
blob_appendf(&sql,
"AND (EXISTS(SELECT 1 FROM tagxref"
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", tagid);
@@ -1350,10 +1355,12 @@
db_multi_exec("%s", blob_str(&sql));
n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
if( zYearMonth ){
blob_appendf(&desc, "%s events for %h", zEType, zYearMonth);
+ }else if( zYearWeek ){
+ blob_appendf(&desc, "%s events for year/week %h", zEType, zYearWeek);
}else if( zAfter==0 && zBefore==0 && zCirca==0 ){
blob_appendf(&desc, "%d most recent %ss", n, zEType);
}else{
blob_appendf(&desc, "%d %ss", n, zEType);
}
@@ -1829,20 +1836,48 @@
}
db_finalize(&q);
style_footer();
}
-
+/*
+** Helper for stats_report_by_month_year(), which generates a list of
+** week numbers. zTimeframe should be either a timeframe in the form YYYY
+** or YYYY-MM.
+*/
+static void stats_report_output_week_links(char const * zTimeframe){
+ Stmt stWeek = empty_Stmt;
+ char yearPart[5] = {0,0,0,0,0};
+ memcpy(yearPart, zTimeframe, 4);
+ db_prepare(&stWeek,
+ "SELECT DISTINCT strftime('%%W',mtime) AS wk, "
+ "count(*) AS n, "
+ "substr(date(mtime),1,%d) AS ym "
+ "FROM event "
+ "WHERE ym=%Q AND mtime < current_timestamp "
+ "GROUP BY wk ORDER BY wk",
+ strlen(zTimeframe),
+ zTimeframe);
+ while( SQLITE_ROW == db_step(&stWeek) ){
+ char const * zWeek = db_column_text(&stWeek,0);
+ int const nCount = db_column_int(&stWeek,1);
+ cgi_printf("%s",
+ g.zTop, yearPart, zWeek,
+ nCount, zWeek);
+ }
+ db_finalize(&stWeek);
+}
/*
** Implements the "byyear" and "bymonth" reports for /stats_report.
** If includeMonth is true then it generates the "bymonth" report,
** else the "byyear" report. If zUserName is not NULL and not empty
** then the report is restricted to events created by the named user
** account.
*/
static void stats_report_by_month_year(char includeMonth,
+ char includeWeeks,
char const * zUserName){
Stmt query = empty_Stmt;
int const nPixelsPerEvent = 1; /* for sizing the "graph" part */
int nRowNumber = 0; /* current TR number */
int nEventTotal = 0; /* Total event count */
@@ -1906,17 +1941,17 @@
memcpy(zPrevYear,zTimeframe,4);
rowClass = ++nRowNumber % 2;
@
@ %s(zPrevYear) |
@
- }
- }
- rowClass = ++nRowNumber % 2;
- nEventTotal += nCount;
- nEventsPerYear += nCount;
- @
- @
+ }
+ }
+ rowClass = ++nRowNumber % 2;
+ nEventTotal += nCount;
+ nEventsPerYear += nCount;
+ @ |
+ @
if(includeMonth){
cgi_printf("%s",zTimeframe);
}else {
- @ %s(zTimeframe)
+ cgi_printf("%s", zTimeframe);
}
@ | %d(nCount) |
@
@
@ |
@
+ if(includeWeeks){
+ /* This part works fine for months but it terribly slow (4.5s on my PC),
+ so it's only shown for by-year for now. Suggestions/patches for
+ a better/faster layout are welcomed. */
+ @
+ @ Week #: |
+ @
+ stats_report_output_week_links(zTimeframe);
+ @ |
+ }
/*
Potential improvement: calculate the min/max event counts and
use percent-based graph bars.
*/
@@ -2014,10 +2063,120 @@
@
db_finalize(&query);
output_table_sorting_javascript("statsTable","tnx");
}
+
+/*
+** Helper for stats_report_by_month_year(), which generates a list of
+** week numbers. zTimeframe should be either a timeframe in the form YYYY
+** or YYYY-MM.
+*/
+static void stats_report_year_weeks(char const * zUserName){
+ char const * zYear = P("y");
+ int nYear = zYear ? strlen(zYear) : 0;
+ int i = 0;
+ Stmt qYears = empty_Stmt;
+ char * zDefaultYear = NULL;
+ Blob sql = empty_blob;
+ cgi_printf("Select year: ");
+
+ blob_append(&sql,
+ "SELECT DISTINCT substr(date(mtime),1,4) AS y "
+ "FROM event WHERE 1 ", -1);
+ if(zUserName&&*zUserName){
+ blob_appendf(&sql,"AND user=%Q ", zUserName);
+ }
+ blob_append(&sql,"GROUP BY y ORDER BY y", -1);
+ db_prepare(&qYears, blob_str(&sql));
+ blob_reset(&sql);
+ while( SQLITE_ROW == db_step(&qYears) ){
+ char const * zT = db_column_text(&qYears, 0);
+ if( i++ ){
+ cgi_printf(" ");
+ }
+ cgi_printf("%s",zT);
+ }
+ db_finalize(&qYears);
+ cgi_printf("
");
+ if(!zYear || !*zYear){
+ zDefaultYear = db_text("????", "SELECT strftime('%%Y')");
+ zYear = zDefaultYear;
+ nYear = 4;
+ }
+ if(4 == nYear){
+ int const nPixelsPerEvent = 3; /* for sizing the "graph" part */
+ Stmt stWeek = empty_Stmt;
+ int rowCount = 0;
+ int total = 0;
+ Blob header = empty_blob;
+ blob_appendf(&header, "Timeline events for the calendar weeks "
+ "of %h", zYear);
+ blob_appendf(&sql,
+ "SELECT DISTINCT strftime('%%%%W',mtime) AS wk, "
+ "count(*) AS n "
+ "FROM event "
+ "WHERE %Q=substr(date(mtime),1,4) "
+ "AND mtime < current_timestamp ",
+ zYear);
+ if(zUserName&&*zUserName){
+ blob_appendf(&sql, " AND user=%Q ", zUserName);
+ blob_appendf(&header," for user %h", zUserName);
+ }
+ blob_appendf(&sql, "GROUP BY wk ORDER BY wk DESC");
+ cgi_printf("%h
", blob_str(&header));
+ blob_reset(&header);
+ cgi_printf("");
+ cgi_printf(""
+ "Week | "
+ "Events | "
+ " | "
+ "
"
+ "");
+ db_prepare(&stWeek, blob_str(&sql));
+ blob_reset(&sql);
+ while( SQLITE_ROW == db_step(&stWeek) ){
+ char const * zWeek = db_column_text(&stWeek,0);
+ int const nCount = db_column_int(&stWeek,1);
+ int const graphSize = nPixelsPerEvent * nCount;
+ total += nCount;
+ cgi_printf("", ++rowCount % 2 );
+ cgi_printf("%s | ",zWeek);
+
+ cgi_printf("%d | ",nCount);
+ cgi_printf("");
+ if(nCount){
+ cgi_printf("",
+ graphSize);
+ }
+ cgi_printf(" |
");
+ }
+ db_finalize(&stWeek);
+ free(zDefaultYear);
+ if(total){
+ cgi_printf("", ++rowCount%2);
+ cgi_printf("Total events: | %d | ",
+ total);
+ cgi_printf("
");
+ }
+ cgi_printf("
");
+ output_table_sorting_javascript("statsTable","tnx");
+ }
+}
+
/*
** WEBPAGE: stats_report
**
** Shows activity reports for the repository.
**
@@ -2028,35 +2187,38 @@
*/
void stats_report_page(){
HQuery url; /* URL for various branch links */
char const * zView = P("view"); /* Which view/report to show. */
char const *zUserName = P("user");
+ if(!zUserName) zUserName = P("u");
url_initialize(&url, "stats_report");
if(zUserName && *zUserName){
url_add_parameter(&url,"user", zUserName);
timeline_submenu(&url, "(Remove User Flag)", "view", zView, "user");
}
timeline_submenu(&url, "By Year", "view", "byyear", 0);
timeline_submenu(&url, "By Month", "view", "bymonth", 0);
+ timeline_submenu(&url, "By Week", "view", "byweek", 0);
timeline_submenu(&url, "By User", "view", "byuser", "user");
url_reset(&url);
style_header("Activity Reports");
if(0==fossil_strcmp(zView,"byyear")){
- stats_report_by_month_year(0, zUserName);
+ stats_report_by_month_year(0, 0, zUserName);
}else if(0==fossil_strcmp(zView,"bymonth")){
- stats_report_by_month_year(1, zUserName);
+ stats_report_by_month_year(1, 0, zUserName);
}else if(0==fossil_strcmp(zView,"byweek")){
- @ TODO: by-week report.
+ stats_report_year_weeks(zUserName);
}else if(0==fossil_strcmp(zView,"byuser")){
stats_report_by_user();
}else{
@ Select a report to show:
@
}
style_footer();
}