Fossil SCM
Added sub-submenu to /reports for selecting type of event to filter on.
Commit
3e915d420ac777f0423fc585ae4c54183b52b465
Parent
0761df83b09ddb5…
1 file changed
+88
-22
+88
-22
| --- src/timeline.c | ||
| +++ src/timeline.c | ||
| @@ -1851,10 +1851,24 @@ | ||
| 1851 | 1851 | } |
| 1852 | 1852 | db_finalize(&q); |
| 1853 | 1853 | style_footer(); |
| 1854 | 1854 | } |
| 1855 | 1855 | |
| 1856 | + | |
| 1857 | +/* | |
| 1858 | +** Used by stats_report_xxxxx() to remember which type of events | |
| 1859 | +** to show. Populated by stats_report_init_view() and holds the | |
| 1860 | +** return value of that function. | |
| 1861 | +*/ | |
| 1862 | +static int statsReportType = 0; | |
| 1863 | + | |
| 1864 | +/* | |
| 1865 | +** Set by stats_report_init_view() to one of the y=XXXX values | |
| 1866 | +** accepted by /timeline?y=XXXX. | |
| 1867 | +*/ | |
| 1868 | +static char const * statsReportTimelineYFlag = NULL; | |
| 1869 | + | |
| 1856 | 1870 | /* |
| 1857 | 1871 | ** Creates a TEMP VIEW named v_reports which is a wrapper around the |
| 1858 | 1872 | ** EVENT table filtered on event.type. It looks for the request |
| 1859 | 1873 | ** parameter 'type' (reminder: we "should" use 'y' for consistency |
| 1860 | 1874 | ** with /timeline, but /reports uses 'y' for the year) and expects it |
| @@ -1873,10 +1887,11 @@ | ||
| 1873 | 1887 | */ |
| 1874 | 1888 | static int stats_report_init_view(){ |
| 1875 | 1889 | char const * zType = PD("type","*"); /* analog to /timeline?y=... */ |
| 1876 | 1890 | char const * zRealType = NULL; /* normalized form of zType */ |
| 1877 | 1891 | int rc = 0; /* result code */ |
| 1892 | + assert( !statsReportType && "Must not be called more than once." ); | |
| 1878 | 1893 | switch( (zType && *zType) ? *zType : 0 ){ |
| 1879 | 1894 | case 'c': |
| 1880 | 1895 | case 'C': |
| 1881 | 1896 | zRealType = "ci"; |
| 1882 | 1897 | rc = *zRealType; |
| @@ -1905,29 +1920,33 @@ | ||
| 1905 | 1920 | rc = '*'; |
| 1906 | 1921 | break; |
| 1907 | 1922 | } |
| 1908 | 1923 | assert(0 != rc); |
| 1909 | 1924 | if(zRealType){ |
| 1925 | + statsReportTimelineYFlag = zRealType; | |
| 1910 | 1926 | db_multi_exec("CREATE TEMP VIEW v_reports AS " |
| 1911 | 1927 | "SELECT * FROM event WHERE type GLOB %Q", |
| 1912 | 1928 | zRealType); |
| 1913 | 1929 | }else{ |
| 1930 | + statsReportTimelineYFlag = "a"; | |
| 1914 | 1931 | db_multi_exec("CREATE TEMP VIEW v_reports AS " |
| 1915 | 1932 | "SELECT * FROM event"); |
| 1916 | 1933 | } |
| 1917 | - return rc; | |
| 1934 | + return statsReportType = rc; | |
| 1918 | 1935 | } |
| 1919 | 1936 | |
| 1920 | 1937 | /* |
| 1921 | -** Expects to be passed the return value of stats_report_init_view(), | |
| 1922 | -** and returns a string suitable (for a given value of suitable) for | |
| 1923 | -** use in a label. The returned bytes are static. | |
| 1938 | +** Returns a string suitable (for a given value of suitable) for | |
| 1939 | +** use in a label with the header of the /reports pages, dependent | |
| 1940 | +** on the 'type' flag. See stats_report_init_view(). | |
| 1941 | +** The returned bytes are static. | |
| 1924 | 1942 | */ |
| 1925 | -static char const * stats_report_label_for_type( int reportType ){ | |
| 1926 | - switch( reportType ){ | |
| 1943 | +static char const * stats_report_label_for_type(){ | |
| 1944 | + assert( statsReportType && "Must call stats_report_init_view() first." ); | |
| 1945 | + switch( statsReportType ){ | |
| 1927 | 1946 | case 'c': |
| 1928 | - return "commits"; | |
| 1947 | + return "checkins"; | |
| 1929 | 1948 | case 'w': |
| 1930 | 1949 | return "wiki changes"; |
| 1931 | 1950 | case 't': |
| 1932 | 1951 | return "ticket changes"; |
| 1933 | 1952 | case 'g': |
| @@ -1934,10 +1953,50 @@ | ||
| 1934 | 1953 | return "tag changes"; |
| 1935 | 1954 | default: |
| 1936 | 1955 | return "all types"; |
| 1937 | 1956 | } |
| 1938 | 1957 | } |
| 1958 | + | |
| 1959 | +/* | |
| 1960 | +** A helper for the /reports family of pages which prints out a menu | |
| 1961 | +** of links for the various type=XXX flags. zCurrentViewName must be | |
| 1962 | +** the name/value of the 'view' parameter which is in effect at | |
| 1963 | +** the time this is called. e.g. if called from the 'byuser' view | |
| 1964 | +** then zCurrentViewName must be "byuser". | |
| 1965 | +*/ | |
| 1966 | +static void stats_report_event_types_menu(char const * zCurrentViewName){ | |
| 1967 | + char * zTop = mprintf("%s/reports?view=%s", g.zTop, zCurrentViewName); | |
| 1968 | + cgi_printf("<div>"); | |
| 1969 | + cgi_printf("<span>Event types:</span> "); | |
| 1970 | + if('*' == statsReportType){ | |
| 1971 | + cgi_printf(" <strong>all</strong>", zTop); | |
| 1972 | + }else{ | |
| 1973 | + cgi_printf(" <a href='%s'>all</a>", zTop); | |
| 1974 | + } | |
| 1975 | + if('c' == statsReportType){ | |
| 1976 | + cgi_printf(" <strong>checkins</strong>", zTop); | |
| 1977 | + }else{ | |
| 1978 | + cgi_printf(" <a href='%s&type=ci'>checkins</a>", zTop); | |
| 1979 | + } | |
| 1980 | + if( 't' == statsReportType ){ | |
| 1981 | + cgi_printf(" <strong>tickets</strong>", zTop); | |
| 1982 | + }else{ | |
| 1983 | + cgi_printf(" <a href='%s&type=t'>tickets</a>", zTop); | |
| 1984 | + } | |
| 1985 | + if( 'g' == statsReportType ){ | |
| 1986 | + cgi_printf(" <strong>tags</strong>", zTop); | |
| 1987 | + }else{ | |
| 1988 | + cgi_printf(" <a href='%s&type=g'>tags</a>", zTop); | |
| 1989 | + } | |
| 1990 | + if( 'w' == statsReportType ){ | |
| 1991 | + cgi_printf(" <strong>wiki</strong>", zTop); | |
| 1992 | + }else{ | |
| 1993 | + cgi_printf(" <a href='%s&type=w'>wiki</a>", zTop); | |
| 1994 | + } | |
| 1995 | + fossil_free(zTop); | |
| 1996 | + cgi_printf("</div>"); | |
| 1997 | +} | |
| 1939 | 1998 | |
| 1940 | 1999 | |
| 1941 | 2000 | /* |
| 1942 | 2001 | ** Helper for stats_report_by_month_year(), which generates a list of |
| 1943 | 2002 | ** week numbers. zTimeframe should be either a timeframe in the form YYYY |
| @@ -1958,13 +2017,13 @@ | ||
| 1958 | 2017 | zTimeframe); |
| 1959 | 2018 | while( SQLITE_ROW == db_step(&stWeek) ){ |
| 1960 | 2019 | const char * zWeek = db_column_text(&stWeek,0); |
| 1961 | 2020 | const int nCount = db_column_int(&stWeek,1); |
| 1962 | 2021 | cgi_printf("<a href='%s/timeline?" |
| 1963 | - "yw=%t-%t&n=%d'>%s</a>", | |
| 2022 | + "yw=%t-%t&n=%d&y=%s'>%s</a>", | |
| 1964 | 2023 | g.zTop, yearPart, zWeek, |
| 1965 | - nCount, zWeek); | |
| 2024 | + nCount, statsReportTimelineYFlag, zWeek); | |
| 1966 | 2025 | } |
| 1967 | 2026 | db_finalize(&stWeek); |
| 1968 | 2027 | } |
| 1969 | 2028 | |
| 1970 | 2029 | /* |
| @@ -1993,13 +2052,14 @@ | ||
| 1993 | 2052 | Blob header = empty_blob; /* Page header text */ |
| 1994 | 2053 | int nMaxEvents = 1; /* for calculating length of graph |
| 1995 | 2054 | bars. */ |
| 1996 | 2055 | int iterations = 0; /* number of weeks/months we iterate |
| 1997 | 2056 | over */ |
| 1998 | - int reportType = stats_report_init_view(); | |
| 2057 | + stats_report_init_view(); | |
| 2058 | + stats_report_event_types_menu( includeMonth ? "bymonth" : "byyear" ); | |
| 1999 | 2059 | blob_appendf(&header, "Timeline Events (%s) by year%s", |
| 2000 | - stats_report_label_for_type(reportType), | |
| 2060 | + stats_report_label_for_type(), | |
| 2001 | 2061 | (includeMonth ? "/month" : "")); |
| 2002 | 2062 | blob_appendf(&sql, |
| 2003 | 2063 | "SELECT substr(date(mtime),1,%d) AS timeframe, " |
| 2004 | 2064 | "count(*) AS eventCount " |
| 2005 | 2065 | "FROM v_reports ", |
| @@ -2068,21 +2128,23 @@ | ||
| 2068 | 2128 | nEventsPerYear += nCount; |
| 2069 | 2129 | @<tr class='row%d(rowClass)'> |
| 2070 | 2130 | @ <td> |
| 2071 | 2131 | if(includeMonth){ |
| 2072 | 2132 | cgi_printf("<a href='%s/timeline?" |
| 2073 | - "ym=%t&n=%d", | |
| 2074 | - g.zTop, zTimeframe, nCount ); | |
| 2133 | + "ym=%t&n=%d&y=%s", | |
| 2134 | + g.zTop, zTimeframe, nCount, | |
| 2135 | + statsReportTimelineYFlag ); | |
| 2075 | 2136 | /* Reminder: n=nCount is not actually correct for bymonth unless |
| 2076 | 2137 | that was the only user who caused events. |
| 2077 | 2138 | */ |
| 2078 | 2139 | if( zUserName && *zUserName ){ |
| 2079 | 2140 | cgi_printf("&u=%t", zUserName); |
| 2080 | 2141 | } |
| 2081 | 2142 | cgi_printf("' target='_new'>%s</a>",zTimeframe); |
| 2082 | 2143 | }else { |
| 2083 | - cgi_printf("<a href='?view=byweek&y=%s", zTimeframe); | |
| 2144 | + cgi_printf("<a href='?view=byweek&y=%s&type=%c", | |
| 2145 | + zTimeframe, (char)statsReportType); | |
| 2084 | 2146 | if(zUserName && *zUserName){ |
| 2085 | 2147 | cgi_printf("&u=%t", zUserName); |
| 2086 | 2148 | } |
| 2087 | 2149 | cgi_printf("'>%s</a>", zTimeframe); |
| 2088 | 2150 | } |
| @@ -2140,21 +2202,22 @@ | ||
| 2140 | 2202 | int rowClass = 0; /* counter for alternating |
| 2141 | 2203 | row colors */ |
| 2142 | 2204 | Blob sql = empty_blob; /* SQL */ |
| 2143 | 2205 | int nMaxEvents = 1; /* max number of events for |
| 2144 | 2206 | all rows. */ |
| 2145 | - int reportType = stats_report_init_view(); | |
| 2207 | + stats_report_init_view(); | |
| 2208 | + stats_report_event_types_menu("byuser"); | |
| 2146 | 2209 | blob_append(&sql, |
| 2147 | 2210 | "SELECT user, " |
| 2148 | 2211 | "COUNT(*) AS eventCount " |
| 2149 | 2212 | "FROM v_reports " |
| 2150 | 2213 | "GROUP BY user ORDER BY eventCount DESC", |
| 2151 | 2214 | -1); |
| 2152 | 2215 | db_prepare(&query, blob_str(&sql)); |
| 2153 | 2216 | blob_reset(&sql); |
| 2154 | 2217 | @ <h1>Timeline Events |
| 2155 | - @ (%s(stats_report_label_for_type(reportType))) by User</h1> | |
| 2218 | + @ (%s(stats_report_label_for_type())) by User</h1> | |
| 2156 | 2219 | @ <table class='statistics-report-table-events' border='0' |
| 2157 | 2220 | @ cellpadding='2' cellspacing='0' id='statsTable'> |
| 2158 | 2221 | @ <thead><tr> |
| 2159 | 2222 | @ <th>User</th> |
| 2160 | 2223 | @ <th>Events</th> |
| @@ -2176,11 +2239,11 @@ | ||
| 2176 | 2239 | if(!nCount) continue /* arguable! Possible? */; |
| 2177 | 2240 | rowClass = ++nRowNumber % 2; |
| 2178 | 2241 | nEventTotal += nCount; |
| 2179 | 2242 | @<tr class='row%d(rowClass)'> |
| 2180 | 2243 | @ <td> |
| 2181 | - @ <a href="?view=bymonth&user=%h(zUser)">%h(zUser)</a> | |
| 2244 | + @ <a href="?view=bymonth&user=%h(zUser)&type=%c((char)statsReportType)">%h(zUser)</a> | |
| 2182 | 2245 | @ </td><td>%d(nCount)</td> |
| 2183 | 2246 | @ <td> |
| 2184 | 2247 | @ <div class='statistics-report-graph-line' |
| 2185 | 2248 | @ style='height:16px;width:%d(nSize)%%;'> |
| 2186 | 2249 | @ </div></td> |
| @@ -2209,11 +2272,12 @@ | ||
| 2209 | 2272 | char * zDefaultYear = NULL; |
| 2210 | 2273 | Blob sql = empty_blob; |
| 2211 | 2274 | int nMaxEvents = 1; /* max number of events for |
| 2212 | 2275 | all rows. */ |
| 2213 | 2276 | int iterations = 0; /* # of active time periods. */ |
| 2214 | - int reportType = stats_report_init_view(); | |
| 2277 | + stats_report_init_view(); | |
| 2278 | + stats_report_event_types_menu("byweek"); | |
| 2215 | 2279 | cgi_printf("Select year: "); |
| 2216 | 2280 | blob_append(&sql, |
| 2217 | 2281 | "SELECT DISTINCT substr(date(mtime),1,4) AS y " |
| 2218 | 2282 | "FROM v_reports WHERE 1 ", -1); |
| 2219 | 2283 | if(zUserName&&*zUserName){ |
| @@ -2225,11 +2289,12 @@ | ||
| 2225 | 2289 | while( SQLITE_ROW == db_step(&qYears) ){ |
| 2226 | 2290 | const char * zT = db_column_text(&qYears, 0); |
| 2227 | 2291 | if( i++ ){ |
| 2228 | 2292 | cgi_printf(" "); |
| 2229 | 2293 | } |
| 2230 | - cgi_printf("<a href='?view=byweek&y=%s", zT); | |
| 2294 | + cgi_printf("<a href='?view=byweek&y=%s&type=%c", zT, | |
| 2295 | + (char)statsReportType); | |
| 2231 | 2296 | if(zUserName && *zUserName){ |
| 2232 | 2297 | cgi_printf("&user=%t",zUserName); |
| 2233 | 2298 | } |
| 2234 | 2299 | cgi_printf("'>%s</a>",zT); |
| 2235 | 2300 | } |
| @@ -2244,11 +2309,11 @@ | ||
| 2244 | 2309 | Stmt stWeek = empty_Stmt; |
| 2245 | 2310 | int rowCount = 0; |
| 2246 | 2311 | int total = 0; |
| 2247 | 2312 | Blob header = empty_blob; |
| 2248 | 2313 | blob_appendf(&header, "Timeline events (%s) for the calendar weeks " |
| 2249 | - "of %h", stats_report_label_for_type(reportType), | |
| 2314 | + "of %h", stats_report_label_for_type(), | |
| 2250 | 2315 | zYear); |
| 2251 | 2316 | blob_appendf(&sql, |
| 2252 | 2317 | "SELECT DISTINCT strftime('%%%%W',mtime) AS wk, " |
| 2253 | 2318 | "count(*) AS n " |
| 2254 | 2319 | "FROM v_reports " |
| @@ -2287,12 +2352,13 @@ | ||
| 2287 | 2352 | const int nSize = nCount |
| 2288 | 2353 | ? (int)(100 * nCount / nMaxEvents) |
| 2289 | 2354 | : 0; |
| 2290 | 2355 | total += nCount; |
| 2291 | 2356 | cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); |
| 2292 | - cgi_printf("<td><a href='%s/timeline?yw=%t-%s&n=%d", | |
| 2293 | - g.zTop, zYear, zWeek, nCount); | |
| 2357 | + cgi_printf("<td><a href='%s/timeline?yw=%t-%s&n=%d&y=%s", | |
| 2358 | + g.zTop, zYear, zWeek, nCount, | |
| 2359 | + statsReportTimelineYFlag); | |
| 2294 | 2360 | if(zUserName && *zUserName){ |
| 2295 | 2361 | cgi_printf("&u=%t",zUserName); |
| 2296 | 2362 | } |
| 2297 | 2363 | cgi_printf("'>%s</a></td>",zWeek); |
| 2298 | 2364 | |
| 2299 | 2365 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -1851,10 +1851,24 @@ | |
| 1851 | } |
| 1852 | db_finalize(&q); |
| 1853 | style_footer(); |
| 1854 | } |
| 1855 | |
| 1856 | /* |
| 1857 | ** Creates a TEMP VIEW named v_reports which is a wrapper around the |
| 1858 | ** EVENT table filtered on event.type. It looks for the request |
| 1859 | ** parameter 'type' (reminder: we "should" use 'y' for consistency |
| 1860 | ** with /timeline, but /reports uses 'y' for the year) and expects it |
| @@ -1873,10 +1887,11 @@ | |
| 1873 | */ |
| 1874 | static int stats_report_init_view(){ |
| 1875 | char const * zType = PD("type","*"); /* analog to /timeline?y=... */ |
| 1876 | char const * zRealType = NULL; /* normalized form of zType */ |
| 1877 | int rc = 0; /* result code */ |
| 1878 | switch( (zType && *zType) ? *zType : 0 ){ |
| 1879 | case 'c': |
| 1880 | case 'C': |
| 1881 | zRealType = "ci"; |
| 1882 | rc = *zRealType; |
| @@ -1905,29 +1920,33 @@ | |
| 1905 | rc = '*'; |
| 1906 | break; |
| 1907 | } |
| 1908 | assert(0 != rc); |
| 1909 | if(zRealType){ |
| 1910 | db_multi_exec("CREATE TEMP VIEW v_reports AS " |
| 1911 | "SELECT * FROM event WHERE type GLOB %Q", |
| 1912 | zRealType); |
| 1913 | }else{ |
| 1914 | db_multi_exec("CREATE TEMP VIEW v_reports AS " |
| 1915 | "SELECT * FROM event"); |
| 1916 | } |
| 1917 | return rc; |
| 1918 | } |
| 1919 | |
| 1920 | /* |
| 1921 | ** Expects to be passed the return value of stats_report_init_view(), |
| 1922 | ** and returns a string suitable (for a given value of suitable) for |
| 1923 | ** use in a label. The returned bytes are static. |
| 1924 | */ |
| 1925 | static char const * stats_report_label_for_type( int reportType ){ |
| 1926 | switch( reportType ){ |
| 1927 | case 'c': |
| 1928 | return "commits"; |
| 1929 | case 'w': |
| 1930 | return "wiki changes"; |
| 1931 | case 't': |
| 1932 | return "ticket changes"; |
| 1933 | case 'g': |
| @@ -1934,10 +1953,50 @@ | |
| 1934 | return "tag changes"; |
| 1935 | default: |
| 1936 | return "all types"; |
| 1937 | } |
| 1938 | } |
| 1939 | |
| 1940 | |
| 1941 | /* |
| 1942 | ** Helper for stats_report_by_month_year(), which generates a list of |
| 1943 | ** week numbers. zTimeframe should be either a timeframe in the form YYYY |
| @@ -1958,13 +2017,13 @@ | |
| 1958 | zTimeframe); |
| 1959 | while( SQLITE_ROW == db_step(&stWeek) ){ |
| 1960 | const char * zWeek = db_column_text(&stWeek,0); |
| 1961 | const int nCount = db_column_int(&stWeek,1); |
| 1962 | cgi_printf("<a href='%s/timeline?" |
| 1963 | "yw=%t-%t&n=%d'>%s</a>", |
| 1964 | g.zTop, yearPart, zWeek, |
| 1965 | nCount, zWeek); |
| 1966 | } |
| 1967 | db_finalize(&stWeek); |
| 1968 | } |
| 1969 | |
| 1970 | /* |
| @@ -1993,13 +2052,14 @@ | |
| 1993 | Blob header = empty_blob; /* Page header text */ |
| 1994 | int nMaxEvents = 1; /* for calculating length of graph |
| 1995 | bars. */ |
| 1996 | int iterations = 0; /* number of weeks/months we iterate |
| 1997 | over */ |
| 1998 | int reportType = stats_report_init_view(); |
| 1999 | blob_appendf(&header, "Timeline Events (%s) by year%s", |
| 2000 | stats_report_label_for_type(reportType), |
| 2001 | (includeMonth ? "/month" : "")); |
| 2002 | blob_appendf(&sql, |
| 2003 | "SELECT substr(date(mtime),1,%d) AS timeframe, " |
| 2004 | "count(*) AS eventCount " |
| 2005 | "FROM v_reports ", |
| @@ -2068,21 +2128,23 @@ | |
| 2068 | nEventsPerYear += nCount; |
| 2069 | @<tr class='row%d(rowClass)'> |
| 2070 | @ <td> |
| 2071 | if(includeMonth){ |
| 2072 | cgi_printf("<a href='%s/timeline?" |
| 2073 | "ym=%t&n=%d", |
| 2074 | g.zTop, zTimeframe, nCount ); |
| 2075 | /* Reminder: n=nCount is not actually correct for bymonth unless |
| 2076 | that was the only user who caused events. |
| 2077 | */ |
| 2078 | if( zUserName && *zUserName ){ |
| 2079 | cgi_printf("&u=%t", zUserName); |
| 2080 | } |
| 2081 | cgi_printf("' target='_new'>%s</a>",zTimeframe); |
| 2082 | }else { |
| 2083 | cgi_printf("<a href='?view=byweek&y=%s", zTimeframe); |
| 2084 | if(zUserName && *zUserName){ |
| 2085 | cgi_printf("&u=%t", zUserName); |
| 2086 | } |
| 2087 | cgi_printf("'>%s</a>", zTimeframe); |
| 2088 | } |
| @@ -2140,21 +2202,22 @@ | |
| 2140 | int rowClass = 0; /* counter for alternating |
| 2141 | row colors */ |
| 2142 | Blob sql = empty_blob; /* SQL */ |
| 2143 | int nMaxEvents = 1; /* max number of events for |
| 2144 | all rows. */ |
| 2145 | int reportType = stats_report_init_view(); |
| 2146 | blob_append(&sql, |
| 2147 | "SELECT user, " |
| 2148 | "COUNT(*) AS eventCount " |
| 2149 | "FROM v_reports " |
| 2150 | "GROUP BY user ORDER BY eventCount DESC", |
| 2151 | -1); |
| 2152 | db_prepare(&query, blob_str(&sql)); |
| 2153 | blob_reset(&sql); |
| 2154 | @ <h1>Timeline Events |
| 2155 | @ (%s(stats_report_label_for_type(reportType))) by User</h1> |
| 2156 | @ <table class='statistics-report-table-events' border='0' |
| 2157 | @ cellpadding='2' cellspacing='0' id='statsTable'> |
| 2158 | @ <thead><tr> |
| 2159 | @ <th>User</th> |
| 2160 | @ <th>Events</th> |
| @@ -2176,11 +2239,11 @@ | |
| 2176 | if(!nCount) continue /* arguable! Possible? */; |
| 2177 | rowClass = ++nRowNumber % 2; |
| 2178 | nEventTotal += nCount; |
| 2179 | @<tr class='row%d(rowClass)'> |
| 2180 | @ <td> |
| 2181 | @ <a href="?view=bymonth&user=%h(zUser)">%h(zUser)</a> |
| 2182 | @ </td><td>%d(nCount)</td> |
| 2183 | @ <td> |
| 2184 | @ <div class='statistics-report-graph-line' |
| 2185 | @ style='height:16px;width:%d(nSize)%%;'> |
| 2186 | @ </div></td> |
| @@ -2209,11 +2272,12 @@ | |
| 2209 | char * zDefaultYear = NULL; |
| 2210 | Blob sql = empty_blob; |
| 2211 | int nMaxEvents = 1; /* max number of events for |
| 2212 | all rows. */ |
| 2213 | int iterations = 0; /* # of active time periods. */ |
| 2214 | int reportType = stats_report_init_view(); |
| 2215 | cgi_printf("Select year: "); |
| 2216 | blob_append(&sql, |
| 2217 | "SELECT DISTINCT substr(date(mtime),1,4) AS y " |
| 2218 | "FROM v_reports WHERE 1 ", -1); |
| 2219 | if(zUserName&&*zUserName){ |
| @@ -2225,11 +2289,12 @@ | |
| 2225 | while( SQLITE_ROW == db_step(&qYears) ){ |
| 2226 | const char * zT = db_column_text(&qYears, 0); |
| 2227 | if( i++ ){ |
| 2228 | cgi_printf(" "); |
| 2229 | } |
| 2230 | cgi_printf("<a href='?view=byweek&y=%s", zT); |
| 2231 | if(zUserName && *zUserName){ |
| 2232 | cgi_printf("&user=%t",zUserName); |
| 2233 | } |
| 2234 | cgi_printf("'>%s</a>",zT); |
| 2235 | } |
| @@ -2244,11 +2309,11 @@ | |
| 2244 | Stmt stWeek = empty_Stmt; |
| 2245 | int rowCount = 0; |
| 2246 | int total = 0; |
| 2247 | Blob header = empty_blob; |
| 2248 | blob_appendf(&header, "Timeline events (%s) for the calendar weeks " |
| 2249 | "of %h", stats_report_label_for_type(reportType), |
| 2250 | zYear); |
| 2251 | blob_appendf(&sql, |
| 2252 | "SELECT DISTINCT strftime('%%%%W',mtime) AS wk, " |
| 2253 | "count(*) AS n " |
| 2254 | "FROM v_reports " |
| @@ -2287,12 +2352,13 @@ | |
| 2287 | const int nSize = nCount |
| 2288 | ? (int)(100 * nCount / nMaxEvents) |
| 2289 | : 0; |
| 2290 | total += nCount; |
| 2291 | cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); |
| 2292 | cgi_printf("<td><a href='%s/timeline?yw=%t-%s&n=%d", |
| 2293 | g.zTop, zYear, zWeek, nCount); |
| 2294 | if(zUserName && *zUserName){ |
| 2295 | cgi_printf("&u=%t",zUserName); |
| 2296 | } |
| 2297 | cgi_printf("'>%s</a></td>",zWeek); |
| 2298 | |
| 2299 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -1851,10 +1851,24 @@ | |
| 1851 | } |
| 1852 | db_finalize(&q); |
| 1853 | style_footer(); |
| 1854 | } |
| 1855 | |
| 1856 | |
| 1857 | /* |
| 1858 | ** Used by stats_report_xxxxx() to remember which type of events |
| 1859 | ** to show. Populated by stats_report_init_view() and holds the |
| 1860 | ** return value of that function. |
| 1861 | */ |
| 1862 | static int statsReportType = 0; |
| 1863 | |
| 1864 | /* |
| 1865 | ** Set by stats_report_init_view() to one of the y=XXXX values |
| 1866 | ** accepted by /timeline?y=XXXX. |
| 1867 | */ |
| 1868 | static char const * statsReportTimelineYFlag = NULL; |
| 1869 | |
| 1870 | /* |
| 1871 | ** Creates a TEMP VIEW named v_reports which is a wrapper around the |
| 1872 | ** EVENT table filtered on event.type. It looks for the request |
| 1873 | ** parameter 'type' (reminder: we "should" use 'y' for consistency |
| 1874 | ** with /timeline, but /reports uses 'y' for the year) and expects it |
| @@ -1873,10 +1887,11 @@ | |
| 1887 | */ |
| 1888 | static int stats_report_init_view(){ |
| 1889 | char const * zType = PD("type","*"); /* analog to /timeline?y=... */ |
| 1890 | char const * zRealType = NULL; /* normalized form of zType */ |
| 1891 | int rc = 0; /* result code */ |
| 1892 | assert( !statsReportType && "Must not be called more than once." ); |
| 1893 | switch( (zType && *zType) ? *zType : 0 ){ |
| 1894 | case 'c': |
| 1895 | case 'C': |
| 1896 | zRealType = "ci"; |
| 1897 | rc = *zRealType; |
| @@ -1905,29 +1920,33 @@ | |
| 1920 | rc = '*'; |
| 1921 | break; |
| 1922 | } |
| 1923 | assert(0 != rc); |
| 1924 | if(zRealType){ |
| 1925 | statsReportTimelineYFlag = zRealType; |
| 1926 | db_multi_exec("CREATE TEMP VIEW v_reports AS " |
| 1927 | "SELECT * FROM event WHERE type GLOB %Q", |
| 1928 | zRealType); |
| 1929 | }else{ |
| 1930 | statsReportTimelineYFlag = "a"; |
| 1931 | db_multi_exec("CREATE TEMP VIEW v_reports AS " |
| 1932 | "SELECT * FROM event"); |
| 1933 | } |
| 1934 | return statsReportType = rc; |
| 1935 | } |
| 1936 | |
| 1937 | /* |
| 1938 | ** Returns a string suitable (for a given value of suitable) for |
| 1939 | ** use in a label with the header of the /reports pages, dependent |
| 1940 | ** on the 'type' flag. See stats_report_init_view(). |
| 1941 | ** The returned bytes are static. |
| 1942 | */ |
| 1943 | static char const * stats_report_label_for_type(){ |
| 1944 | assert( statsReportType && "Must call stats_report_init_view() first." ); |
| 1945 | switch( statsReportType ){ |
| 1946 | case 'c': |
| 1947 | return "checkins"; |
| 1948 | case 'w': |
| 1949 | return "wiki changes"; |
| 1950 | case 't': |
| 1951 | return "ticket changes"; |
| 1952 | case 'g': |
| @@ -1934,10 +1953,50 @@ | |
| 1953 | return "tag changes"; |
| 1954 | default: |
| 1955 | return "all types"; |
| 1956 | } |
| 1957 | } |
| 1958 | |
| 1959 | /* |
| 1960 | ** A helper for the /reports family of pages which prints out a menu |
| 1961 | ** of links for the various type=XXX flags. zCurrentViewName must be |
| 1962 | ** the name/value of the 'view' parameter which is in effect at |
| 1963 | ** the time this is called. e.g. if called from the 'byuser' view |
| 1964 | ** then zCurrentViewName must be "byuser". |
| 1965 | */ |
| 1966 | static void stats_report_event_types_menu(char const * zCurrentViewName){ |
| 1967 | char * zTop = mprintf("%s/reports?view=%s", g.zTop, zCurrentViewName); |
| 1968 | cgi_printf("<div>"); |
| 1969 | cgi_printf("<span>Event types:</span> "); |
| 1970 | if('*' == statsReportType){ |
| 1971 | cgi_printf(" <strong>all</strong>", zTop); |
| 1972 | }else{ |
| 1973 | cgi_printf(" <a href='%s'>all</a>", zTop); |
| 1974 | } |
| 1975 | if('c' == statsReportType){ |
| 1976 | cgi_printf(" <strong>checkins</strong>", zTop); |
| 1977 | }else{ |
| 1978 | cgi_printf(" <a href='%s&type=ci'>checkins</a>", zTop); |
| 1979 | } |
| 1980 | if( 't' == statsReportType ){ |
| 1981 | cgi_printf(" <strong>tickets</strong>", zTop); |
| 1982 | }else{ |
| 1983 | cgi_printf(" <a href='%s&type=t'>tickets</a>", zTop); |
| 1984 | } |
| 1985 | if( 'g' == statsReportType ){ |
| 1986 | cgi_printf(" <strong>tags</strong>", zTop); |
| 1987 | }else{ |
| 1988 | cgi_printf(" <a href='%s&type=g'>tags</a>", zTop); |
| 1989 | } |
| 1990 | if( 'w' == statsReportType ){ |
| 1991 | cgi_printf(" <strong>wiki</strong>", zTop); |
| 1992 | }else{ |
| 1993 | cgi_printf(" <a href='%s&type=w'>wiki</a>", zTop); |
| 1994 | } |
| 1995 | fossil_free(zTop); |
| 1996 | cgi_printf("</div>"); |
| 1997 | } |
| 1998 | |
| 1999 | |
| 2000 | /* |
| 2001 | ** Helper for stats_report_by_month_year(), which generates a list of |
| 2002 | ** week numbers. zTimeframe should be either a timeframe in the form YYYY |
| @@ -1958,13 +2017,13 @@ | |
| 2017 | zTimeframe); |
| 2018 | while( SQLITE_ROW == db_step(&stWeek) ){ |
| 2019 | const char * zWeek = db_column_text(&stWeek,0); |
| 2020 | const int nCount = db_column_int(&stWeek,1); |
| 2021 | cgi_printf("<a href='%s/timeline?" |
| 2022 | "yw=%t-%t&n=%d&y=%s'>%s</a>", |
| 2023 | g.zTop, yearPart, zWeek, |
| 2024 | nCount, statsReportTimelineYFlag, zWeek); |
| 2025 | } |
| 2026 | db_finalize(&stWeek); |
| 2027 | } |
| 2028 | |
| 2029 | /* |
| @@ -1993,13 +2052,14 @@ | |
| 2052 | Blob header = empty_blob; /* Page header text */ |
| 2053 | int nMaxEvents = 1; /* for calculating length of graph |
| 2054 | bars. */ |
| 2055 | int iterations = 0; /* number of weeks/months we iterate |
| 2056 | over */ |
| 2057 | stats_report_init_view(); |
| 2058 | stats_report_event_types_menu( includeMonth ? "bymonth" : "byyear" ); |
| 2059 | blob_appendf(&header, "Timeline Events (%s) by year%s", |
| 2060 | stats_report_label_for_type(), |
| 2061 | (includeMonth ? "/month" : "")); |
| 2062 | blob_appendf(&sql, |
| 2063 | "SELECT substr(date(mtime),1,%d) AS timeframe, " |
| 2064 | "count(*) AS eventCount " |
| 2065 | "FROM v_reports ", |
| @@ -2068,21 +2128,23 @@ | |
| 2128 | nEventsPerYear += nCount; |
| 2129 | @<tr class='row%d(rowClass)'> |
| 2130 | @ <td> |
| 2131 | if(includeMonth){ |
| 2132 | cgi_printf("<a href='%s/timeline?" |
| 2133 | "ym=%t&n=%d&y=%s", |
| 2134 | g.zTop, zTimeframe, nCount, |
| 2135 | statsReportTimelineYFlag ); |
| 2136 | /* Reminder: n=nCount is not actually correct for bymonth unless |
| 2137 | that was the only user who caused events. |
| 2138 | */ |
| 2139 | if( zUserName && *zUserName ){ |
| 2140 | cgi_printf("&u=%t", zUserName); |
| 2141 | } |
| 2142 | cgi_printf("' target='_new'>%s</a>",zTimeframe); |
| 2143 | }else { |
| 2144 | cgi_printf("<a href='?view=byweek&y=%s&type=%c", |
| 2145 | zTimeframe, (char)statsReportType); |
| 2146 | if(zUserName && *zUserName){ |
| 2147 | cgi_printf("&u=%t", zUserName); |
| 2148 | } |
| 2149 | cgi_printf("'>%s</a>", zTimeframe); |
| 2150 | } |
| @@ -2140,21 +2202,22 @@ | |
| 2202 | int rowClass = 0; /* counter for alternating |
| 2203 | row colors */ |
| 2204 | Blob sql = empty_blob; /* SQL */ |
| 2205 | int nMaxEvents = 1; /* max number of events for |
| 2206 | all rows. */ |
| 2207 | stats_report_init_view(); |
| 2208 | stats_report_event_types_menu("byuser"); |
| 2209 | blob_append(&sql, |
| 2210 | "SELECT user, " |
| 2211 | "COUNT(*) AS eventCount " |
| 2212 | "FROM v_reports " |
| 2213 | "GROUP BY user ORDER BY eventCount DESC", |
| 2214 | -1); |
| 2215 | db_prepare(&query, blob_str(&sql)); |
| 2216 | blob_reset(&sql); |
| 2217 | @ <h1>Timeline Events |
| 2218 | @ (%s(stats_report_label_for_type())) by User</h1> |
| 2219 | @ <table class='statistics-report-table-events' border='0' |
| 2220 | @ cellpadding='2' cellspacing='0' id='statsTable'> |
| 2221 | @ <thead><tr> |
| 2222 | @ <th>User</th> |
| 2223 | @ <th>Events</th> |
| @@ -2176,11 +2239,11 @@ | |
| 2239 | if(!nCount) continue /* arguable! Possible? */; |
| 2240 | rowClass = ++nRowNumber % 2; |
| 2241 | nEventTotal += nCount; |
| 2242 | @<tr class='row%d(rowClass)'> |
| 2243 | @ <td> |
| 2244 | @ <a href="?view=bymonth&user=%h(zUser)&type=%c((char)statsReportType)">%h(zUser)</a> |
| 2245 | @ </td><td>%d(nCount)</td> |
| 2246 | @ <td> |
| 2247 | @ <div class='statistics-report-graph-line' |
| 2248 | @ style='height:16px;width:%d(nSize)%%;'> |
| 2249 | @ </div></td> |
| @@ -2209,11 +2272,12 @@ | |
| 2272 | char * zDefaultYear = NULL; |
| 2273 | Blob sql = empty_blob; |
| 2274 | int nMaxEvents = 1; /* max number of events for |
| 2275 | all rows. */ |
| 2276 | int iterations = 0; /* # of active time periods. */ |
| 2277 | stats_report_init_view(); |
| 2278 | stats_report_event_types_menu("byweek"); |
| 2279 | cgi_printf("Select year: "); |
| 2280 | blob_append(&sql, |
| 2281 | "SELECT DISTINCT substr(date(mtime),1,4) AS y " |
| 2282 | "FROM v_reports WHERE 1 ", -1); |
| 2283 | if(zUserName&&*zUserName){ |
| @@ -2225,11 +2289,12 @@ | |
| 2289 | while( SQLITE_ROW == db_step(&qYears) ){ |
| 2290 | const char * zT = db_column_text(&qYears, 0); |
| 2291 | if( i++ ){ |
| 2292 | cgi_printf(" "); |
| 2293 | } |
| 2294 | cgi_printf("<a href='?view=byweek&y=%s&type=%c", zT, |
| 2295 | (char)statsReportType); |
| 2296 | if(zUserName && *zUserName){ |
| 2297 | cgi_printf("&user=%t",zUserName); |
| 2298 | } |
| 2299 | cgi_printf("'>%s</a>",zT); |
| 2300 | } |
| @@ -2244,11 +2309,11 @@ | |
| 2309 | Stmt stWeek = empty_Stmt; |
| 2310 | int rowCount = 0; |
| 2311 | int total = 0; |
| 2312 | Blob header = empty_blob; |
| 2313 | blob_appendf(&header, "Timeline events (%s) for the calendar weeks " |
| 2314 | "of %h", stats_report_label_for_type(), |
| 2315 | zYear); |
| 2316 | blob_appendf(&sql, |
| 2317 | "SELECT DISTINCT strftime('%%%%W',mtime) AS wk, " |
| 2318 | "count(*) AS n " |
| 2319 | "FROM v_reports " |
| @@ -2287,12 +2352,13 @@ | |
| 2352 | const int nSize = nCount |
| 2353 | ? (int)(100 * nCount / nMaxEvents) |
| 2354 | : 0; |
| 2355 | total += nCount; |
| 2356 | cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); |
| 2357 | cgi_printf("<td><a href='%s/timeline?yw=%t-%s&n=%d&y=%s", |
| 2358 | g.zTop, zYear, zWeek, nCount, |
| 2359 | statsReportTimelineYFlag); |
| 2360 | if(zUserName && *zUserName){ |
| 2361 | cgi_printf("&u=%t",zUserName); |
| 2362 | } |
| 2363 | cgi_printf("'>%s</a></td>",zWeek); |
| 2364 | |
| 2365 |