From 1367aa09951e0aa7491bc2a5bf7209b0b47be65e Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 13 Sep 2018 19:27:00 +0300 Subject: Add support for packages and builds global views --- etc/brep-module.conf | 6 +++++ libbrep/package-extra.sql | 21 ++++++++------- libbrep/package.cxx | 54 +++++++++++++++++++------------------ libbrep/package.hxx | 4 ++- mod/mod-build-log.cxx | 7 +++-- mod/mod-builds.cxx | 68 ++++++++++++++++++++++++++++++++--------------- mod/mod-packages.cxx | 35 ++++++++++++++++-------- mod/options.cli | 7 +++++ mod/page.cxx | 18 +++++++++++++ mod/page.hxx | 23 ++++++++++++++++ www/builds-body.css | 3 ++- www/packages-body.css | 3 ++- 12 files changed, 176 insertions(+), 73 deletions(-) diff --git a/etc/brep-module.conf b/etc/brep-module.conf index 5602d61..b4c0ee4 100644 --- a/etc/brep-module.conf +++ b/etc/brep-module.conf @@ -326,6 +326,12 @@ menu About=?about # root-tenant-view packages +# Name to call the tenant values on web pages. If not specified, then 'tenant' +# is used. +# +# tenant-name tenant + + # Trace verbosity. Disabled by default. # # verbosity 0 diff --git a/libbrep/package-extra.sql b/libbrep/package-extra.sql index d9930aa..93c61d8 100644 --- a/libbrep/package-extra.sql +++ b/libbrep/package-extra.sql @@ -30,7 +30,8 @@ DROP FUNCTION IF EXISTS latest_packages(IN tenant TEXT); DROP TYPE IF EXISTS weighted_text CASCADE; CREATE TYPE weighted_text AS (a TEXT, b TEXT, c TEXT, d TEXT); --- Return the latest versions of internal packages as a set of package rows. +-- Return the latest versions of matching a tenant internal packages as a set +-- of package rows. If tenant is NULL, then match all tenants. -- CREATE FUNCTION latest_packages(IN tenant TEXT) @@ -49,7 +50,7 @@ RETURNS SETOF package AS $$ p1.version_canonical_release = p2.version_canonical_release AND p1.version_revision < p2.version_revision)))) WHERE - p1.tenant = latest_packages.tenant AND + (latest_packages.tenant IS NULL OR p1.tenant = latest_packages.tenant) AND p1.internal_repository_canonical_name IS NOT NULL AND p2.name IS NULL; $$ LANGUAGE SQL STABLE; @@ -72,10 +73,10 @@ RETURNS SETOF record AS $$ WHERE name = latest_package.name; $$ LANGUAGE SQL STABLE; --- Search for the latest version of an internal packages matching the specified --- search query. Return a set of rows containing the package id and search --- rank. If query is NULL, then match all packages and return 0 rank for --- all rows. +-- Search for the latest version of an internal packages matching the +-- specified search query and tenant. Return a set of rows containing the +-- package id and search rank. If query is NULL, then match all packages and +-- return 0 rank for all rows. If tenant is NULL, then match all tenants. -- CREATE FUNCTION search_latest_packages(IN query tsquery, @@ -98,10 +99,10 @@ RETURNS SETOF record AS $$ WHERE query IS NULL OR search_index @@ query; $$ LANGUAGE SQL STABLE; --- Search for packages matching the search query and having the specified --- tenant and name. Return a set of rows containing the package id and search +-- Search for packages matching the search query and tenant and having the +-- specified name. Return a set of rows containing the package id and search -- rank. If query is NULL, then match all packages and return 0 rank for all --- rows. +-- rows. If tenant is NULL, then match all tenants. -- CREATE FUNCTION search_packages(IN query tsquery, @@ -122,7 +123,7 @@ RETURNS SETOF record AS $$ END AS rank FROM package WHERE - tenant = search_packages.tenant AND + (search_packages.tenant IS NULL OR tenant = search_packages.tenant) AND name = search_packages.name AND internal_repository_canonical_name IS NOT NULL AND (query IS NULL OR search_index @@ query); diff --git a/libbrep/package.cxx b/libbrep/package.cxx index 41dd4e2..e3921fe 100644 --- a/libbrep/package.cxx +++ b/libbrep/package.cxx @@ -64,32 +64,33 @@ namespace brep optional fr, optional sh, shared_ptr rp) - : id (rp->tenant, move (nm), vr), - name (id.name), - version (move (vr)), - project (move (pn)), - priority (move (pr)), - summary (move (sm)), - license_alternatives (move (la)), - tags (move (tg)), - description (move (ds)), - changes (move (ch)), - url (move (ur)), - doc_url (move (du)), - src_url (move (su)), - package_url (move (pu)), - email (move (em)), - package_email (move (pe)), - build_email (move (be)), - dependencies (move (dp)), - requirements (move (rq)), - build_constraints (version.compare (wildcard_version, true) != 0 - ? move (bc) - : build_constraints_type ()), - internal_repository (move (rp)), - location (move (lc)), - fragment (move (fr)), - sha256sum (move (sh)) + : id (rp->tenant, move (nm), vr), + tenant (id.tenant), + name (id.name), + version (move (vr)), + project (move (pn)), + priority (move (pr)), + summary (move (sm)), + license_alternatives (move (la)), + tags (move (tg)), + description (move (ds)), + changes (move (ch)), + url (move (ur)), + doc_url (move (du)), + src_url (move (su)), + package_url (move (pu)), + email (move (em)), + package_email (move (pe)), + build_email (move (be)), + dependencies (move (dp)), + requirements (move (rq)), + build_constraints (version.compare (wildcard_version, true) != 0 + ? move (bc) + : build_constraints_type ()), + internal_repository (move (rp)), + location (move (lc)), + fragment (move (fr)), + sha256sum (move (sh)) { assert (internal_repository->internal); } @@ -99,6 +100,7 @@ namespace brep version_type vr, shared_ptr rp) : id (rp->tenant, move (nm), vr), + tenant (id.tenant), name (id.name), version (move (vr)) { diff --git a/libbrep/package.hxx b/libbrep/package.hxx index 6e372b3..fbe6f60 100644 --- a/libbrep/package.hxx +++ b/libbrep/package.hxx @@ -346,6 +346,7 @@ namespace brep // package_id id; + const string& tenant; // Tracks id.tenant. const package_name& name; // Tracks id.name. upstream_version version; @@ -395,6 +396,7 @@ namespace brep // Database mapping. // #pragma db member(id) id column("") + #pragma db member(tenant) transient #pragma db member(name) transient #pragma db member(version) set(this.version.init (this.id.version, (?))) @@ -476,7 +478,7 @@ namespace brep private: friend class odb::access; - package (): name (id.name) {} + package (): tenant (id.tenant), name (id.name) {} // Save keywords, summary, description, and changes to weighted_text // a, b, c, d members, respectively. So a word found in keywords will diff --git a/mod/mod-build-log.cxx b/mod/mod-build-log.cxx index 70e2c7e..ee5d1b2 100644 --- a/mod/mod-build-log.cxx +++ b/mod/mod-build-log.cxx @@ -212,10 +212,13 @@ handle (request& rq, response& rs) // ostream& os (rs.content (200, "text/plain;charset=utf-8", false)); - auto print_header = [&os, &b] () + auto print_header = [&os, &b, this] () { - // @@ Should we print the tenant? How to call it if that's the case? + // Print the build tenant in the multi-tenant mode. // + if (!b->tenant.empty ()) + os << options_->tenant_name () << ": " << b->tenant << endl << endl; + os << "package: " << b->package_name << endl << "version: " << b->package_version << endl << "toolchain: " << b->toolchain_name << '-' << b->toolchain_version diff --git a/mod/mod-builds.cxx b/mod/mod-builds.cxx index d2e3a25..a55b6f9 100644 --- a/mod/mod-builds.cxx +++ b/mod/mod-builds.cxx @@ -94,7 +94,7 @@ template static inline query build_query (const brep::cstrings& configs, const brep::params::builds& params, - const brep::optional& tenant) + const brep::optional& tenant) { using namespace brep; using query = query; @@ -207,12 +207,16 @@ build_query (const brep::cstrings& configs, template ::build_package> static inline query -package_query (const brep::params::builds& params, const string& tenant) +package_query (const brep::params::builds& params, + const brep::optional& tenant) { using namespace brep; using query = query; - query q (P::id.tenant == tenant); + query q (true); + + if (tenant) + q = q && P::id.tenant == *tenant; // Note that there is no error reported if the filter parameters parsing // fails. Instead, it is considered that no packages match such a query. @@ -281,6 +285,7 @@ handle (request& rq, response& rs) const size_t page_configs (options_->build_configurations ()); const string& host (options_->host ()); const dir_path& root (options_->root ()); + const string& tenant_name (options_->tenant_name ()); params::builds params; @@ -321,18 +326,25 @@ handle (request& rq, response& rs) << DIV_HEADER (options_->logo (), options_->menu (), root, tenant) << DIV(ID="content"); + // If the tenant is empty then we are in the global view and will display + // builds from all the tenants. + // + optional tn; + if (!tenant.empty ()) + tn = tenant; + // Return the list of distinct toolchain name/version pairs. The build db // transaction must be started. // using toolchains = vector>; - auto query_toolchains = [this] () -> toolchains + auto query_toolchains = [this, &tn] () -> toolchains { using query = query; toolchains r; for (auto& t: build_db_->query ( - (query::id.package.tenant == tenant) + + (tn ? query::id.package.tenant == *tn : query (true)) + "ORDER BY" + query::toolchain_name + order_by_version_desc (query::id.toolchain_version, false))) r.emplace_back (move (t.name), move (t.version)); @@ -428,7 +440,7 @@ handle (request& rq, response& rs) transaction t (build_db_->begin ()); count = build_db_->query_value ( - build_query (*build_conf_names_, params, tenant)); + build_query (*build_conf_names_, params, tn)); // Print the filter form. // @@ -443,7 +455,7 @@ handle (request& rq, response& rs) // s << DIV; for (auto& pb: build_db_->query ( - build_query (*build_conf_names_, params, tenant) + + build_query (*build_conf_names_, params, tn) + "ORDER BY" + query::build::timestamp + "DESC" + "OFFSET" + to_string (page * page_configs) + "LIMIT" + to_string (page_configs))) @@ -461,8 +473,8 @@ handle (request& rq, response& rs) s << TABLE(CLASS="proplist build") << TBODY - << TR_NAME (b.package_name, string (), root, tenant) - << TR_VERSION (b.package_name, b.package_version, root, tenant) + << TR_NAME (b.package_name, string (), root, b.tenant) + << TR_VERSION (b.package_name, b.package_version, root, b.tenant) << TR_VALUE ("toolchain", b.toolchain_name + '-' + b.toolchain_version.string ()) @@ -470,8 +482,15 @@ handle (request& rq, response& rs) << TR_VALUE ("machine", b.machine) << TR_VALUE ("target", b.target.string ()) << TR_VALUE ("timestamp", ts) - << TR_BUILD_RESULT (b, host, root) - << ~TBODY + << TR_BUILD_RESULT (b, host, root); + + // In the global view mode add the tenant builds link. Note that the + // global view (and the link) makes sense only in the multi-tenant mode. + // + if (!tn && !b.tenant.empty ()) + s << TR_TENANT (tenant_name, "builds", root, b.tenant); + + s << ~TBODY << ~TABLE; } s << ~DIV; @@ -589,12 +608,12 @@ handle (request& rq, response& rs) size_t nmax ( config_toolchains.size () * build_db_->query_value ( - package_query (params, tenant))); + package_query (params, tn))); size_t ncur = build_db_->query_value ( build_query (*build_conf_names_, bld_params, - tenant)); + tn)); // From now we will be using specific package name and version for each // build database query. @@ -641,7 +660,7 @@ handle (request& rq, response& rs) // using query = query; query q (package_query (params, - tenant)); + tn)); for (const auto& p: build_db_->query (q)) { @@ -696,18 +715,17 @@ handle (request& rq, response& rs) using pkg_query = query; using prep_pkg_query = prepared_query; - pkg_query pq (package_query (params, tenant)); + pkg_query pq (package_query (params, tn)); // Specify the portion. Note that we will still be querying packages in // chunks, not to hold locks for too long. // size_t offset (0); - // @@ TENANT: use tenant for sorting when add support for global view. - // pq += "ORDER BY" + pkg_query::build_package::id.name + order_by_version_desc (pkg_query::build_package::id.version, false) + + "," + pkg_query::build_package::id.tenant + "OFFSET" + pkg_query::_ref (offset) + "LIMIT 50"; connection_ptr conn (build_db_->connection ()); @@ -834,14 +852,22 @@ handle (request& rq, response& rs) s << TABLE(CLASS="proplist build") << TBODY - << TR_NAME (id.name, string (), root, tenant) - << TR_VERSION (id.name, p.version, root, tenant) + << TR_NAME (id.name, string (), root, id.tenant) + << TR_VERSION (id.name, p.version, root, id.tenant) << TR_VALUE ("toolchain", string (ct.toolchain_name) + '-' + ct.toolchain_version.string ()) << TR_VALUE ("config", ct.configuration) - << TR_VALUE ("target", i->second->target.string ()) - << ~TBODY + << TR_VALUE ("target", i->second->target.string ()); + + // In the global view mode add the tenant builds link. Note that + // the global view (and the link) makes sense only in the + // multi-tenant mode. + // + if (!tn && !id.tenant.empty ()) + s << TR_TENANT (tenant_name, "builds", root, id.tenant); + + s << ~TBODY << ~TABLE; if (--print == 0) // Bail out the configuration loop. diff --git a/mod/mod-packages.cxx b/mod/mod-packages.cxx index 1515a53..27e1270 100644 --- a/mod/mod-packages.cxx +++ b/mod/mod-packages.cxx @@ -67,7 +67,7 @@ init (scanner& s) template static inline query -search_param (const brep::string& q, const brep::string& t) +search_param (const brep::string& q, const brep::optional& t) { using query = query; return "(" + @@ -75,7 +75,7 @@ search_param (const brep::string& q, const brep::string& t) ? query ("NULL") : "plainto_tsquery (" + query::_val (q) + ")") + "," + - query::_val (t) + + (!t ? query ("NULL") : query (query::_val (*t))) + ")"; } @@ -89,6 +89,7 @@ handle (request& rq, response& rs) const size_t res_page (options_->search_results ()); const dir_path& root (options_->root ()); const string& title (options_->search_title ()); + const string& tenant_name (options_->tenant_name ()); params::packages params; @@ -135,25 +136,30 @@ handle (request& rq, response& rs) << DIV_HEADER (options_->logo (), options_->menu (), root, tenant) << DIV(ID="content"); + // If the tenant is empty then we are in the global view and will display + // packages from all the tenants. + // + optional tn; + if (!tenant.empty ()) + tn = tenant; + session sn; transaction t (package_db_->begin ()); auto pkg_count ( package_db_->query_value ( - search_param (squery, tenant))); + search_param (squery, tn))); s << FORM_SEARCH (squery, "packages") << DIV_COUNTER (pkg_count, "Package", "Packages"); // Enclose the subsequent tables to be able to use nth-child CSS selector. // - // @@ TENANT: use tenant for sorting when add support for global view. - // s << DIV; for (const auto& pr: package_db_->query ( - search_param (squery, tenant) + - "ORDER BY rank DESC, name" + + search_param (squery, tn) + + "ORDER BY rank DESC, name, tenant" + "OFFSET" + to_string (page * res_page) + "LIMIT" + to_string (res_page))) { @@ -161,13 +167,20 @@ handle (request& rq, response& rs) s << TABLE(CLASS="proplist package") << TBODY - << TR_NAME (p->name, equery, root, tenant) + << TR_NAME (p->name, equery, root, p->tenant) << TR_SUMMARY (p->summary) << TR_LICENSE (p->license_alternatives) << TR_TAGS (p->project, p->tags, root, tenant) - << TR_DEPENDS (p->dependencies, root, tenant) - << TR_REQUIRES (p->requirements) - << ~TBODY + << TR_DEPENDS (p->dependencies, root, p->tenant) + << TR_REQUIRES (p->requirements); + + // In the global view mode add the tenant packages link. Note that the + // global view (and the link) makes sense only in the multi-tenant mode. + // + if (!tn && !p->tenant.empty ()) + s << TR_TENANT (tenant_name, "packages", root, p->tenant); + + s << ~TBODY << ~TABLE; } s << ~DIV; diff --git a/mod/options.cli b/mod/options.cli index 5fbd117..c78af73 100644 --- a/mod/options.cli +++ b/mod/options.cli @@ -46,6 +46,13 @@ namespace brep (\cb{http://example.org/})." } + string tenant-name = "tenant" + { + "", + "Name to call the tenant values on web pages. If not specified, then + \cb{tenant} is used." + } + uint16_t verbosity = 0 { "", diff --git a/mod/page.cxx b/mod/page.cxx index ce12da6..1b2faae 100644 --- a/mod/page.cxx +++ b/mod/page.cxx @@ -182,6 +182,24 @@ namespace brep << ~TR; } + // TR_TENANT + // + void TR_TENANT:: + operator() (serializer& s) const + { + s << TR(CLASS="tenant") + << TH << name_ << ~TH + << TD + << SPAN(CLASS="value") + << A + << HREF << tenant_dir (root_, tenant_) << '?' << service_ << ~HREF + << tenant_ + << ~A + << ~SPAN + << ~TD + << ~TR; + } + // TR_NAME // void TR_NAME:: diff --git a/mod/page.hxx b/mod/page.hxx index d9f4249..4b59e7d 100644 --- a/mod/page.hxx +++ b/mod/page.hxx @@ -162,6 +162,29 @@ namespace brep const vector>& options_; }; + // Generates tenant id element. + // + // Displays a link to the service page for the specified tenant. + // + class TR_TENANT + { + public: + TR_TENANT (const string& n, + const string& s, + const dir_path& r, + const string& t) + : name_ (n), service_ (s), root_ (r), tenant_ (t) {} + + void + operator() (xml::serializer&) const; + + private: + const string& name_; + const string& service_; + const dir_path& root_; + const string& tenant_; + }; + // Generates package name element with an optional search criteria. The // search string should be url-encoded, if specified. // diff --git a/www/builds-body.css b/www/builds-body.css index ef641e8..6c27b09 100644 --- a/www/builds-body.css +++ b/www/builds-body.css @@ -47,7 +47,8 @@ .build tr.machine td .value, .build tr.target td .value, .build tr.timestamp td .value, -.build tr.result td .value +.build tr.result td .value, +.build tr.tenant td .value { /* style. */ font-family: monospace; diff --git a/www/packages-body.css b/www/packages-body.css index df77d14..79911d4 100644 --- a/www/packages-body.css +++ b/www/packages-body.css @@ -33,7 +33,8 @@ .package tr.name td .value, .package tr.depends td .value, -.package tr.requires td .value +.package tr.requires td .value, +.package tr.tenant td .value { /* style. */ font-family: monospace; -- cgit v1.1