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("" + "" + "" + "" + "" + ""); + 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("",zWeek); + + cgi_printf("",nCount); + cgi_printf(""); + } + db_finalize(&stWeek); + free(zDefaultYear); + if(total){ + cgi_printf("", ++rowCount%2); + cgi_printf("", + total); + cgi_printf(""); + } + cgi_printf("
WeekEvents
%s%d"); + if(nCount){ + cgi_printf("
", + graphSize); + } + cgi_printf("
Total events:%d
"); + 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(); }