Fossil SCM

Merge the weekly status reporting enhancements into trunk.

drh 2013-07-17 12:56 trunk merge
Commit 5d74ce03da97e5c2e72e975429698e50ca848f3e
+13
--- src/style.c
+++ src/style.c
@@ -1058,10 +1058,23 @@
10581058
@ padding: 0.1em 1em 0.1em 1em;
10591059
},
10601060
{ ".statistics-report-row-year",
10611061
"",
10621062
@ text-align: left;
1063
+ },
1064
+ { ".statistics-report-graph-line",
1065
+ "for the /stats_report views",
1066
+ @ background-color: #446979;
1067
+ },
1068
+ { ".statistics-report-week-number-label",
1069
+ "for the /stats_report views",
1070
+ @ text-align: right;
1071
+ @ font-size: 0.8em;
1072
+ },
1073
+ { ".statistics-report-week-of-year-list",
1074
+ "for the /stats_report views",
1075
+ @ font-size: 0.8em;
10631076
},
10641077
{ "tr.row0",
10651078
"even table row color",
10661079
@ /* use default */
10671080
},
10681081
--- src/style.c
+++ src/style.c
@@ -1058,10 +1058,23 @@
1058 @ padding: 0.1em 1em 0.1em 1em;
1059 },
1060 { ".statistics-report-row-year",
1061 "",
1062 @ text-align: left;
 
 
 
 
 
 
 
 
 
 
 
 
 
1063 },
1064 { "tr.row0",
1065 "even table row color",
1066 @ /* use default */
1067 },
1068
--- src/style.c
+++ src/style.c
@@ -1058,10 +1058,23 @@
1058 @ padding: 0.1em 1em 0.1em 1em;
1059 },
1060 { ".statistics-report-row-year",
1061 "",
1062 @ text-align: left;
1063 },
1064 { ".statistics-report-graph-line",
1065 "for the /stats_report views",
1066 @ background-color: #446979;
1067 },
1068 { ".statistics-report-week-number-label",
1069 "for the /stats_report views",
1070 @ text-align: right;
1071 @ font-size: 0.8em;
1072 },
1073 { ".statistics-report-week-of-year-list",
1074 "for the /stats_report views",
1075 @ font-size: 0.8em;
1076 },
1077 { "tr.row0",
1078 "even table row color",
1079 @ /* use default */
1080 },
1081
+13
--- src/style.c
+++ src/style.c
@@ -1058,10 +1058,23 @@
10581058
@ padding: 0.1em 1em 0.1em 1em;
10591059
},
10601060
{ ".statistics-report-row-year",
10611061
"",
10621062
@ text-align: left;
1063
+ },
1064
+ { ".statistics-report-graph-line",
1065
+ "for the /stats_report views",
1066
+ @ background-color: #446979;
1067
+ },
1068
+ { ".statistics-report-week-number-label",
1069
+ "for the /stats_report views",
1070
+ @ text-align: right;
1071
+ @ font-size: 0.8em;
1072
+ },
1073
+ { ".statistics-report-week-of-year-list",
1074
+ "for the /stats_report views",
1075
+ @ font-size: 0.8em;
10631076
},
10641077
{ "tr.row0",
10651078
"even table row color",
10661079
@ /* use default */
10671080
},
10681081
--- src/style.c
+++ src/style.c
@@ -1058,10 +1058,23 @@
1058 @ padding: 0.1em 1em 0.1em 1em;
1059 },
1060 { ".statistics-report-row-year",
1061 "",
1062 @ text-align: left;
 
 
 
 
 
 
 
 
 
 
 
 
 
1063 },
1064 { "tr.row0",
1065 "even table row color",
1066 @ /* use default */
1067 },
1068
--- src/style.c
+++ src/style.c
@@ -1058,10 +1058,23 @@
1058 @ padding: 0.1em 1em 0.1em 1em;
1059 },
1060 { ".statistics-report-row-year",
1061 "",
1062 @ text-align: left;
1063 },
1064 { ".statistics-report-graph-line",
1065 "for the /stats_report views",
1066 @ background-color: #446979;
1067 },
1068 { ".statistics-report-week-number-label",
1069 "for the /stats_report views",
1070 @ text-align: right;
1071 @ font-size: 0.8em;
1072 },
1073 { ".statistics-report-week-of-year-list",
1074 "for the /stats_report views",
1075 @ font-size: 0.8em;
1076 },
1077 { "tr.row0",
1078 "even table row color",
1079 @ /* use default */
1080 },
1081
+174 -12
--- src/timeline.c
+++ src/timeline.c
@@ -1032,10 +1032,11 @@
10321032
const char *zTagName = P("t"); /* Show events with this tag */
10331033
const char *zBrName = P("r"); /* Show events related to this tag */
10341034
const char *zSearch = P("s"); /* Search string */
10351035
const char *zUses = P("uf"); /* Only show checkins hold this file */
10361036
const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */
1037
+ const char *zYearWeek = P("yw"); /* Show checkins for the given YYYY-WW (weak-of-year) */
10371038
int useDividers = P("nd")==0; /* Show dividers if "nd" is missing */
10381039
int renameOnly = P("namechng")!=0; /* Show only checkins that rename files */
10391040
int tagid; /* Tag ID */
10401041
int tmFlags; /* Timeline flags */
10411042
const char *zThisTag = 0; /* Suppress links to this tag */
@@ -1218,10 +1219,14 @@
12181219
}
12191220
if( zYearMonth ){
12201221
blob_appendf(&sql, " AND %Q=strftime('%%Y-%%m',event.mtime) ",
12211222
zYearMonth);
12221223
}
1224
+ else if( zYearWeek ){
1225
+ blob_appendf(&sql, " AND %Q=strftime('%%Y-%%W',event.mtime) ",
1226
+ zYearWeek);
1227
+ }
12231228
if( tagid>0 ){
12241229
blob_appendf(&sql,
12251230
"AND (EXISTS(SELECT 1 FROM tagxref"
12261231
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", tagid);
12271232
@@ -1350,10 +1355,12 @@
13501355
db_multi_exec("%s", blob_str(&sql));
13511356
13521357
n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
13531358
if( zYearMonth ){
13541359
blob_appendf(&desc, "%s events for %h", zEType, zYearMonth);
1360
+ }else if( zYearWeek ){
1361
+ blob_appendf(&desc, "%s events for year/week %h", zEType, zYearWeek);
13551362
}else if( zAfter==0 && zBefore==0 && zCirca==0 ){
13561363
blob_appendf(&desc, "%d most recent %ss", n, zEType);
13571364
}else{
13581365
blob_appendf(&desc, "%d %ss", n, zEType);
13591366
}
@@ -1829,20 +1836,48 @@
18291836
}
18301837
db_finalize(&q);
18311838
style_footer();
18321839
}
18331840
1834
-
1841
+/*
1842
+** Helper for stats_report_by_month_year(), which generates a list of
1843
+** week numbers. zTimeframe should be either a timeframe in the form YYYY
1844
+** or YYYY-MM.
1845
+*/
1846
+static void stats_report_output_week_links(char const * zTimeframe){
1847
+ Stmt stWeek = empty_Stmt;
1848
+ char yearPart[5] = {0,0,0,0,0};
1849
+ memcpy(yearPart, zTimeframe, 4);
1850
+ db_prepare(&stWeek,
1851
+ "SELECT DISTINCT strftime('%%W',mtime) AS wk, "
1852
+ "count(*) AS n, "
1853
+ "substr(date(mtime),1,%d) AS ym "
1854
+ "FROM event "
1855
+ "WHERE ym=%Q AND mtime < current_timestamp "
1856
+ "GROUP BY wk ORDER BY wk",
1857
+ strlen(zTimeframe),
1858
+ zTimeframe);
1859
+ while( SQLITE_ROW == db_step(&stWeek) ){
1860
+ char const * zWeek = db_column_text(&stWeek,0);
1861
+ int const nCount = db_column_int(&stWeek,1);
1862
+ cgi_printf("<a href='%s/timeline?"
1863
+ "yw=%t-%t&n=%d'>%s</a>",
1864
+ g.zTop, yearPart, zWeek,
1865
+ nCount, zWeek);
1866
+ }
1867
+ db_finalize(&stWeek);
1868
+}
18351869
18361870
/*
18371871
** Implements the "byyear" and "bymonth" reports for /stats_report.
18381872
** If includeMonth is true then it generates the "bymonth" report,
18391873
** else the "byyear" report. If zUserName is not NULL and not empty
18401874
** then the report is restricted to events created by the named user
18411875
** account.
18421876
*/
18431877
static void stats_report_by_month_year(char includeMonth,
1878
+ char includeWeeks,
18441879
char const * zUserName){
18451880
Stmt query = empty_Stmt;
18461881
int const nPixelsPerEvent = 1; /* for sizing the "graph" part */
18471882
int nRowNumber = 0; /* current TR number */
18481883
int nEventTotal = 0; /* Total event count */
@@ -1906,17 +1941,17 @@
19061941
memcpy(zPrevYear,zTimeframe,4);
19071942
rowClass = ++nRowNumber % 2;
19081943
@ <tr class='row%d(rowClass)'>
19091944
@ <th colspan='3' class='statistics-report-row-year'>%s(zPrevYear)</th>
19101945
@ </tr>
1911
- }
1912
- }
1913
- rowClass = ++nRowNumber % 2;
1914
- nEventTotal += nCount;
1915
- nEventsPerYear += nCount;
1916
- @<tr class='row%d(rowClass)'>
1917
- @ <td>
1946
+ }
1947
+ }
1948
+ rowClass = ++nRowNumber % 2;
1949
+ nEventTotal += nCount;
1950
+ nEventsPerYear += nCount;
1951
+ @<tr class='row%d(rowClass)'>
1952
+ @ <td>
19181953
if(includeMonth){
19191954
cgi_printf("<a href='%s/timeline?"
19201955
"ym=%t&n=%d",
19211956
g.zTop, zTimeframe, nCount );
19221957
/* Reminder: n=nCount is not actually correct for bymonth unless
@@ -1925,18 +1960,32 @@
19251960
if( zUserName && *zUserName ){
19261961
cgi_printf("&u=%t", zUserName);
19271962
}
19281963
cgi_printf("' target='_new'>%s</a>",zTimeframe);
19291964
}else {
1930
- @ %s(zTimeframe)
1965
+ cgi_printf("<a href='?view=byweek&y=%s", zTimeframe);
1966
+ if(zUserName && *zUserName){
1967
+ cgi_printf("&u=%t", zUserName);
1968
+ }
1969
+ cgi_printf("'>%s</a>", zTimeframe);
19311970
}
19321971
@ </td><td>%d(nCount)</td>
19331972
@ <td>
19341973
@ <div class='statistics-report-graph-line'
19351974
@ style='height:16px;width:%d(nSize)px;'>
19361975
@ </div></td>
19371976
@</tr>
1977
+ if(includeWeeks){
1978
+ /* This part works fine for months but it terribly slow (4.5s on my PC),
1979
+ so it's only shown for by-year for now. Suggestions/patches for
1980
+ a better/faster layout are welcomed. */
1981
+ @ <tr class='row%d(rowClass)'>
1982
+ @ <td colspan='2' class='statistics-report-week-number-label'>Week #:</td>
1983
+ @ <td class='statistics-report-week-of-year-list'>
1984
+ stats_report_output_week_links(zTimeframe);
1985
+ @ </td></tr>
1986
+ }
19381987
19391988
/*
19401989
Potential improvement: calculate the min/max event counts and
19411990
use percent-based graph bars.
19421991
*/
@@ -2014,10 +2063,120 @@
20142063
@ </tbody></table>
20152064
db_finalize(&query);
20162065
output_table_sorting_javascript("statsTable","tnx");
20172066
}
20182067
2068
+
2069
+/*
2070
+** Helper for stats_report_by_month_year(), which generates a list of
2071
+** week numbers. zTimeframe should be either a timeframe in the form YYYY
2072
+** or YYYY-MM.
2073
+*/
2074
+static void stats_report_year_weeks(char const * zUserName){
2075
+ char const * zYear = P("y");
2076
+ int nYear = zYear ? strlen(zYear) : 0;
2077
+ int i = 0;
2078
+ Stmt qYears = empty_Stmt;
2079
+ char * zDefaultYear = NULL;
2080
+ Blob sql = empty_blob;
2081
+ cgi_printf("Select year: ");
2082
+
2083
+ blob_append(&sql,
2084
+ "SELECT DISTINCT substr(date(mtime),1,4) AS y "
2085
+ "FROM event WHERE 1 ", -1);
2086
+ if(zUserName&&*zUserName){
2087
+ blob_appendf(&sql,"AND user=%Q ", zUserName);
2088
+ }
2089
+ blob_append(&sql,"GROUP BY y ORDER BY y", -1);
2090
+ db_prepare(&qYears, blob_str(&sql));
2091
+ blob_reset(&sql);
2092
+ while( SQLITE_ROW == db_step(&qYears) ){
2093
+ char const * zT = db_column_text(&qYears, 0);
2094
+ if( i++ ){
2095
+ cgi_printf(" ");
2096
+ }
2097
+ cgi_printf("<a href='?view=byweek&y=%s", zT);
2098
+ if(zUserName && *zUserName){
2099
+ cgi_printf("&user=%t",zUserName);
2100
+ }
2101
+ cgi_printf("'>%s</a>",zT);
2102
+ }
2103
+ db_finalize(&qYears);
2104
+ cgi_printf("<br/>");
2105
+ if(!zYear || !*zYear){
2106
+ zDefaultYear = db_text("????", "SELECT strftime('%%Y')");
2107
+ zYear = zDefaultYear;
2108
+ nYear = 4;
2109
+ }
2110
+ if(4 == nYear){
2111
+ int const nPixelsPerEvent = 3; /* for sizing the "graph" part */
2112
+ Stmt stWeek = empty_Stmt;
2113
+ int rowCount = 0;
2114
+ int total = 0;
2115
+ Blob header = empty_blob;
2116
+ blob_appendf(&header, "Timeline events for the calendar weeks "
2117
+ "of %h", zYear);
2118
+ blob_appendf(&sql,
2119
+ "SELECT DISTINCT strftime('%%%%W',mtime) AS wk, "
2120
+ "count(*) AS n "
2121
+ "FROM event "
2122
+ "WHERE %Q=substr(date(mtime),1,4) "
2123
+ "AND mtime < current_timestamp ",
2124
+ zYear);
2125
+ if(zUserName&&*zUserName){
2126
+ blob_appendf(&sql, " AND user=%Q ", zUserName);
2127
+ blob_appendf(&header," for user %h", zUserName);
2128
+ }
2129
+ blob_appendf(&sql, "GROUP BY wk ORDER BY wk DESC");
2130
+ cgi_printf("<h1>%h</h1>", blob_str(&header));
2131
+ blob_reset(&header);
2132
+ cgi_printf("<table class='statistics-report-table-events' "
2133
+ "border='0' cellpadding='2' "
2134
+ "cellspacing='0' id='statsTable'>");
2135
+ cgi_printf("<thead><tr>"
2136
+ "<th>Week</th>"
2137
+ "<th>Events</th>"
2138
+ "<th><!-- relative commits graph --></th>"
2139
+ "</tr></thead>"
2140
+ "<tbody>");
2141
+ db_prepare(&stWeek, blob_str(&sql));
2142
+ blob_reset(&sql);
2143
+ while( SQLITE_ROW == db_step(&stWeek) ){
2144
+ char const * zWeek = db_column_text(&stWeek,0);
2145
+ int const nCount = db_column_int(&stWeek,1);
2146
+ int const graphSize = nPixelsPerEvent * nCount;
2147
+ total += nCount;
2148
+ cgi_printf("<tr class='row%d'>", ++rowCount % 2 );
2149
+ cgi_printf("<td><a href='%s/timeline?yw=%t-%s&n=%d",
2150
+ g.zTop, zYear, zWeek, nCount);
2151
+ if(zUserName && *zUserName){
2152
+ cgi_printf("&u=%t",zUserName);
2153
+ }
2154
+ cgi_printf("'>%s</a></td>",zWeek);
2155
+
2156
+ cgi_printf("<td>%d</td>",nCount);
2157
+ cgi_printf("<td>");
2158
+ if(nCount){
2159
+ cgi_printf("<div class='statistics-report-graph-line'"
2160
+ "style='height:16px;width:%dpx;'></div>",
2161
+ graphSize);
2162
+ }
2163
+ cgi_printf("</td></tr>");
2164
+ }
2165
+ db_finalize(&stWeek);
2166
+ free(zDefaultYear);
2167
+ if(total){
2168
+ cgi_printf("<tr class='row%d'>", ++rowCount%2);
2169
+ cgi_printf("<td colspan='2'>Total events:</td><td>%d</td>",
2170
+ total);
2171
+ cgi_printf("</tr>");
2172
+ }
2173
+ cgi_printf("</tbody></table>");
2174
+ output_table_sorting_javascript("statsTable","tnx");
2175
+ }
2176
+}
2177
+
20192178
/*
20202179
** WEBPAGE: stats_report
20212180
**
20222181
** Shows activity reports for the repository.
20232182
**
@@ -2028,35 +2187,38 @@
20282187
*/
20292188
void stats_report_page(){
20302189
HQuery url; /* URL for various branch links */
20312190
char const * zView = P("view"); /* Which view/report to show. */
20322191
char const *zUserName = P("user");
2192
+ if(!zUserName) zUserName = P("u");
20332193
url_initialize(&url, "stats_report");
20342194
20352195
if(zUserName && *zUserName){
20362196
url_add_parameter(&url,"user", zUserName);
20372197
timeline_submenu(&url, "(Remove User Flag)", "view", zView, "user");
20382198
}
20392199
timeline_submenu(&url, "By Year", "view", "byyear", 0);
20402200
timeline_submenu(&url, "By Month", "view", "bymonth", 0);
2201
+ timeline_submenu(&url, "By Week", "view", "byweek", 0);
20412202
timeline_submenu(&url, "By User", "view", "byuser", "user");
20422203
url_reset(&url);
20432204
style_header("Activity Reports");
20442205
if(0==fossil_strcmp(zView,"byyear")){
2045
- stats_report_by_month_year(0, zUserName);
2206
+ stats_report_by_month_year(0, 0, zUserName);
20462207
}else if(0==fossil_strcmp(zView,"bymonth")){
2047
- stats_report_by_month_year(1, zUserName);
2208
+ stats_report_by_month_year(1, 0, zUserName);
20482209
}else if(0==fossil_strcmp(zView,"byweek")){
2049
- @ TODO: by-week report.
2210
+ stats_report_year_weeks(zUserName);
20502211
}else if(0==fossil_strcmp(zView,"byuser")){
20512212
stats_report_by_user();
20522213
}else{
20532214
@ <h1>Select a report to show:</h1>
20542215
@ <ul>
20552216
@ <li><a href='?view=byyear'>Events by year</a></li>
20562217
@ <li><a href='?view=bymonth'>Events by month</a></li>
2218
+ @ <li><a href='?view=byweek'>Events by calendar week</a></li>
20572219
@ <li><a href='?view=byuser'>Events by user</a></li>
20582220
@ </ul>
20592221
}
20602222
20612223
style_footer();
20622224
}
20632225
--- src/timeline.c
+++ src/timeline.c
@@ -1032,10 +1032,11 @@
1032 const char *zTagName = P("t"); /* Show events with this tag */
1033 const char *zBrName = P("r"); /* Show events related to this tag */
1034 const char *zSearch = P("s"); /* Search string */
1035 const char *zUses = P("uf"); /* Only show checkins hold this file */
1036 const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */
 
1037 int useDividers = P("nd")==0; /* Show dividers if "nd" is missing */
1038 int renameOnly = P("namechng")!=0; /* Show only checkins that rename files */
1039 int tagid; /* Tag ID */
1040 int tmFlags; /* Timeline flags */
1041 const char *zThisTag = 0; /* Suppress links to this tag */
@@ -1218,10 +1219,14 @@
1218 }
1219 if( zYearMonth ){
1220 blob_appendf(&sql, " AND %Q=strftime('%%Y-%%m',event.mtime) ",
1221 zYearMonth);
1222 }
 
 
 
 
1223 if( tagid>0 ){
1224 blob_appendf(&sql,
1225 "AND (EXISTS(SELECT 1 FROM tagxref"
1226 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", tagid);
1227
@@ -1350,10 +1355,12 @@
1350 db_multi_exec("%s", blob_str(&sql));
1351
1352 n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
1353 if( zYearMonth ){
1354 blob_appendf(&desc, "%s events for %h", zEType, zYearMonth);
 
 
1355 }else if( zAfter==0 && zBefore==0 && zCirca==0 ){
1356 blob_appendf(&desc, "%d most recent %ss", n, zEType);
1357 }else{
1358 blob_appendf(&desc, "%d %ss", n, zEType);
1359 }
@@ -1829,20 +1836,48 @@
1829 }
1830 db_finalize(&q);
1831 style_footer();
1832 }
1833
1834
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1835
1836 /*
1837 ** Implements the "byyear" and "bymonth" reports for /stats_report.
1838 ** If includeMonth is true then it generates the "bymonth" report,
1839 ** else the "byyear" report. If zUserName is not NULL and not empty
1840 ** then the report is restricted to events created by the named user
1841 ** account.
1842 */
1843 static void stats_report_by_month_year(char includeMonth,
 
1844 char const * zUserName){
1845 Stmt query = empty_Stmt;
1846 int const nPixelsPerEvent = 1; /* for sizing the "graph" part */
1847 int nRowNumber = 0; /* current TR number */
1848 int nEventTotal = 0; /* Total event count */
@@ -1906,17 +1941,17 @@
1906 memcpy(zPrevYear,zTimeframe,4);
1907 rowClass = ++nRowNumber % 2;
1908 @ <tr class='row%d(rowClass)'>
1909 @ <th colspan='3' class='statistics-report-row-year'>%s(zPrevYear)</th>
1910 @ </tr>
1911 }
1912 }
1913 rowClass = ++nRowNumber % 2;
1914 nEventTotal += nCount;
1915 nEventsPerYear += nCount;
1916 @<tr class='row%d(rowClass)'>
1917 @ <td>
1918 if(includeMonth){
1919 cgi_printf("<a href='%s/timeline?"
1920 "ym=%t&n=%d",
1921 g.zTop, zTimeframe, nCount );
1922 /* Reminder: n=nCount is not actually correct for bymonth unless
@@ -1925,18 +1960,32 @@
1925 if( zUserName && *zUserName ){
1926 cgi_printf("&u=%t", zUserName);
1927 }
1928 cgi_printf("' target='_new'>%s</a>",zTimeframe);
1929 }else {
1930 @ %s(zTimeframe)
 
 
 
 
1931 }
1932 @ </td><td>%d(nCount)</td>
1933 @ <td>
1934 @ <div class='statistics-report-graph-line'
1935 @ style='height:16px;width:%d(nSize)px;'>
1936 @ </div></td>
1937 @</tr>
 
 
 
 
 
 
 
 
 
 
1938
1939 /*
1940 Potential improvement: calculate the min/max event counts and
1941 use percent-based graph bars.
1942 */
@@ -2014,10 +2063,120 @@
2014 @ </tbody></table>
2015 db_finalize(&query);
2016 output_table_sorting_javascript("statsTable","tnx");
2017 }
2018
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2019 /*
2020 ** WEBPAGE: stats_report
2021 **
2022 ** Shows activity reports for the repository.
2023 **
@@ -2028,35 +2187,38 @@
2028 */
2029 void stats_report_page(){
2030 HQuery url; /* URL for various branch links */
2031 char const * zView = P("view"); /* Which view/report to show. */
2032 char const *zUserName = P("user");
 
2033 url_initialize(&url, "stats_report");
2034
2035 if(zUserName && *zUserName){
2036 url_add_parameter(&url,"user", zUserName);
2037 timeline_submenu(&url, "(Remove User Flag)", "view", zView, "user");
2038 }
2039 timeline_submenu(&url, "By Year", "view", "byyear", 0);
2040 timeline_submenu(&url, "By Month", "view", "bymonth", 0);
 
2041 timeline_submenu(&url, "By User", "view", "byuser", "user");
2042 url_reset(&url);
2043 style_header("Activity Reports");
2044 if(0==fossil_strcmp(zView,"byyear")){
2045 stats_report_by_month_year(0, zUserName);
2046 }else if(0==fossil_strcmp(zView,"bymonth")){
2047 stats_report_by_month_year(1, zUserName);
2048 }else if(0==fossil_strcmp(zView,"byweek")){
2049 @ TODO: by-week report.
2050 }else if(0==fossil_strcmp(zView,"byuser")){
2051 stats_report_by_user();
2052 }else{
2053 @ <h1>Select a report to show:</h1>
2054 @ <ul>
2055 @ <li><a href='?view=byyear'>Events by year</a></li>
2056 @ <li><a href='?view=bymonth'>Events by month</a></li>
 
2057 @ <li><a href='?view=byuser'>Events by user</a></li>
2058 @ </ul>
2059 }
2060
2061 style_footer();
2062 }
2063
--- src/timeline.c
+++ src/timeline.c
@@ -1032,10 +1032,11 @@
1032 const char *zTagName = P("t"); /* Show events with this tag */
1033 const char *zBrName = P("r"); /* Show events related to this tag */
1034 const char *zSearch = P("s"); /* Search string */
1035 const char *zUses = P("uf"); /* Only show checkins hold this file */
1036 const char *zYearMonth = P("ym"); /* Show checkins for the given YYYY-MM */
1037 const char *zYearWeek = P("yw"); /* Show checkins for the given YYYY-WW (weak-of-year) */
1038 int useDividers = P("nd")==0; /* Show dividers if "nd" is missing */
1039 int renameOnly = P("namechng")!=0; /* Show only checkins that rename files */
1040 int tagid; /* Tag ID */
1041 int tmFlags; /* Timeline flags */
1042 const char *zThisTag = 0; /* Suppress links to this tag */
@@ -1218,10 +1219,14 @@
1219 }
1220 if( zYearMonth ){
1221 blob_appendf(&sql, " AND %Q=strftime('%%Y-%%m',event.mtime) ",
1222 zYearMonth);
1223 }
1224 else if( zYearWeek ){
1225 blob_appendf(&sql, " AND %Q=strftime('%%Y-%%W',event.mtime) ",
1226 zYearWeek);
1227 }
1228 if( tagid>0 ){
1229 blob_appendf(&sql,
1230 "AND (EXISTS(SELECT 1 FROM tagxref"
1231 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", tagid);
1232
@@ -1350,10 +1355,12 @@
1355 db_multi_exec("%s", blob_str(&sql));
1356
1357 n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
1358 if( zYearMonth ){
1359 blob_appendf(&desc, "%s events for %h", zEType, zYearMonth);
1360 }else if( zYearWeek ){
1361 blob_appendf(&desc, "%s events for year/week %h", zEType, zYearWeek);
1362 }else if( zAfter==0 && zBefore==0 && zCirca==0 ){
1363 blob_appendf(&desc, "%d most recent %ss", n, zEType);
1364 }else{
1365 blob_appendf(&desc, "%d %ss", n, zEType);
1366 }
@@ -1829,20 +1836,48 @@
1836 }
1837 db_finalize(&q);
1838 style_footer();
1839 }
1840
1841 /*
1842 ** Helper for stats_report_by_month_year(), which generates a list of
1843 ** week numbers. zTimeframe should be either a timeframe in the form YYYY
1844 ** or YYYY-MM.
1845 */
1846 static void stats_report_output_week_links(char const * zTimeframe){
1847 Stmt stWeek = empty_Stmt;
1848 char yearPart[5] = {0,0,0,0,0};
1849 memcpy(yearPart, zTimeframe, 4);
1850 db_prepare(&stWeek,
1851 "SELECT DISTINCT strftime('%%W',mtime) AS wk, "
1852 "count(*) AS n, "
1853 "substr(date(mtime),1,%d) AS ym "
1854 "FROM event "
1855 "WHERE ym=%Q AND mtime < current_timestamp "
1856 "GROUP BY wk ORDER BY wk",
1857 strlen(zTimeframe),
1858 zTimeframe);
1859 while( SQLITE_ROW == db_step(&stWeek) ){
1860 char const * zWeek = db_column_text(&stWeek,0);
1861 int const nCount = db_column_int(&stWeek,1);
1862 cgi_printf("<a href='%s/timeline?"
1863 "yw=%t-%t&n=%d'>%s</a>",
1864 g.zTop, yearPart, zWeek,
1865 nCount, zWeek);
1866 }
1867 db_finalize(&stWeek);
1868 }
1869
1870 /*
1871 ** Implements the "byyear" and "bymonth" reports for /stats_report.
1872 ** If includeMonth is true then it generates the "bymonth" report,
1873 ** else the "byyear" report. If zUserName is not NULL and not empty
1874 ** then the report is restricted to events created by the named user
1875 ** account.
1876 */
1877 static void stats_report_by_month_year(char includeMonth,
1878 char includeWeeks,
1879 char const * zUserName){
1880 Stmt query = empty_Stmt;
1881 int const nPixelsPerEvent = 1; /* for sizing the "graph" part */
1882 int nRowNumber = 0; /* current TR number */
1883 int nEventTotal = 0; /* Total event count */
@@ -1906,17 +1941,17 @@
1941 memcpy(zPrevYear,zTimeframe,4);
1942 rowClass = ++nRowNumber % 2;
1943 @ <tr class='row%d(rowClass)'>
1944 @ <th colspan='3' class='statistics-report-row-year'>%s(zPrevYear)</th>
1945 @ </tr>
1946 }
1947 }
1948 rowClass = ++nRowNumber % 2;
1949 nEventTotal += nCount;
1950 nEventsPerYear += nCount;
1951 @<tr class='row%d(rowClass)'>
1952 @ <td>
1953 if(includeMonth){
1954 cgi_printf("<a href='%s/timeline?"
1955 "ym=%t&n=%d",
1956 g.zTop, zTimeframe, nCount );
1957 /* Reminder: n=nCount is not actually correct for bymonth unless
@@ -1925,18 +1960,32 @@
1960 if( zUserName && *zUserName ){
1961 cgi_printf("&u=%t", zUserName);
1962 }
1963 cgi_printf("' target='_new'>%s</a>",zTimeframe);
1964 }else {
1965 cgi_printf("<a href='?view=byweek&y=%s", zTimeframe);
1966 if(zUserName && *zUserName){
1967 cgi_printf("&u=%t", zUserName);
1968 }
1969 cgi_printf("'>%s</a>", zTimeframe);
1970 }
1971 @ </td><td>%d(nCount)</td>
1972 @ <td>
1973 @ <div class='statistics-report-graph-line'
1974 @ style='height:16px;width:%d(nSize)px;'>
1975 @ </div></td>
1976 @</tr>
1977 if(includeWeeks){
1978 /* This part works fine for months but it terribly slow (4.5s on my PC),
1979 so it's only shown for by-year for now. Suggestions/patches for
1980 a better/faster layout are welcomed. */
1981 @ <tr class='row%d(rowClass)'>
1982 @ <td colspan='2' class='statistics-report-week-number-label'>Week #:</td>
1983 @ <td class='statistics-report-week-of-year-list'>
1984 stats_report_output_week_links(zTimeframe);
1985 @ </td></tr>
1986 }
1987
1988 /*
1989 Potential improvement: calculate the min/max event counts and
1990 use percent-based graph bars.
1991 */
@@ -2014,10 +2063,120 @@
2063 @ </tbody></table>
2064 db_finalize(&query);
2065 output_table_sorting_javascript("statsTable","tnx");
2066 }
2067
2068
2069 /*
2070 ** Helper for stats_report_by_month_year(), which generates a list of
2071 ** week numbers. zTimeframe should be either a timeframe in the form YYYY
2072 ** or YYYY-MM.
2073 */
2074 static void stats_report_year_weeks(char const * zUserName){
2075 char const * zYear = P("y");
2076 int nYear = zYear ? strlen(zYear) : 0;
2077 int i = 0;
2078 Stmt qYears = empty_Stmt;
2079 char * zDefaultYear = NULL;
2080 Blob sql = empty_blob;
2081 cgi_printf("Select year: ");
2082
2083 blob_append(&sql,
2084 "SELECT DISTINCT substr(date(mtime),1,4) AS y "
2085 "FROM event WHERE 1 ", -1);
2086 if(zUserName&&*zUserName){
2087 blob_appendf(&sql,"AND user=%Q ", zUserName);
2088 }
2089 blob_append(&sql,"GROUP BY y ORDER BY y", -1);
2090 db_prepare(&qYears, blob_str(&sql));
2091 blob_reset(&sql);
2092 while( SQLITE_ROW == db_step(&qYears) ){
2093 char const * zT = db_column_text(&qYears, 0);
2094 if( i++ ){
2095 cgi_printf(" ");
2096 }
2097 cgi_printf("<a href='?view=byweek&y=%s", zT);
2098 if(zUserName && *zUserName){
2099 cgi_printf("&user=%t",zUserName);
2100 }
2101 cgi_printf("'>%s</a>",zT);
2102 }
2103 db_finalize(&qYears);
2104 cgi_printf("<br/>");
2105 if(!zYear || !*zYear){
2106 zDefaultYear = db_text("????", "SELECT strftime('%%Y')");
2107 zYear = zDefaultYear;
2108 nYear = 4;
2109 }
2110 if(4 == nYear){
2111 int const nPixelsPerEvent = 3; /* for sizing the "graph" part */
2112 Stmt stWeek = empty_Stmt;
2113 int rowCount = 0;
2114 int total = 0;
2115 Blob header = empty_blob;
2116 blob_appendf(&header, "Timeline events for the calendar weeks "
2117 "of %h", zYear);
2118 blob_appendf(&sql,
2119 "SELECT DISTINCT strftime('%%%%W',mtime) AS wk, "
2120 "count(*) AS n "
2121 "FROM event "
2122 "WHERE %Q=substr(date(mtime),1,4) "
2123 "AND mtime < current_timestamp ",
2124 zYear);
2125 if(zUserName&&*zUserName){
2126 blob_appendf(&sql, " AND user=%Q ", zUserName);
2127 blob_appendf(&header," for user %h", zUserName);
2128 }
2129 blob_appendf(&sql, "GROUP BY wk ORDER BY wk DESC");
2130 cgi_printf("<h1>%h</h1>", blob_str(&header));
2131 blob_reset(&header);
2132 cgi_printf("<table class='statistics-report-table-events' "
2133 "border='0' cellpadding='2' "
2134 "cellspacing='0' id='statsTable'>");
2135 cgi_printf("<thead><tr>"
2136 "<th>Week</th>"
2137 "<th>Events</th>"
2138 "<th><!-- relative commits graph --></th>"
2139 "</tr></thead>"
2140 "<tbody>");
2141 db_prepare(&stWeek, blob_str(&sql));
2142 blob_reset(&sql);
2143 while( SQLITE_ROW == db_step(&stWeek) ){
2144 char const * zWeek = db_column_text(&stWeek,0);
2145 int const nCount = db_column_int(&stWeek,1);
2146 int const graphSize = nPixelsPerEvent * nCount;
2147 total += nCount;
2148 cgi_printf("<tr class='row%d'>", ++rowCount % 2 );
2149 cgi_printf("<td><a href='%s/timeline?yw=%t-%s&n=%d",
2150 g.zTop, zYear, zWeek, nCount);
2151 if(zUserName && *zUserName){
2152 cgi_printf("&u=%t",zUserName);
2153 }
2154 cgi_printf("'>%s</a></td>",zWeek);
2155
2156 cgi_printf("<td>%d</td>",nCount);
2157 cgi_printf("<td>");
2158 if(nCount){
2159 cgi_printf("<div class='statistics-report-graph-line'"
2160 "style='height:16px;width:%dpx;'></div>",
2161 graphSize);
2162 }
2163 cgi_printf("</td></tr>");
2164 }
2165 db_finalize(&stWeek);
2166 free(zDefaultYear);
2167 if(total){
2168 cgi_printf("<tr class='row%d'>", ++rowCount%2);
2169 cgi_printf("<td colspan='2'>Total events:</td><td>%d</td>",
2170 total);
2171 cgi_printf("</tr>");
2172 }
2173 cgi_printf("</tbody></table>");
2174 output_table_sorting_javascript("statsTable","tnx");
2175 }
2176 }
2177
2178 /*
2179 ** WEBPAGE: stats_report
2180 **
2181 ** Shows activity reports for the repository.
2182 **
@@ -2028,35 +2187,38 @@
2187 */
2188 void stats_report_page(){
2189 HQuery url; /* URL for various branch links */
2190 char const * zView = P("view"); /* Which view/report to show. */
2191 char const *zUserName = P("user");
2192 if(!zUserName) zUserName = P("u");
2193 url_initialize(&url, "stats_report");
2194
2195 if(zUserName && *zUserName){
2196 url_add_parameter(&url,"user", zUserName);
2197 timeline_submenu(&url, "(Remove User Flag)", "view", zView, "user");
2198 }
2199 timeline_submenu(&url, "By Year", "view", "byyear", 0);
2200 timeline_submenu(&url, "By Month", "view", "bymonth", 0);
2201 timeline_submenu(&url, "By Week", "view", "byweek", 0);
2202 timeline_submenu(&url, "By User", "view", "byuser", "user");
2203 url_reset(&url);
2204 style_header("Activity Reports");
2205 if(0==fossil_strcmp(zView,"byyear")){
2206 stats_report_by_month_year(0, 0, zUserName);
2207 }else if(0==fossil_strcmp(zView,"bymonth")){
2208 stats_report_by_month_year(1, 0, zUserName);
2209 }else if(0==fossil_strcmp(zView,"byweek")){
2210 stats_report_year_weeks(zUserName);
2211 }else if(0==fossil_strcmp(zView,"byuser")){
2212 stats_report_by_user();
2213 }else{
2214 @ <h1>Select a report to show:</h1>
2215 @ <ul>
2216 @ <li><a href='?view=byyear'>Events by year</a></li>
2217 @ <li><a href='?view=bymonth'>Events by month</a></li>
2218 @ <li><a href='?view=byweek'>Events by calendar week</a></li>
2219 @ <li><a href='?view=byuser'>Events by user</a></li>
2220 @ </ul>
2221 }
2222
2223 style_footer();
2224 }
2225

Keyboard Shortcuts

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