runbot/runbot/templates/utils.xml
Xavier-Do dff50b49c4 [IMP] runbot: Improve stats listing display
Sort trigger and group them by category, better selection of categories
to display on stats page
2024-09-06 09:57:45 +02:00

399 lines
26 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- base layout -->
<template id="runbot.base_page">
<html t-att-data-bs-theme="theme">
<head>
<title t-esc="title or 'Runbot'"/>
<link rel="stylesheet" type="text/css" href="/runbot/static/src/libs/bootstrap/css/bootstrap.css"/>
<link rel="stylesheet" type="text/css" href="/runbot/static/src/libs/fontawesome/css/font-awesome.css"/>
<link rel="stylesheet" type="text/css" href="/runbot/static/src/css/runbot.css"/>
<script src="/runbot/static/src/libs/jquery/jquery.js" type="text/javascript"/>
<script type="text/javascript" src="/runbot/static/src/libs/popper/popper.js"/>
<script type="text/javascript" src="/runbot/static/src/libs/bootstrap/js/bootstrap.bundle.js"/>
<script type="text/javascript" src="/runbot/static/src/js/runbot.js"/>
<t t-if="refresh">
<meta http-equiv="refresh" t-att-content="refresh"/>
</t>
</head>
<body>
<t t-out="0"/>
</body>
</html>
</template>
<template id="runbot.layout" inherit_id="runbot.base_page" primary="True">
<xpath expr="//body" position="replace">
<body>
<header>
<nav class="navbar navbar-expand-md bg-body-tertiary">
<a t-if="project" t-att-href="qu(search=search)">
<b class="active_project">
<t t-esc="project.name"/>
</b>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#top_menu_collapse">
<span class="navbar-toggler-icon"/>
</button>
<div class="collapse navbar-collapse" id="top_menu_collapse">
<ul class="nav navbar-nav ms-auto text-end" id="top_menu">
<t t-if="projects">
<t t-foreach="projects" t-as="l_project">
<li class="nav-item">
<a class="nav-link" t-att-href="qu('/runbot/%s' % slug(l_project), search=search)">
<t t-esc="l_project.name"/>
</a>
</li>
</t>
</t>
<li class="nav-item divider"/>
<li class="nav-item dropdown">
<a data-bs-toggle="collapse" href="#collapsePreference" role="button" class="nav-link">
<i class="fa fa-gear"/>
</a>
</li>
<li class="nav-item divider" t-ignore="true"/>
<t t-if="not user_id._is_public()">
<t t-call="runbot.build_errors_link"/>
<li class="nav-item dropdown" t-ignore="true">
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">
<b>
<span t-esc="user_id.name[:23] + '...' if user_id.name and len(user_id.name) &gt; 25 else user_id.name"/>
</b>
</a>
<div class="dropdown-menu dropdown-menu-end js_usermenu" role="menu">
<a class="dropdown-item" id="o_logout" role="menuitem" t-attf-href="/web/session/logout?redirect=/">Logout</a>
<a class="dropdown-item" role="menuitem" t-attf-href="/web">Web</a>
<div t-if="user_id.runbot_team_ids" class="dropdown-divider"/>
<div t-if="user_id.runbot_team_ids" class="dropdown-header">Teams</div>
<a t-foreach="user_id.runbot_team_ids" t-as="team" class="dropdown-item" role="menuitem" t-attf-href="/runbot/teams/{{team.id}}">
<t t-esc="team.name.capitalize()"/>
</a>
</div>
</li>
</t>
<t t-else="">
<li class="nav-item dropdown" t-ignore="true">
<b>
<a class="nav-link" t-attf-href="/web/login?redirect={{request.httprequest.path}}">Login</a>
</b>
</li>
</t>
</ul>
<t t-out="nav_form or ''">
</t>
</div>
</nav>
</header>
<div id="collapsePreference" class="collapse">
<form class="px-4 py-3" method="post" action="/runbot/submit" id="preferences_form">
<input type="hidden" name="redirect" t-att-value="current_path"/>
<hr class="separator"/>
<div class="form-check form-switch">
<input onclick="document.getElementById('preferences_form').submit()" class="form-check-input" type="checkbox" role="switch" id="more" name="more" t-att-checked="more"/>
<label class="form-check-label" for="flexSwitchCheckDefault" >More info</label>
</div>
<hr class="separator"/>
<div class="text-nowrap btn-group btn-group-sm" role="group">
<button onclick="document.cookie = 'theme=legacy; expires=Thu, 1 Dec 2942 12:00:00 UTC; path=/'; location.reload();" type="button" t-attf-class="btn btn-{{'primary' if theme=='legacy' else 'secondary'}}">Legacy</button>
<button onclick="document.cookie = 'theme=dark; expires=Thu, 1 Dec 2942 12:00:00 UTC; path=/'; location.reload();" type="button" t-attf-class="btn btn-{{'primary' if theme=='dark' else 'secondary'}}">Dark</button>
<button onclick="document.cookie = 'theme=light; expires=Thu, 1 Dec 2942 12:00:00 UTC; path=/'; location.reload();" type="button" t-attf-class="btn btn-{{'primary' if theme=='light' else 'secondary'}}">Light</button>
<button onclick="document.cookie = 'theme=red404; expires=Thu, 1 Dec 2942 12:00:00 UTC; path=/'; location.reload();" type="button" t-attf-class="btn btn-{{'primary' if theme=='red404' else 'secondary'}}">Red404</button>
</div>
<hr class="separator"/>
<div class="text-nowrap btn-group btn-group-sm" role="group">
<button onclick="document.cookie = 'filter_mode=all; expires=Thu, 1 Dec 2942 12:00:00 UTC; path=/'; location.reload();" type="button" t-attf-class="btn btn-{{'primary' if filter_mode=='all' else 'secondary'}}">All</button>
<button onclick="document.cookie = 'filter_mode=sticky; expires=Thu, 1 Dec 2942 12:00:00 UTC; path=/'; location.reload();" type="button" t-attf-class="btn btn-{{'primary' if filter_mode=='sticky' else 'secondary'}}">Sticky only</button>
<button onclick="document.cookie = 'filter_mode=nosticky; expires=Thu, 1 Dec 2942 12:00:00 UTC; path=/'; location.reload();" type="button" t-attf-class="btn btn-{{'primary' if filter_mode=='nosticky' else 'secondary'}}">Dev only</button>
</div>
<div class="text-nowrap btn-group btn-group-sm" role="group">
<t t-foreach="categories" t-as="category">
<button t-attf-onclick="document.cookie = 'category={{category.id}}; expires=Thu, 1 Dec 2942 12:00:00 UTC; path=/'; location.reload();" type="button" t-attf-class="btn btn-{{'primary' if category.id == active_category_id else 'secondary'}}" t-esc="category.name"/>
</t>
</div>
<hr class="separator"/>
<div t-if="triggers">
<input type="hidden" name="update_triggers" t-att-value="project.id"/>
<t t-foreach="categories" t-as="category">
<t t-set="category_triggers" t-value="triggers.filtered(lambda t: not t.manual and t.category_id == category)"/>
<t t-if="category_triggers">
<h3 t-esc="category.name"/>
<div class="row">
<t t-foreach="category_triggers" t-as="trigger">
<div class="col-md-3 text-nowrap">
<input t-attf-class="trigger_selection {{'trigger_selection_hide' if trigger.hide else 'trigger_selection_show'}}" type="checkbox" t-attf-name="trigger_{{trigger.id}}" t-attf-id="trigger_{{trigger.id}}" t-att-checked="trigger_display is None or trigger.id in trigger_display"/>
<label t-attf-for="trigger_{{trigger.id}}" t-esc="trigger.name"/>
</div>
</t>
</div>
</t>
</t>
</div>
<button
onclick="Array.from(document.getElementsByClassName('trigger_selection_show')).forEach((element) => element.checked = true); Array.from(document.getElementsByClassName('trigger_selection_hide')).forEach((element) => element.checked = false); event.preventDefault();"
class="btn btn-secondary">Reset to default</button>
<button
onclick="Array.from(document.getElementsByClassName('trigger_selection')).forEach((element) => element.checked = true); event.preventDefault();"
class="btn btn-secondary">All</button>
<button
onclick="Array.from(document.getElementsByClassName('trigger_selection')).forEach((element) => element.checked = false); event.preventDefault();"
class="btn btn-secondary">None</button>
<button type="submit" class="btn btn-primary">Save</button>
</form>
</div>
<t t-out="0"/>
</body>
</xpath>
</template>
<template id="runbot.build_errors_link">
<t t-if="nb_assigned_errors">
<li class="nav-item divider"/>
<li class="nav-item">
<a href="/runbot/errors" class="nav-link text-danger" t-attf-title="You have {{nb_assigned_errors}} random bug assigned">
<i class="fa fa-bug"/>
<t t-esc="nb_assigned_errors"/>
<span class="text-warning" t-if="nb_team_errors">+<t t-esc="nb_team_errors"/></span>
</a>
</li>
</t>
<t t-elif="nb_team_errors">
<li class="nav-item divider"/>
<li class="nav-item">
<a href="/runbot/errors" class="nav-link text-warning" t-attf-title="Your team has {{nb_team_errors}} random bug assigned">
<i class="fa fa-bug"/>
<t t-esc="nb_team_errors"/>
</a>
</li>
</t>
<t t-elif="nb_build_errors">
<li class="nav-item divider"/>
<li class="nav-item">
<a href="/runbot/errors" class="nav-link" title="Random Bugs"><i class="fa fa-bug"/></a>
</li>
</t>
</template>
<template id="runbot.slots_infos" name="Hosts slot nb pending/testing/slots">
<a href="/runbot/load_info" class="slots_infos">
<span t-attf-class="badge text-bg-{{pending_level}}">
Pending:
<t t-esc="pending_count"/><span title="Assigned build (reserved host)" t-if="pending_assigned_count">(<t t-esc="pending_assigned_count"/>)</span>
</span>
<t t-set="testing" t-value="hosts_data._total_testing()"/>
<t t-set="workers" t-value="hosts_data._total_workers()"/>
<t t-set="klass">success</t>
<t t-if="not workers" t-set="klass">danger</t>
<t t-else="">
<t t-if="int(testing)/workers > 0" t-set="klass">info</t>
<t t-if="int(testing)/workers > 0.75" t-set="klass">warning</t>
<t t-if="int(testing)/workers >= 1" t-set="klass">danger</t>
</t>
<span t-attf-class="badge text-bg-{{klass}}">
Testing:
<t t-esc="testing"/>
/
<t t-esc="workers"/>
</span>
</a>
</template>
<template id="runbot.slot_button">
<t t-set="bu" t-value="slot.build_id"/>
<t t-set="color" t-value="bu._get_color_class()"/>
<div t-attf-class="btn-group btn-group-ssm slot_button_group">
<span t-attf-class="btn btn-{{color}} disabled" t-att-title="slot.link_type">
<i t-attf-class="fa fa-{{slot._fa_link_type()}}"/>
</span>
<a t-if="bu" t-attf-href="/runbot/batch/{{slot.batch_id.id}}/build/#{bu.id}" t-attf-class="btn btn-default slot_name">
<span t-esc="slot.trigger_id.name"/>
</a>
<span t-else="" t-attf-class="btn btn-default disabled slot_name">
<span t-esc="slot.trigger_id.name"/>
</span>
<a t-if="slot.trigger_id.report_view_id" title="View last trigger report" t-attf-href="/runbot/trigger/report/{{slot.trigger_id.id}}" class="fa fa-eye btn btn-info"/>
<a t-if="bu.local_state == 'running' and bu.database_ids" t-attf-href="/runbot/run/{{bu.id}}" class="fa fa-sign-in btn btn-info"/>
<a t-if="bu.static_run" t-att-href="bu.static_run" class="fa fa-sign-in btn btn-info"/>
<t t-if="bu" t-call="runbot.build_menu"/>
<a t-if="not bu" groups="base.group_user" class="btn btn-default" title="Create build" t-attf-href="/runbot/batch/slot/{{slot.id}}/build">
<i class="fa fa-play fa-fw"/>
</a>
</div>
</template>
<template id="runbot.build_button">
<div t-attf-class="pull-right">
<div t-attf-class="btn-group {{klass}}">
<a t-if="bu.local_state == 'running' and bu.database_ids" t-attf-href="/runbot/run/{{bu.id}}" class="btn btn-info" title="Sign in on this build" aria-label="Sign in on this build">
<i class="fa fa-sign-in"/>
</a>
<a t-if="bu.static_run" t-att-href="bu.static_run" class="btn btn-info" title="View result" aria-label="View result">
<i class="fa fa-sign-in"/>
</a>
<a groups="base.group_user" t-if="bu.local_state=='done' and (not bu.parent_id.database_ids or user_id.has_group('runbot.group_runbot_advanced_user')) and bu.requested_action != 'wake_up' and bu.database_ids" href="#" data-runbot="wakeup" t-att-data-runbot-build="bu.id" class="btn btn-default" title="Wake up this build" aria-label="Wake up this build">
<i class="fa fa-coffee"/>
</a>
<a t-attf-href="/runbot/build/{{bu['id']}}" class="btn btn-default" title="Build details" aria-label="Build details">
<i class="fa fa-file-text-o"/>
</a>
<!--<a t-if="show_commit_button" t-attf-href="https://#{repo.base_url}/commit/#{bu['name']}" class="btn btn-default" title="Open commit on GitHub" aria-label="Open commit on GitHub"><i class="fa fa-github"/></a>-->
<t t-call="runbot.build_menu"/>
</div>
</div>
</template>
<!-- Event / Logs page -->
<template id="runbot.build_class">
<t t-set="rowclass">info</t>
<t t-if="build.global_state in ['running','done']">
<t t-if="build.global_result == 'ok'">
<t t-set="rowclass">success</t>
</t>
<t t-if="build.global_result == 'skipped'">
<t t-set="rowclass">default</t>
</t>
<t t-if="build.global_result in ['killed', 'manually_killed']">
<t t-set="rowclass">killed</t>
</t>
</t>
<t t-if="build.global_result == 'ko'">
<t t-set="rowclass">danger</t>
</t>
<t t-if="build.global_result == 'warn'">
<t t-set="rowclass">warning</t>
</t>
<t t-esc="rowclass"/>
</template>
<template id="runbot.build_menu">
<button t-attf-class="btn btn-default dropdown-toggle" data-bs-toggle="dropdown" title="Build options" aria-label="Build options" aria-expanded="false">
<i t-attf-class="fa {{'fa-spinner' if bu.global_state == 'pending' else 'fa-cog'}} {{'' if bu.global_state in ('done', 'running') else 'fa-spin'}} fa-fw"/>
<span class="caret"/>
</button>
<div class="dropdown-menu dropdown-menu-end" role="menu">
<a t-if="bu.global_result=='skipped'" groups="runbot.group_runbot_admin" class="dropdown-item" href="#" data-runbot="rebuild" t-att-data-runbot-build="bu['id']">
<i class="fa fa-level-up"/>
Force Build
</a>
<t t-if="bu.local_state=='running'">
<t t-foreach="bu.database_ids[1:].sorted('name')" t-as="db">
<a class="dropdown-item" t-attf-href="/runbot/run/{{bu.id}}/{{db.db_suffix}}">
<i class="fa fa-sign-in"/>
Connect <t t-esc="db.db_suffix"></t>
</a>
</t>
<a class="dropdown-item" t-attf-href="http://{{bu.domain}}/web/database/selector">
<i class="fa fa-sign-in"/>
Database selector
</a>
</t>
<a class="dropdown-item" t-if="bu.global_state in ['done','running'] or bu.requested_action == 'deathrow'" groups="base.group_user" href="#" data-runbot="rebuild" t-att-data-runbot-build="bu['id']" title="Retry this build, usefull for false positive">
<i class="fa fa-refresh"/>
Rebuild
</a>
<t t-if="bu.global_state != 'done'">
<t t-if="bu.requested_action != 'deathrow'">
<a groups="base.group_user" href="#" data-runbot="kill" class="dropdown-item" t-att-data-runbot-build="bu['id']">
<i class="fa fa-crosshairs"/>
Kill
</a>
</t>
<t t-else="">
<a groups="base.group_user" class="dropdown-item disabled">
<i class="fa fa-spinner fa-spin"/>
Killing
<i class="fa fa-crosshairs"/>
</a>
</t>
</t>
<t t-if="bu.global_state == 'done'">
<t t-if="bu.requested_action != 'wake_up' and bu.database_ids">
<a groups="base.group_user" class="dropdown-item" href="#" data-runbot="wakeup" t-att-data-runbot-build="bu['id']">
<i class="fa fa-coffee"/>
Wake up
</a>
</t>
<t t-if="bu.requested_action == 'wake_up'">
<a groups="base.group_user" class="dropdown-item disabled">
<i class="fa fa-spinner fa-spin"/>
Waking up
<i class="fa fa-crosshairs"/>
</a>
</t>
</t>
<div t-if="bu.global_state not in ('testing', 'waiting', 'pending')" groups="base.group_user" class="dropdown-divider"/>
<t t-set="log_url" t-value="'http://%s' % bu.host if bu.host != fqdn else ''"/>
<t t-if="bu.host" t-foreach="bu.log_list.split(',') if bu.log_list else []" t-as="log_name">
<a class="dropdown-item" t-attf-href="{{log_url}}/runbot/static/build/#{bu.dest}/logs/#{log_name}.txt">
<i class="fa fa-file-text-o"/>
Full
<t t-esc="log_name"/>
logs
</a>
</t>
<t groups="runbot.group_runbot_admin">
<div class="dropdown-divider"/>
<a class="dropdown-item" t-attf-href="/runbot/build/search?config_id={{bu.params_id.config_id.id}}&amp;trigger_id={{bu.params_id.trigger_id.id}}&amp;version_id={{bu.params_id.version_id.id}}&amp;create_batch_id.bundle_id={{bu.params_id.create_batch_id.bundle_id.id}}&amp;description={{bu.description or ''}}">
<i class="fa fa-search"/>
Find similar builds
</a>
<a class="dropdown-item" t-attf-href="/web/#id={{bu['id']}}&amp;view_type=form&amp;model=runbot.build&amp;menu_id={{env['ir.model.data']._xmlid_to_res_id('runbot.runbot_menu_root')}}" target="new">
<i class="fa fa-list"/>
View in backend
</a>
</t>
</div>
</template>
<template id="runbot.branch_github_menu">
<button t-attf-class="btn btn-default btn-ssm" data-bs-toggle="dropdown" title="Github links" aria-label="Github links" aria-expanded="false">
<i t-attf-class="fa fa-github {{'text-primary' if any(branch_id.is_pr and branch_id.alive for branch_id in bundle.branch_ids) else 'text-secondary' if all(not branch_id.alive for branch_id in bundle.branch_ids) else ''}}"/>
<span class="caret"/>
</button>
<div class="dropdown-menu" role="menu">
<t t-foreach="bundle.branch_ids.sorted(key=lambda b: (not b.alive, b.remote_id.repo_id.sequence, b.remote_id.repo_id.id, b.is_pr, b.id))" t-as="branch">
<t t-set="link_title" t-value="'View %s %s on Github' % ('PR' if branch.is_pr else 'Branch', branch.name)"/>
<a t-att-href="branch.branch_url" class="dropdown-item" t-att-title="link_title">
<span class="font-italic text-muted" t-esc="branch.remote_id.short_name"/> <span t-att-class="'' if branch.alive else 'line-through'" t-esc="branch.name"/> <i t-if="not branch.alive" title="deleted/closed" class="fa fa-ban text-danger"/>
</a>
</t>
</div>
</template>
<template id="runbot.branch_copy_button">
<button t-attf-class="btn btn-default {{btn_size or 'btn-ssm'}}" title="Copy Bundle name" aria-label="Copy Bundle name" t-attf-onclick="copyToClipboard('{{ bundle.name.split(':')[-1] }}')">
<i t-attf-class="fa fa-clipboard"/>
</button>
</template>
<template id="runbot.bundle_stats_dropdown">
<button t-attf-class="btn btn-default dropdown-toggle" data-bs-toggle="dropdown" title="Bundle Stats" aria-label="Bundle Stats" aria-expanded="false">
<i t-attf-class="fa fa-bar-chart"/>
<span class="caret"/>
</button>
<div class="dropdown-menu dropdown-menu-start" role="menu">
<t t-foreach="project.trigger_ids.sorted(lambda t: (t.category_id.id, t.sequence, t.id))" t-as="trigger">
<t t-if="trigger.has_stats and not trigger.manual" >
<t t-if="current_category != trigger.category_id">
<span class="dropdown-item"><b><t t-esc="trigger.category_id.name"/></b></span>
<t t-set="current_category" t-value="trigger.category_id"/>
</t>
<a class="dropdown-item" t-attf-href="/runbot/stats/{{bundle.id}}/{{trigger.id}}">
<t t-esc="trigger.name" />
</a>
</t>
</t>
</div>
</template>
</data>
</odoo>