aboutsummaryrefslogtreecommitdiff
path: root/mod/mod-builds.cxx
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2017-05-16 23:27:53 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2017-05-19 11:44:48 +0300
commit4718a059f842a791c89a1922996bb8f1dbea8f65 (patch)
treea6dcc621f8287d444a699355f89b4a383eafd283 /mod/mod-builds.cxx
parente2264d6c34de011753913dd9b447b3d38649619c (diff)
Add filter form to builds page
Diffstat (limited to 'mod/mod-builds.cxx')
-rw-r--r--mod/mod-builds.cxx263
1 files changed, 249 insertions, 14 deletions
diff --git a/mod/mod-builds.cxx b/mod/mod-builds.cxx
index 6b6ab08..70ef605 100644
--- a/mod/mod-builds.cxx
+++ b/mod/mod-builds.cxx
@@ -11,8 +11,11 @@
#include <libbutl/timestamp.hxx> // to_string()
+#include <libbbot/manifest.hxx> // to_result_status(), to_string(result_status)
+
#include <web/xhtml.hxx>
#include <web/module.hxx>
+#include <web/mime-url-encoding.hxx>
#include <libbrep/build.hxx>
#include <libbrep/build-odb.hxx>
@@ -25,6 +28,7 @@
using namespace std;
using namespace bbot;
+using namespace web;
using namespace odb::core;
using namespace brep::cli;
@@ -60,12 +64,137 @@ init (scanner& s)
template <typename T, typename C>
static inline query<T>
-build_query (const C& configs)
+build_query (const C& configs, const brep::params::builds& params)
{
+ using namespace brep;
+
using query = query<T>;
- return query::id.configuration.in_range (configs.begin (), configs.end ()) &&
- (query::state == "testing" || query::state == "tested");
+ // Transform the wildcard to the LIKE-pattern.
+ //
+ auto transform = [] (const string& s) -> string
+ {
+ if (s.empty ())
+ return "%";
+
+ string r;
+ for (char c: s)
+ {
+ switch (c)
+ {
+ case '*': c = '%'; break;
+ case '?': c = '_'; break;
+ case '\\':
+ case '%':
+ case '_': r += '\\'; break;
+ }
+
+ r += c;
+ }
+
+ return r;
+ };
+
+ query q (
+ query::id.configuration.in_range (configs.begin (), configs.end ()) &&
+ (query::state == "testing" || query::state == "tested"));
+
+ // Note that there is no error reported if the filter parameters parsing
+ // fails. Instead, it is considered that no package builds match such a
+ // query.
+ //
+ try
+ {
+ // Package name.
+ //
+ if (!params.name ().empty ())
+ q = q && query::id.package.name.like (transform (params.name ()));
+
+ // Package version.
+ //
+ if (!params.version ().empty () && params.version () != "*")
+ {
+ version v (params.version ()); // May throw invalid_argument.
+ q = q && compare_version_eq (query::id.package.version, v, true);
+ }
+
+ // Build toolchain name/version.
+ //
+ const string& tc (params.toolchain ());
+
+ if (tc != "*")
+ {
+ size_t p (tc.find ('-'));
+ if (p == string::npos) // Invalid format.
+ throw invalid_argument ("");
+
+ string tn (tc, 0, p);
+ version tv (string (tc, p + 1)); // May throw invalid_argument.
+
+ q = q && query::toolchain_name == tn &&
+ compare_version_eq (query::id.toolchain_version, tv, true);
+ }
+
+ // Build configuration name.
+ //
+ if (!params.configuration ().empty ())
+ q = q && query::id.configuration.like (
+ transform (params.configuration ()));
+
+ // Build machine name.
+ //
+ if (!params.machine ().empty ())
+ query::machine.like (transform (params.machine ()));
+
+ // Build target.
+ //
+ const string& tg (params.target ());
+
+ if (tg != "*")
+ q = q && (tg.empty ()
+ ? query::target.is_null ()
+ : query::target.like (transform (tg)));
+
+ // Build result.
+ //
+ const string& rs (params.result ());
+
+ if (!rs.empty () && rs != "*")
+ {
+ if (rs == "pending")
+ q = q && query::forced;
+ else if (rs == "building")
+ q = q && query::state == "testing";
+ else
+ {
+ query sq (query::status == rs);
+ result_status st (to_result_status(rs)); // May throw invalid_argument.
+
+ if (st != result_status::success)
+ {
+ auto next = [&st] () -> bool
+ {
+ if (st == result_status::abnormal)
+ return false;
+
+ st = static_cast<result_status> (static_cast<uint8_t> (st) + 1);
+ return true;
+ };
+
+ while (next ())
+ sq = sq || query::status == to_string (st);
+ }
+
+ q = q && sq;
+ }
+ }
+ }
+ catch (const invalid_argument&)
+ {
+ return query (false);
+ }
+
+ return q;
}
bool brep::builds::
@@ -104,6 +233,15 @@ handle (request& rq, response& rs)
<< HEAD
<< TITLE << title << ~TITLE
<< CSS_LINKS (path ("builds.css"), root)
+ //
+ // This hack is required to avoid the "flash of unstyled content", which
+ // happens due to the presence of the autofocus attribute in the input
+ // element of the search form. The problem appears in Firefox and has a
+ // (4-year old, at the time of this writing) bug report:
+ //
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=712130.
+ //
+ << SCRIPT << " " << ~SCRIPT
<< ~HEAD
<< BODY
<< DIV_HEADER (root, options_->logo (), options_->menu ())
@@ -119,28 +257,103 @@ handle (request& rq, response& rs)
//
auto count (
build_db_->query_value<build_count> (
- build_query<build_count> (*build_conf_names_)));
+ build_query<build_count> (*build_conf_names_, params)));
- s << DIV_COUNTER (count, "Build", "Builds");
+ // Print the package builds filter form on the first page only.
+ //
+ if (page == 0)
+ {
+ // Populate the toolchains list with the distinct list of toolchain
+ // name/version pairs from all the existing package builds. Make sure the
+ // selected toolchain still present in the database. Otherwise fallback to
+ // the * wildcard selection.
+ //
+ string tc ("*");
+ vector<pair<string,string>> toolchains ({{"*", "*"}});
+ {
+ using query = query<toolchain>;
+
+ for (const auto& t: build_db_->query<toolchain> (
+ "ORDER BY" + query::toolchain_name +
+ order_by_version_desc (query::id.toolchain_version, false)))
+ {
+ string s (t.name + '-' + t.version.string ());
+ toolchains.emplace_back (s, s);
+
+ if (s == params.toolchain ())
+ tc = move (s);
+ }
+ }
+
+ // The 'action' attribute is optional in HTML5. While the standard doesn't
+ // specify browser behavior explicitly for the case the attribute is
+ // omitted, the only reasonable behavior is to default it to the current
+ // document URL. Note that we specify the function name using the "hidden"
+ // <input/> element since the action url must not contain the query part.
+ //
+ s << FORM
+ << *INPUT(TYPE="hidden", NAME="builds")
+ << TABLE(ID="filter", CLASS="proplist")
+ << TBODY
+ << TR_INPUT ("name", "pn", params.name (), "*", true)
+ << TR_INPUT ("version", "pv", params.version (), "*")
+ << TR_SELECT ("toolchain", "tc", tc, toolchains)
+
+ << TR(CLASS="config")
+ << TH << "config" << ~TH
+ << TD
+ << *INPUT(TYPE="text",
+ NAME="cf",
+ VALUE=params.configuration (),
+ PLACEHOLDER="*",
+ LIST="configs")
+ << DATALIST(ID="configs")
+ << *OPTION(VALUE="*");
+
+ for (const auto& c: *build_conf_names_)
+ s << *OPTION(VALUE=c);
+
+ s << ~DATALIST
+ << ~TD
+ << ~TR
+
+ << TR_INPUT ("machine", "mn", params.machine (), "*")
+ << TR_INPUT ("target", "tg", params.target (), "<default>")
+ << TR_INPUT ("result", "rs", params.result (), "*")
+ << ~TBODY
+ << ~TABLE
+ << TABLE(CLASS="form-table")
+ << TBODY
+ << TR
+ << TD(ID="build-count")
+ << DIV_COUNTER (count, "Build", "Builds")
+ << ~TD
+ << TD(ID="filter-btn")
+ << *INPUT(TYPE="submit", VALUE="Filter")
+ << ~TD
+ << ~TR
+ << ~TBODY
+ << ~TABLE
+ << ~FORM;
+ }
+ else
+ s << DIV_COUNTER (count, "Build", "Builds");
// Enclose the subsequent tables to be able to use nth-child CSS selector.
//
s << DIV;
for (auto& b: build_db_->query<build> (
- build_query<build> (*build_conf_names_) +
+ build_query<build> (*build_conf_names_, params) +
"ORDER BY" + query<build>::timestamp + "DESC" +
"OFFSET" + to_string (page * page_configs) +
"LIMIT" + to_string (page_configs)))
{
assert (b.machine);
- auto i (build_conf_map_->find (b.configuration.c_str ()));
- assert (i != build_conf_map_->end ());
- const build_config& c (*i->second);
-
string ts (butl::to_string (b.timestamp,
"%Y-%m-%d %H:%M:%S%[.N] %Z",
- true, true));
+ true,
+ true));
s << TABLE(CLASS="proplist build")
<< TBODY
@@ -151,7 +364,7 @@ handle (request& rq, response& rs)
b.toolchain_version.string ())
<< TR_VALUE ("config", b.configuration)
<< TR_VALUE ("machine", *b.machine)
- << TR_VALUE ("target", c.target ? c.target->string () : "<default>")
+ << TR_VALUE ("target", b.target ? b.target->string () : "<default>")
<< TR_VALUE ("timestamp", ts)
<< TR(CLASS="result")
<< TH << "result" << ~TH
@@ -220,8 +433,30 @@ handle (request& rq, response& rs)
t.commit ();
- s << DIV_PAGER (page, count, page_configs, options_->build_pages (),
- root.string () + "?builds")
+ string u (root.string () + "?builds");
+
+ auto add_filter = [&u] (const char* pn,
+ const string& pv,
+ const char* def = "")
+ {
+ if (pv != def)
+ {
+ u += '&';
+ u += pn;
+ u += '=';
+ u += mime_url_encode (pv);
+ }
+ };
+
+ add_filter ("pn", params.name ());
+ add_filter ("pv", params.version ());
+ add_filter ("tc", params.toolchain (), "*");
+ add_filter ("cf", params.configuration ());
+ add_filter ("mn", params.machine ());
+ add_filter ("tg", params.target (), "*");
+ add_filter ("rs", params.result ());
+
+ s << DIV_PAGER (page, count, page_configs, options_->build_pages (), u)
<< ~DIV
<< ~BODY
<< ~HTML;