| | @@ -2044,688 +2044,7 @@ |
| 2044 | 2044 | const char *zUuid = db_column_text(&q, 0); |
| 2045 | 2045 | @ <li> |
| 2046 | 2046 | @ <a href="%s(g.zTop)/timeline?p=%s(zUuid)&d=%s(zUuid)&unhide">%S(zUuid)</a> |
| 2047 | 2047 | } |
| 2048 | 2048 | db_finalize(&q); |
| 2049 | | - style_footer(); |
| 2050 | | -} |
| 2051 | | - |
| 2052 | | - |
| 2053 | | -/* |
| 2054 | | -** Used by stats_report_xxxxx() to remember which type of events |
| 2055 | | -** to show. Populated by stats_report_init_view() and holds the |
| 2056 | | -** return value of that function. |
| 2057 | | -*/ |
| 2058 | | -static int statsReportType = 0; |
| 2059 | | - |
| 2060 | | -/* |
| 2061 | | -** Set by stats_report_init_view() to one of the y=XXXX values |
| 2062 | | -** accepted by /timeline?y=XXXX. |
| 2063 | | -*/ |
| 2064 | | -static const char *statsReportTimelineYFlag = NULL; |
| 2065 | | - |
| 2066 | | -/* |
| 2067 | | -** Creates a TEMP VIEW named v_reports which is a wrapper around the |
| 2068 | | -** EVENT table filtered on event.type. It looks for the request |
| 2069 | | -** parameter 'type' (reminder: we "should" use 'y' for consistency |
| 2070 | | -** with /timeline, but /reports uses 'y' for the year) and expects it |
| 2071 | | -** to contain one of the conventional values from event.type or the |
| 2072 | | -** value "all", which is treated as equivalent to "*". By default (if |
| 2073 | | -** no 'y' is specified), "*" is assumed (that is also the default for |
| 2074 | | -** invalid/unknown filter values). That 'y' filter is the one used for |
| 2075 | | -** the event list. Note that a filter of "*" or "all" is equivalent to |
| 2076 | | -** querying against the full event table. The view, however, adds an |
| 2077 | | -** abstraction level to simplify the implementation code for the |
| 2078 | | -** various /reports pages. |
| 2079 | | -** |
| 2080 | | -** Returns one of: 'c', 'w', 'g', 't', 'e', representing the type of |
| 2081 | | -** filter it applies, or '*' if no filter is applied (i.e. if "all" is |
| 2082 | | -** used). |
| 2083 | | -*/ |
| 2084 | | -static int stats_report_init_view(){ |
| 2085 | | - const char *zType = PD("type","*"); /* analog to /timeline?y=... */ |
| 2086 | | - const char *zRealType = NULL; /* normalized form of zType */ |
| 2087 | | - int rc = 0; /* result code */ |
| 2088 | | - assert( !statsReportType && "Must not be called more than once." ); |
| 2089 | | - switch( (zType && *zType) ? *zType : 0 ){ |
| 2090 | | - case 'c': |
| 2091 | | - case 'C': |
| 2092 | | - zRealType = "ci"; |
| 2093 | | - rc = *zRealType; |
| 2094 | | - break; |
| 2095 | | - case 'e': |
| 2096 | | - case 'E': |
| 2097 | | - zRealType = "e"; |
| 2098 | | - rc = *zRealType; |
| 2099 | | - break; |
| 2100 | | - case 'g': |
| 2101 | | - case 'G': |
| 2102 | | - zRealType = "g"; |
| 2103 | | - rc = *zRealType; |
| 2104 | | - break; |
| 2105 | | - case 't': |
| 2106 | | - case 'T': |
| 2107 | | - zRealType = "t"; |
| 2108 | | - rc = *zRealType; |
| 2109 | | - break; |
| 2110 | | - case 'w': |
| 2111 | | - case 'W': |
| 2112 | | - zRealType = "w"; |
| 2113 | | - rc = *zRealType; |
| 2114 | | - break; |
| 2115 | | - default: |
| 2116 | | - rc = '*'; |
| 2117 | | - break; |
| 2118 | | - } |
| 2119 | | - assert(0 != rc); |
| 2120 | | - if(zRealType){ |
| 2121 | | - statsReportTimelineYFlag = zRealType; |
| 2122 | | - db_multi_exec("CREATE TEMP VIEW v_reports AS " |
| 2123 | | - "SELECT * FROM event WHERE type GLOB %Q", |
| 2124 | | - zRealType); |
| 2125 | | - }else{ |
| 2126 | | - statsReportTimelineYFlag = "a"; |
| 2127 | | - db_multi_exec("CREATE TEMP VIEW v_reports AS " |
| 2128 | | - "SELECT * FROM event"); |
| 2129 | | - } |
| 2130 | | - return statsReportType = rc; |
| 2131 | | -} |
| 2132 | | - |
| 2133 | | -/* |
| 2134 | | -** Returns a string suitable (for a given value of suitable) for |
| 2135 | | -** use in a label with the header of the /reports pages, dependent |
| 2136 | | -** on the 'type' flag. See stats_report_init_view(). |
| 2137 | | -** The returned bytes are static. |
| 2138 | | -*/ |
| 2139 | | -static const char *stats_report_label_for_type(){ |
| 2140 | | - assert( statsReportType && "Must call stats_report_init_view() first." ); |
| 2141 | | - switch( statsReportType ){ |
| 2142 | | - case 'c': |
| 2143 | | - return "checkins"; |
| 2144 | | - case 'e': |
| 2145 | | - return "events"; |
| 2146 | | - case 'w': |
| 2147 | | - return "wiki changes"; |
| 2148 | | - case 't': |
| 2149 | | - return "ticket changes"; |
| 2150 | | - case 'g': |
| 2151 | | - return "tag changes"; |
| 2152 | | - default: |
| 2153 | | - return "all types"; |
| 2154 | | - } |
| 2155 | | -} |
| 2156 | | - |
| 2157 | | -/* |
| 2158 | | -** A helper for the /reports family of pages which prints out a menu |
| 2159 | | -** of links for the various type=XXX flags. zCurrentViewName must be |
| 2160 | | -** the name/value of the 'view' parameter which is in effect at the |
| 2161 | | -** time this is called. e.g. if called from the 'byuser' view then |
| 2162 | | -** zCurrentViewName must be "byuser". Any URL parameters which need to |
| 2163 | | -** be added to the generated URLs should be passed in zParam. The |
| 2164 | | -** caller is expected to have already encoded any zParam in the %T or |
| 2165 | | -** %t encoding. */ |
| 2166 | | -static void stats_report_event_types_menu(const char *zCurrentViewName, |
| 2167 | | - const char *zParam){ |
| 2168 | | - char *zTop; |
| 2169 | | - if(zParam && !*zParam){ |
| 2170 | | - zParam = NULL; |
| 2171 | | - } |
| 2172 | | - zTop = mprintf("%s/reports?view=%s%s%s", g.zTop, zCurrentViewName, |
| 2173 | | - zParam ? "&" : "", zParam); |
| 2174 | | - cgi_printf("<div>"); |
| 2175 | | - cgi_printf("<span>Types:</span> "); |
| 2176 | | - if('*' == statsReportType){ |
| 2177 | | - cgi_printf(" <strong>all</strong>", zTop); |
| 2178 | | - }else{ |
| 2179 | | - cgi_printf(" <a href='%s'>all</a>", zTop); |
| 2180 | | - } |
| 2181 | | - if('c' == statsReportType){ |
| 2182 | | - cgi_printf(" <strong>checkins</strong>", zTop); |
| 2183 | | - }else{ |
| 2184 | | - cgi_printf(" <a href='%s&type=ci'>checkins</a>", zTop); |
| 2185 | | - } |
| 2186 | | - if('e' == statsReportType){ |
| 2187 | | - cgi_printf(" <strong>events</strong>", zTop); |
| 2188 | | - }else{ |
| 2189 | | - cgi_printf(" <a href='%s&type=e'>events</a>", zTop); |
| 2190 | | - } |
| 2191 | | - if( 't' == statsReportType ){ |
| 2192 | | - cgi_printf(" <strong>tickets</strong>", zTop); |
| 2193 | | - }else{ |
| 2194 | | - cgi_printf(" <a href='%s&type=t'>tickets</a>", zTop); |
| 2195 | | - } |
| 2196 | | - if( 'g' == statsReportType ){ |
| 2197 | | - cgi_printf(" <strong>tags</strong>", zTop); |
| 2198 | | - }else{ |
| 2199 | | - cgi_printf(" <a href='%s&type=g'>tags</a>", zTop); |
| 2200 | | - } |
| 2201 | | - if( 'w' == statsReportType ){ |
| 2202 | | - cgi_printf(" <strong>wiki</strong>", zTop); |
| 2203 | | - }else{ |
| 2204 | | - cgi_printf(" <a href='%s&type=w'>wiki</a>", zTop); |
| 2205 | | - } |
| 2206 | | - fossil_free(zTop); |
| 2207 | | - cgi_printf("</div>"); |
| 2208 | | -} |
| 2209 | | - |
| 2210 | | - |
| 2211 | | -/* |
| 2212 | | -** Helper for stats_report_by_month_year(), which generates a list of |
| 2213 | | -** week numbers. zTimeframe should be either a timeframe in the form YYYY |
| 2214 | | -** or YYYY-MM. |
| 2215 | | -*/ |
| 2216 | | -static void stats_report_output_week_links(const char *zTimeframe){ |
| 2217 | | - Stmt stWeek = empty_Stmt; |
| 2218 | | - char yearPart[5] = {0,0,0,0,0}; |
| 2219 | | - memcpy(yearPart, zTimeframe, 4); |
| 2220 | | - db_prepare(&stWeek, |
| 2221 | | - "SELECT DISTINCT strftime('%%W',mtime) AS wk, " |
| 2222 | | - "count(*) AS n, " |
| 2223 | | - "substr(date(mtime),1,%d) AS ym " |
| 2224 | | - "FROM v_reports " |
| 2225 | | - "WHERE ym=%Q AND mtime < current_timestamp " |
| 2226 | | - "GROUP BY wk ORDER BY wk", |
| 2227 | | - strlen(zTimeframe), |
| 2228 | | - zTimeframe); |
| 2229 | | - while( SQLITE_ROW == db_step(&stWeek) ){ |
| 2230 | | - const char *zWeek = db_column_text(&stWeek,0); |
| 2231 | | - const int nCount = db_column_int(&stWeek,1); |
| 2232 | | - cgi_printf("<a href='%s/timeline?" |
| 2233 | | - "yw=%t-%t&n=%d&y=%s'>%s</a>", |
| 2234 | | - g.zTop, yearPart, zWeek, |
| 2235 | | - nCount, statsReportTimelineYFlag, zWeek); |
| 2236 | | - } |
| 2237 | | - db_finalize(&stWeek); |
| 2238 | | -} |
| 2239 | | - |
| 2240 | | -/* |
| 2241 | | -** Implements the "byyear" and "bymonth" reports for /reports. |
| 2242 | | -** If includeMonth is true then it generates the "bymonth" report, |
| 2243 | | -** else the "byyear" report. If zUserName is not NULL and not empty |
| 2244 | | -** then the report is restricted to events created by the named user |
| 2245 | | -** account. |
| 2246 | | -*/ |
| 2247 | | -static void stats_report_by_month_year(char includeMonth, |
| 2248 | | - char includeWeeks, |
| 2249 | | - const char *zUserName){ |
| 2250 | | - Stmt query = empty_Stmt; |
| 2251 | | - int nRowNumber = 0; /* current TR number */ |
| 2252 | | - int nEventTotal = 0; /* Total event count */ |
| 2253 | | - int rowClass = 0; /* counter for alternating |
| 2254 | | - row colors */ |
| 2255 | | - Blob sql = empty_blob; /* SQL */ |
| 2256 | | - const char *zTimeLabel = includeMonth ? "Year/Month" : "Year"; |
| 2257 | | - char zPrevYear[5] = {0}; /* For keeping track of when |
| 2258 | | - we change years while looping */ |
| 2259 | | - int nEventsPerYear = 0; /* Total event count for the |
| 2260 | | - current year */ |
| 2261 | | - char showYearTotal = 0; /* Flag telling us when to show |
| 2262 | | - the per-year event totals */ |
| 2263 | | - Blob header = empty_blob; /* Page header text */ |
| 2264 | | - int nMaxEvents = 1; /* for calculating length of graph |
| 2265 | | - bars. */ |
| 2266 | | - int iterations = 0; /* number of weeks/months we iterate |
| 2267 | | - over */ |
| 2268 | | - stats_report_init_view(); |
| 2269 | | - stats_report_event_types_menu( includeMonth ? "bymonth" : "byyear", NULL ); |
| 2270 | | - blob_appendf(&header, "Timeline Events (%s) by year%s", |
| 2271 | | - stats_report_label_for_type(), |
| 2272 | | - (includeMonth ? "/month" : "")); |
| 2273 | | - blob_append_sql(&sql, |
| 2274 | | - "SELECT substr(date(mtime),1,%d) AS timeframe, " |
| 2275 | | - "count(*) AS eventCount " |
| 2276 | | - "FROM v_reports ", |
| 2277 | | - includeMonth ? 7 : 4); |
| 2278 | | - if(zUserName&&*zUserName){ |
| 2279 | | - blob_append_sql(&sql, " WHERE user=%Q ", zUserName); |
| 2280 | | - blob_appendf(&header," for user %q", zUserName); |
| 2281 | | - } |
| 2282 | | - blob_append(&sql, |
| 2283 | | - " GROUP BY timeframe" |
| 2284 | | - " ORDER BY timeframe DESC", |
| 2285 | | - -1); |
| 2286 | | - db_prepare(&query, "%s", blob_sql_text(&sql)); |
| 2287 | | - blob_reset(&sql); |
| 2288 | | - @ <h1>%b(&header)</h1> |
| 2289 | | - @ <table class='statistics-report-table-events' border='0' cellpadding='2' |
| 2290 | | - @ cellspacing='0' id='statsTable'> |
| 2291 | | - @ <thead> |
| 2292 | | - @ <th>%s(zTimeLabel)</th> |
| 2293 | | - @ <th>Events</th> |
| 2294 | | - @ <th width='90%%'><!-- relative commits graph --></th> |
| 2295 | | - @ </thead><tbody> |
| 2296 | | - blob_reset(&header); |
| 2297 | | - /* |
| 2298 | | - Run the query twice. The first time we calculate the maximum |
| 2299 | | - number of events for a given row. Maybe someone with better SQL |
| 2300 | | - Fu can re-implement this with a single query. |
| 2301 | | - */ |
| 2302 | | - while( SQLITE_ROW == db_step(&query) ){ |
| 2303 | | - const int nCount = db_column_int(&query, 1); |
| 2304 | | - if(nCount>nMaxEvents){ |
| 2305 | | - nMaxEvents = nCount; |
| 2306 | | - } |
| 2307 | | - ++iterations; |
| 2308 | | - } |
| 2309 | | - db_reset(&query); |
| 2310 | | - while( SQLITE_ROW == db_step(&query) ){ |
| 2311 | | - const char *zTimeframe = db_column_text(&query, 0); |
| 2312 | | - const int nCount = db_column_int(&query, 1); |
| 2313 | | - int nSize = nCount |
| 2314 | | - ? (int)(100 * nCount / nMaxEvents) |
| 2315 | | - : 1; |
| 2316 | | - showYearTotal = 0; |
| 2317 | | - if(!nSize) nSize = 1; |
| 2318 | | - if(includeMonth){ |
| 2319 | | - /* For Month/year view, add a separator for each distinct year. */ |
| 2320 | | - if(!*zPrevYear || |
| 2321 | | - (0!=fossil_strncmp(zPrevYear,zTimeframe,4))){ |
| 2322 | | - showYearTotal = *zPrevYear; |
| 2323 | | - if(showYearTotal){ |
| 2324 | | - rowClass = ++nRowNumber % 2; |
| 2325 | | - @ <tr class='row%d(rowClass)'> |
| 2326 | | - @ <td></td> |
| 2327 | | - @ <td colspan='2'>Yearly total: %d(nEventsPerYear)</td> |
| 2328 | | - @</tr> |
| 2329 | | - } |
| 2330 | | - nEventsPerYear = 0; |
| 2331 | | - memcpy(zPrevYear,zTimeframe,4); |
| 2332 | | - rowClass = ++nRowNumber % 2; |
| 2333 | | - @ <tr class='row%d(rowClass)'> |
| 2334 | | - @ <th colspan='3' class='statistics-report-row-year'>%s(zPrevYear)</th> |
| 2335 | | - @ </tr> |
| 2336 | | - } |
| 2337 | | - } |
| 2338 | | - rowClass = ++nRowNumber % 2; |
| 2339 | | - nEventTotal += nCount; |
| 2340 | | - nEventsPerYear += nCount; |
| 2341 | | - @<tr class='row%d(rowClass)'> |
| 2342 | | - @ <td> |
| 2343 | | - if(includeMonth){ |
| 2344 | | - cgi_printf("<a href='%s/timeline?" |
| 2345 | | - "ym=%t&n=%d&y=%s", |
| 2346 | | - g.zTop, zTimeframe, nCount, |
| 2347 | | - statsReportTimelineYFlag ); |
| 2348 | | - /* Reminder: n=nCount is not actually correct for bymonth unless |
| 2349 | | - that was the only user who caused events. |
| 2350 | | - */ |
| 2351 | | - if( zUserName && *zUserName ){ |
| 2352 | | - cgi_printf("&u=%t", zUserName); |
| 2353 | | - } |
| 2354 | | - cgi_printf("' target='_new'>%s</a>",zTimeframe); |
| 2355 | | - }else { |
| 2356 | | - cgi_printf("<a href='?view=byweek&y=%s&type=%c", |
| 2357 | | - zTimeframe, (char)statsReportType); |
| 2358 | | - if(zUserName && *zUserName){ |
| 2359 | | - cgi_printf("&u=%t", zUserName); |
| 2360 | | - } |
| 2361 | | - cgi_printf("'>%s</a>", zTimeframe); |
| 2362 | | - } |
| 2363 | | - @ </td><td>%d(nCount)</td> |
| 2364 | | - @ <td> |
| 2365 | | - @ <div class='statistics-report-graph-line' |
| 2366 | | - @ style='width:%d(nSize)%%;'> </div> |
| 2367 | | - @ </td> |
| 2368 | | - @</tr> |
| 2369 | | - if(includeWeeks){ |
| 2370 | | - /* This part works fine for months but it terribly slow (4.5s on my PC), |
| 2371 | | - so it's only shown for by-year for now. Suggestions/patches for |
| 2372 | | - a better/faster layout are welcomed. */ |
| 2373 | | - @ <tr class='row%d(rowClass)'> |
| 2374 | | - @ <td colspan='2' class='statistics-report-week-number-label'>Week #:</td> |
| 2375 | | - @ <td class='statistics-report-week-of-year-list'> |
| 2376 | | - stats_report_output_week_links(zTimeframe); |
| 2377 | | - @ </td></tr> |
| 2378 | | - } |
| 2379 | | - |
| 2380 | | - /* |
| 2381 | | - Potential improvement: calculate the min/max event counts and |
| 2382 | | - use percent-based graph bars. |
| 2383 | | - */ |
| 2384 | | - } |
| 2385 | | - db_finalize(&query); |
| 2386 | | - if(includeMonth && !showYearTotal && *zPrevYear){ |
| 2387 | | - /* Add final year total separator. */ |
| 2388 | | - rowClass = ++nRowNumber % 2; |
| 2389 | | - @ <tr class='row%d(rowClass)'> |
| 2390 | | - @ <td></td> |
| 2391 | | - @ <td colspan='2'>Yearly total: %d(nEventsPerYear)</td> |
| 2392 | | - @</tr> |
| 2393 | | - } |
| 2394 | | - @ </tbody></table> |
| 2395 | | - if(nEventTotal){ |
| 2396 | | - const char *zAvgLabel = includeMonth ? "month" : "year"; |
| 2397 | | - int nAvg = iterations ? (nEventTotal/iterations) : 0; |
| 2398 | | - @ <br><div>Total events: %d(nEventTotal) |
| 2399 | | - @ <br>Average per active %s(zAvgLabel): %d(nAvg) |
| 2400 | | - @ </div> |
| 2401 | | - } |
| 2402 | | - if( !includeMonth ){ |
| 2403 | | - output_table_sorting_javascript("statsTable","tnx",-1); |
| 2404 | | - } |
| 2405 | | -} |
| 2406 | | - |
| 2407 | | -/* |
| 2408 | | -** Implements the "byuser" view for /reports. |
| 2409 | | -*/ |
| 2410 | | -static void stats_report_by_user(){ |
| 2411 | | - Stmt query = empty_Stmt; |
| 2412 | | - int nRowNumber = 0; /* current TR number */ |
| 2413 | | - int nEventTotal = 0; /* Total event count */ |
| 2414 | | - int rowClass = 0; /* counter for alternating |
| 2415 | | - row colors */ |
| 2416 | | - int nMaxEvents = 1; /* max number of events for |
| 2417 | | - all rows. */ |
| 2418 | | - stats_report_init_view(); |
| 2419 | | - stats_report_event_types_menu("byuser", NULL); |
| 2420 | | - db_prepare(&query, |
| 2421 | | - "SELECT user, " |
| 2422 | | - "COUNT(*) AS eventCount " |
| 2423 | | - "FROM v_reports " |
| 2424 | | - "GROUP BY user ORDER BY eventCount DESC"); |
| 2425 | | - @ <h1>Timeline Events |
| 2426 | | - @ (%s(stats_report_label_for_type())) by User</h1> |
| 2427 | | - @ <table class='statistics-report-table-events' border='0' |
| 2428 | | - @ cellpadding='2' cellspacing='0' id='statsTable'> |
| 2429 | | - @ <thead><tr> |
| 2430 | | - @ <th>User</th> |
| 2431 | | - @ <th>Events</th> |
| 2432 | | - @ <th width='90%%'><!-- relative commits graph --></th> |
| 2433 | | - @ </tr></thead><tbody> |
| 2434 | | - while( SQLITE_ROW == db_step(&query) ){ |
| 2435 | | - const int nCount = db_column_int(&query, 1); |
| 2436 | | - if(nCount>nMaxEvents){ |
| 2437 | | - nMaxEvents = nCount; |
| 2438 | | - } |
| 2439 | | - } |
| 2440 | | - db_reset(&query); |
| 2441 | | - while( SQLITE_ROW == db_step(&query) ){ |
| 2442 | | - const char *zUser = db_column_text(&query, 0); |
| 2443 | | - const int nCount = db_column_int(&query, 1); |
| 2444 | | - int nSize = nCount |
| 2445 | | - ? (int)(100 * nCount / nMaxEvents) |
| 2446 | | - : 0; |
| 2447 | | - if(!nCount) continue /* arguable! Possible? */; |
| 2448 | | - else if(!nSize) nSize = 1; |
| 2449 | | - rowClass = ++nRowNumber % 2; |
| 2450 | | - nEventTotal += nCount; |
| 2451 | | - @<tr class='row%d(rowClass)'> |
| 2452 | | - @ <td> |
| 2453 | | - @ <a href="?view=bymonth&user=%h(zUser)&type=%c((char)statsReportType)">%h(zUser)</a> |
| 2454 | | - @ </td><td data-sortkey='%08x(-nCount)'>%d(nCount)</td> |
| 2455 | | - @ <td> |
| 2456 | | - @ <div class='statistics-report-graph-line' |
| 2457 | | - @ style='width:%d(nSize)%%;'> </div> |
| 2458 | | - @ </td> |
| 2459 | | - @</tr> |
| 2460 | | - /* |
| 2461 | | - Potential improvement: calculate the min/max event counts and |
| 2462 | | - use percent-based graph bars. |
| 2463 | | - */ |
| 2464 | | - } |
| 2465 | | - @ </tbody></table> |
| 2466 | | - db_finalize(&query); |
| 2467 | | - output_table_sorting_javascript("statsTable","tkx",2); |
| 2468 | | -} |
| 2469 | | - |
| 2470 | | -/* |
| 2471 | | -** Implements the "byweekday" view for /reports. |
| 2472 | | -*/ |
| 2473 | | -static void stats_report_day_of_week(){ |
| 2474 | | - Stmt query = empty_Stmt; |
| 2475 | | - int nRowNumber = 0; /* current TR number */ |
| 2476 | | - int nEventTotal = 0; /* Total event count */ |
| 2477 | | - int rowClass = 0; /* counter for alternating |
| 2478 | | - row colors */ |
| 2479 | | - int nMaxEvents = 1; /* max number of events for |
| 2480 | | - all rows. */ |
| 2481 | | - static const char *const daysOfWeek[] = { |
| 2482 | | - "Monday", "Tuesday", "Wednesday", "Thursday", |
| 2483 | | - "Friday", "Saturday", "Sunday" |
| 2484 | | - }; |
| 2485 | | - |
| 2486 | | - stats_report_init_view(); |
| 2487 | | - stats_report_event_types_menu("byweekday", NULL); |
| 2488 | | - db_prepare(&query, |
| 2489 | | - "SELECT cast(mtime %% 7 AS INTEGER) dow, " |
| 2490 | | - "COUNT(*) AS eventCount " |
| 2491 | | - "FROM v_reports " |
| 2492 | | - "GROUP BY dow ORDER BY dow"); |
| 2493 | | - @ <h1>Timeline Events |
| 2494 | | - @ (%s(stats_report_label_for_type())) by Day of the Week</h1> |
| 2495 | | - @ <table class='statistics-report-table-events' border='0' |
| 2496 | | - @ cellpadding='2' cellspacing='0' id='statsTable'> |
| 2497 | | - @ <thead><tr> |
| 2498 | | - @ <th>DoW</th> |
| 2499 | | - @ <th>Day</th> |
| 2500 | | - @ <th>Events</th> |
| 2501 | | - @ <th width='90%%'><!-- relative commits graph --></th> |
| 2502 | | - @ </tr></thead><tbody> |
| 2503 | | - while( SQLITE_ROW == db_step(&query) ){ |
| 2504 | | - const int nCount = db_column_int(&query, 1); |
| 2505 | | - if(nCount>nMaxEvents){ |
| 2506 | | - nMaxEvents = nCount; |
| 2507 | | - } |
| 2508 | | - } |
| 2509 | | - db_reset(&query); |
| 2510 | | - while( SQLITE_ROW == db_step(&query) ){ |
| 2511 | | - const int dayNum =db_column_int(&query, 0); |
| 2512 | | - const int nCount = db_column_int(&query, 1); |
| 2513 | | - int nSize = nCount |
| 2514 | | - ? (int)(100 * nCount / nMaxEvents) |
| 2515 | | - : 0; |
| 2516 | | - if(!nCount) continue /* arguable! Possible? */; |
| 2517 | | - else if(!nSize) nSize = 1; |
| 2518 | | - rowClass = ++nRowNumber % 2; |
| 2519 | | - nEventTotal += nCount; |
| 2520 | | - @<tr class='row%d(rowClass)'> |
| 2521 | | - @ <td>%d(dayNum)</td> |
| 2522 | | - @ <td>%s(daysOfWeek[dayNum])</td> |
| 2523 | | - @ <td>%d(nCount)</td> |
| 2524 | | - @ <td> |
| 2525 | | - @ <div class='statistics-report-graph-line' |
| 2526 | | - @ style='width:%d(nSize)%%;'> </div> |
| 2527 | | - @ </td> |
| 2528 | | - @</tr> |
| 2529 | | - } |
| 2530 | | - @ </tbody></table> |
| 2531 | | - db_finalize(&query); |
| 2532 | | - output_table_sorting_javascript("statsTable","ntnx",1); |
| 2533 | | -} |
| 2534 | | - |
| 2535 | | - |
| 2536 | | -/* |
| 2537 | | -** Helper for stats_report_by_month_year(), which generates a list of |
| 2538 | | -** week numbers. zTimeframe should be either a timeframe in the form YYYY |
| 2539 | | -** or YYYY-MM. |
| 2540 | | -*/ |
| 2541 | | -static void stats_report_year_weeks(const char *zUserName){ |
| 2542 | | - const char *zYear = P("y"); |
| 2543 | | - int nYear = zYear ? strlen(zYear) : 0; |
| 2544 | | - int i = 0; |
| 2545 | | - Stmt qYears = empty_Stmt; |
| 2546 | | - char *zDefaultYear = NULL; |
| 2547 | | - Blob sql = empty_blob; |
| 2548 | | - int nMaxEvents = 1; /* max number of events for |
| 2549 | | - all rows. */ |
| 2550 | | - int iterations = 0; /* # of active time periods. */ |
| 2551 | | - stats_report_init_view(); |
| 2552 | | - if(4==nYear){ |
| 2553 | | - Blob urlParams = empty_blob; |
| 2554 | | - blob_appendf(&urlParams, "y=%T", zYear); |
| 2555 | | - stats_report_event_types_menu("byweek", blob_str(&urlParams)); |
| 2556 | | - blob_reset(&urlParams); |
| 2557 | | - }else{ |
| 2558 | | - stats_report_event_types_menu("byweek", NULL); |
| 2559 | | - } |
| 2560 | | - blob_append(&sql, |
| 2561 | | - "SELECT DISTINCT substr(date(mtime),1,4) AS y " |
| 2562 | | - "FROM v_reports WHERE 1 ", -1); |
| 2563 | | - if(zUserName&&*zUserName){ |
| 2564 | | - blob_append_sql(&sql,"AND user=%Q ", zUserName); |
| 2565 | | - } |
| 2566 | | - blob_append(&sql,"GROUP BY y ORDER BY y", -1); |
| 2567 | | - db_prepare(&qYears, "%s", blob_sql_text(&sql)); |
| 2568 | | - blob_reset(&sql); |
| 2569 | | - cgi_printf("Select year: "); |
| 2570 | | - while( SQLITE_ROW == db_step(&qYears) ){ |
| 2571 | | - const char *zT = db_column_text(&qYears, 0); |
| 2572 | | - if( i++ ){ |
| 2573 | | - cgi_printf(" "); |
| 2574 | | - } |
| 2575 | | - cgi_printf("<a href='?view=byweek&y=%s&type=%c", zT, |
| 2576 | | - (char)statsReportType); |
| 2577 | | - if(zUserName && *zUserName){ |
| 2578 | | - cgi_printf("&user=%t",zUserName); |
| 2579 | | - } |
| 2580 | | - cgi_printf("'>%s</a>",zT); |
| 2581 | | - } |
| 2582 | | - db_finalize(&qYears); |
| 2583 | | - cgi_printf("<br/>"); |
| 2584 | | - if(!zYear || !*zYear){ |
| 2585 | | - zDefaultYear = db_text("????", "SELECT strftime('%%Y')"); |
| 2586 | | - zYear = zDefaultYear; |
| 2587 | | - nYear = 4; |
| 2588 | | - } |
| 2589 | | - if(4 == nYear){ |
| 2590 | | - Stmt stWeek = empty_Stmt; |
| 2591 | | - int rowCount = 0; |
| 2592 | | - int total = 0; |
| 2593 | | - Blob header = empty_blob; |
| 2594 | | - blob_appendf(&header, "Timeline events (%s) for the calendar weeks " |
| 2595 | | - "of %h", stats_report_label_for_type(), |
| 2596 | | - zYear); |
| 2597 | | - blob_append_sql(&sql, |
| 2598 | | - "SELECT DISTINCT strftime('%%W',mtime) AS wk, " |
| 2599 | | - "count(*) AS n " |
| 2600 | | - "FROM v_reports " |
| 2601 | | - "WHERE %Q=substr(date(mtime),1,4) " |
| 2602 | | - "AND mtime < current_timestamp ", |
| 2603 | | - zYear); |
| 2604 | | - if(zUserName&&*zUserName){ |
| 2605 | | - blob_append_sql(&sql, " AND user=%Q ", zUserName); |
| 2606 | | - blob_appendf(&header," for user %h", zUserName); |
| 2607 | | - } |
| 2608 | | - blob_append_sql(&sql, "GROUP BY wk ORDER BY wk DESC"); |
| 2609 | | - cgi_printf("<h1>%h</h1>", blob_str(&header)); |
| 2610 | | - blob_reset(&header); |
| 2611 | | - cgi_printf("<table class='statistics-report-table-events' " |
| 2612 | | - "border='0' cellpadding='2' width='100%%' " |
| 2613 | | - "cellspacing='0' id='statsTable'>"); |
| 2614 | | - cgi_printf("<thead><tr>" |
| 2615 | | - "<th>Week</th>" |
| 2616 | | - "<th>Events</th>" |
| 2617 | | - "<th width='90%%'><!-- relative commits graph --></th>" |
| 2618 | | - "</tr></thead>" |
| 2619 | | - "<tbody>"); |
| 2620 | | - db_prepare(&stWeek, "%s", blob_sql_text(&sql)); |
| 2621 | | - blob_reset(&sql); |
| 2622 | | - while( SQLITE_ROW == db_step(&stWeek) ){ |
| 2623 | | - const int nCount = db_column_int(&stWeek, 1); |
| 2624 | | - if(nCount>nMaxEvents){ |
| 2625 | | - nMaxEvents = nCount; |
| 2626 | | - } |
| 2627 | | - ++iterations; |
| 2628 | | - } |
| 2629 | | - db_reset(&stWeek); |
| 2630 | | - while( SQLITE_ROW == db_step(&stWeek) ){ |
| 2631 | | - const char *zWeek = db_column_text(&stWeek,0); |
| 2632 | | - const int nCount = db_column_int(&stWeek,1); |
| 2633 | | - int nSize = nCount |
| 2634 | | - ? (int)(100 * nCount / nMaxEvents) |
| 2635 | | - : 0; |
| 2636 | | - if(!nSize) nSize = 1; |
| 2637 | | - total += nCount; |
| 2638 | | - cgi_printf("<tr class='row%d'>", ++rowCount % 2 ); |
| 2639 | | - cgi_printf("<td><a href='%s/timeline?yw=%t-%s&n=%d&y=%s", |
| 2640 | | - g.zTop, zYear, zWeek, nCount, |
| 2641 | | - statsReportTimelineYFlag); |
| 2642 | | - if(zUserName && *zUserName){ |
| 2643 | | - cgi_printf("&u=%t",zUserName); |
| 2644 | | - } |
| 2645 | | - cgi_printf("'>%s</a></td>",zWeek); |
| 2646 | | - |
| 2647 | | - cgi_printf("<td>%d</td>",nCount); |
| 2648 | | - cgi_printf("<td>"); |
| 2649 | | - if(nCount){ |
| 2650 | | - cgi_printf("<div class='statistics-report-graph-line'" |
| 2651 | | - "style='width:%d%%;'> </div>", |
| 2652 | | - nSize); |
| 2653 | | - } |
| 2654 | | - cgi_printf("</td></tr>"); |
| 2655 | | - } |
| 2656 | | - db_finalize(&stWeek); |
| 2657 | | - free(zDefaultYear); |
| 2658 | | - cgi_printf("</tbody></table>"); |
| 2659 | | - if(total){ |
| 2660 | | - int nAvg = iterations ? (total/iterations) : 0; |
| 2661 | | - cgi_printf("<br><div>Total events: %d<br>" |
| 2662 | | - "Average per active week: %d</div>", |
| 2663 | | - total, nAvg); |
| 2664 | | - } |
| 2665 | | - output_table_sorting_javascript("statsTable","tnx",-1); |
| 2666 | | - } |
| 2667 | | -} |
| 2668 | | - |
| 2669 | | -/* |
| 2670 | | -** WEBPAGE: reports |
| 2671 | | -** |
| 2672 | | -** Shows activity reports for the repository. |
| 2673 | | -** |
| 2674 | | -** Query Parameters: |
| 2675 | | -** |
| 2676 | | -** view=REPORT_NAME Valid values: bymonth, byyear, byuser |
| 2677 | | -** user=NAME Restricts statistics to the given user |
| 2678 | | -** type=TYPE Restricts the report to a specific event type: |
| 2679 | | -** ci (checkin), w (wiki), t (ticket), g (tag) |
| 2680 | | -** Defaulting to all event types. |
| 2681 | | -** |
| 2682 | | -** The view-specific query parameters include: |
| 2683 | | -** |
| 2684 | | -** view=byweek: |
| 2685 | | -** |
| 2686 | | -** y=YYYY The year to report (default is the server's |
| 2687 | | -** current year). |
| 2688 | | -*/ |
| 2689 | | -void stats_report_page(){ |
| 2690 | | - HQuery url; /* URL for various branch links */ |
| 2691 | | - const char *zView = P("view"); /* Which view/report to show. */ |
| 2692 | | - const char *zUserName = P("user"); |
| 2693 | | - |
| 2694 | | - login_check_credentials(); |
| 2695 | | - if( !g.perm.Read ){ login_needed(); return; } |
| 2696 | | - if(!zUserName) zUserName = P("u"); |
| 2697 | | - url_initialize(&url, "reports"); |
| 2698 | | - if(zUserName && *zUserName){ |
| 2699 | | - url_add_parameter(&url,"user", zUserName); |
| 2700 | | - timeline_submenu(&url, "(Remove User Flag)", "view", zView, "user"); |
| 2701 | | - } |
| 2702 | | - timeline_submenu(&url, "By Year", "view", "byyear", 0); |
| 2703 | | - timeline_submenu(&url, "By Month", "view", "bymonth", 0); |
| 2704 | | - timeline_submenu(&url, "By Week", "view", "byweek", 0); |
| 2705 | | - timeline_submenu(&url, "By Weekday", "view", "byweekday", 0); |
| 2706 | | - timeline_submenu(&url, "By User", "view", "byuser", "user"); |
| 2707 | | - url_reset(&url); |
| 2708 | | - style_header("Activity Reports"); |
| 2709 | | - if(0==fossil_strcmp(zView,"byyear")){ |
| 2710 | | - stats_report_by_month_year(0, 0, zUserName); |
| 2711 | | - }else if(0==fossil_strcmp(zView,"bymonth")){ |
| 2712 | | - stats_report_by_month_year(1, 0, zUserName); |
| 2713 | | - }else if(0==fossil_strcmp(zView,"byweek")){ |
| 2714 | | - stats_report_year_weeks(zUserName); |
| 2715 | | - }else if(0==fossil_strcmp(zView,"byuser")){ |
| 2716 | | - stats_report_by_user(); |
| 2717 | | - }else if(0==fossil_strcmp(zView,"byweekday")){ |
| 2718 | | - stats_report_day_of_week(); |
| 2719 | | - }else{ |
| 2720 | | - @ <h1>Select a report to show:</h1> |
| 2721 | | - @ <ul> |
| 2722 | | - @ <li><a href='?view=byyear'>Events by year</a></li> |
| 2723 | | - @ <li><a href='?view=bymonth'>Events by month</a></li> |
| 2724 | | - @ <li><a href='?view=byweek'>Events by calendar week</a></li> |
| 2725 | | - @ <li><a href='?view=byweekday'>Events by day of the week</a></li> |
| 2726 | | - @ <li><a href='?view=byuser'>Events by user</a></li> |
| 2727 | | - @ </ul> |
| 2728 | | - } |
| 2729 | | - |
| 2730 | 2049 | style_footer(); |
| 2731 | 2050 | } |
| 2732 | 2051 | |