mirror of
https://github.com/odoo/runbot.git
synced 2025-03-15 23:45:44 +07:00
[REF] runbot: rewrite stat page layout
Rewrite the page layout to have good separation within components.
This commit is contained in:
parent
cd70f5f0c1
commit
4eaf55dc58
@ -3,7 +3,6 @@
|
||||
import { Component, useEffect, useRef, useState } from '@odoo/owl';
|
||||
|
||||
import { debounce, filterKeys, randomColor } from '@runbot/utils';
|
||||
import { useBus } from '@runbot/stats/use_bus';
|
||||
import { useConfig, onConfigChange } from '@runbot/stats/use_config';
|
||||
import { Chart } from '@runbot/chartjs';
|
||||
|
||||
@ -70,8 +69,6 @@ export class StatsChart extends Component {
|
||||
});
|
||||
|
||||
onConfigChange(() => this.fetchStats(), true);
|
||||
useBus(this.env.bus, 'click-previous', () => this.selectPrevious());
|
||||
useBus(this.env.bus, 'click-next', () => this.selectNext());
|
||||
useEffect(() => {
|
||||
this.updateChart();
|
||||
}, () => [
|
||||
@ -80,6 +77,28 @@ export class StatsChart extends Component {
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to display the next button
|
||||
*/
|
||||
get shouldDisplayNext() {
|
||||
const builds = Object.keys(this.state.data);
|
||||
if (!builds.length) {
|
||||
return false;
|
||||
}
|
||||
return this.config.center_build_id !== '0' && this.config.center_build_id !== builds[builds.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to display the previous button
|
||||
*/
|
||||
get shouldDisplayPrevious() {
|
||||
const builds = Object.keys(this.state.data);
|
||||
if (!builds.length) {
|
||||
return false;
|
||||
}
|
||||
return this.config.center_build_id !== builds[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before actually fetching stat, this triggers the spinner while waiting
|
||||
* on the debounced _fetchStat.
|
||||
@ -281,7 +300,7 @@ export class StatsChart extends Component {
|
||||
/**
|
||||
* Selects the first build as the center build for the next fetch.
|
||||
*/
|
||||
selectPrevious() {
|
||||
onClickPrevious() {
|
||||
const builds = Object.keys(this.state.data);
|
||||
if (!builds || !builds.length) {
|
||||
return
|
||||
@ -292,7 +311,7 @@ export class StatsChart extends Component {
|
||||
/**
|
||||
* Selects the last build as the center build for the next fetch.
|
||||
*/
|
||||
selectNext() {
|
||||
onClickNext() {
|
||||
const builds = Object.keys(this.state.data);
|
||||
if (!builds || !builds.length) {
|
||||
return
|
||||
|
@ -1,9 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<t t-name="runbot.StatsChart">
|
||||
<div class="container">
|
||||
<!-- Controls strictly related to dataset or chart behavior -->
|
||||
<div class="row g-1">
|
||||
<div class="col-4">
|
||||
<label for="mode" class="form-label fw-bold mb-0">Mode</label>
|
||||
<select class="form-select" id="mode" aria-label="Display Mode" t-model="config.mode">
|
||||
<option title="Real Values ordered by value" value="normal">Value</option>
|
||||
<option title="Real Values ordered by name" value="alpha">Alphabetical</option>
|
||||
<option title="Delta With Reference Build Values" value="difference">Difference</option>
|
||||
<option title="Bigger # of datapoint varying from previous one" value="change_count">Noisy</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<label for="nb_dataset" class="form-label fw-bold mb-0">Display</label>
|
||||
<select class="form-select" id="nb_dataset" aria-label="Number Of Builds" t-on-change="(ev) => this.onChangeNbDataset(ev)">
|
||||
<option value="-1">Custom</option>
|
||||
<option value="0">0</option>
|
||||
<option value="10">Top 10</option>
|
||||
<option value="20">Top 20</option>
|
||||
<option value="50">Top 50</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<label for="display_aggregate" class="form-label fw-bold mb-0">Display Aggregate</label>
|
||||
<select class="form-select" id="display_aggregate" aria-label="Display sum" t-model="config.display_aggregate">
|
||||
<option value="none">No</option>
|
||||
<option value="sum">Sum</option>
|
||||
<option value="average">Average</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between gap-4">
|
||||
<div t-if="!state.loading && (shouldDisplayPrevious || shouldDisplayNext || config.center_build_id !== '0')" class="d-flex justify-content-between flex-grow-1">
|
||||
<button t-if="shouldDisplayPrevious" class="btn btn-default" title="Previous Builds" aria-label="Previous Builds" t-on-click="() => this.onClickPrevious()">
|
||||
<i t-attf-class="fa fa-backward"/>
|
||||
</button>
|
||||
<button t-if="shouldDisplayNext" class="btn btn-default" title="Next Builds" aria-label="Next Builds" t-on-click="() => this.onClickNext()">
|
||||
<i t-attf-class="fa fa-forward"/>
|
||||
</button>
|
||||
<button t-if="config.center_build_id !== '0'" class="btn btn-default" title="Latest Builds" aria-label="Latest Builds" t-on-click="() => this.config.center_build_id = '0'">
|
||||
<i t-attf-class="fa fa-fast-forward"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="align-content-center text-center flex-grow-1">
|
||||
Tips: click a bullet to see corresponding build stats, shift+click to center the graph on this build
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-9 col-md-10">
|
||||
<div class="position-relative">
|
||||
<div class="position-relative w-100 h-100">
|
||||
<div t-if="state.loading" class="o_runbot_stat_chart_backdrop position-absolute h-100 w-100 text-center align-content-center">
|
||||
<i class="fa fa-2x fa-circle-o-notch fa-spin"/>
|
||||
</div>
|
||||
@ -11,29 +61,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-3 col-md-2">
|
||||
<b>Mode:</b>
|
||||
<select class="form-select" aria-label="Display Mode" t-model="config.mode">
|
||||
<option title="Real Values ordered by value" value="normal">Value</option>
|
||||
<option title="Real Values ordered by name" value="alpha">Alphabetical</option>
|
||||
<option title="Delta With Reference Build Values" value="difference">Difference</option>
|
||||
<option title="Bigger # of datapoint varying from previous one" value="change_count">Noisy</option>
|
||||
</select>
|
||||
<br/>
|
||||
<b>Display:</b>
|
||||
<select class="form-select" aria-label="Number Of Builds" t-on-change="(ev) => this.onChangeNbDataset(ev)">
|
||||
<option value="-1">Custom</option>
|
||||
<option value="0">0</option>
|
||||
<option value="10">Top 10</option>
|
||||
<option value="20">Top 20</option>
|
||||
<option value="50">Top 50</option>
|
||||
</select>
|
||||
<br/>
|
||||
<b>Display aggregate:</b>
|
||||
<select class="form-select" aria-label="Display sum" t-model="config.display_aggregate">
|
||||
<option value="none">No</option>
|
||||
<option value="sum">Sum</option>
|
||||
<option value="average">Average</option>
|
||||
</select><br/>
|
||||
<!-- legend -->
|
||||
<div class="chart-legend" t-if="chartConfig.data?.datasets">
|
||||
<ul>
|
||||
|
@ -1,8 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Component, useState } from '@odoo/owl';
|
||||
import { Component } from '@odoo/owl';
|
||||
|
||||
import { useBus } from '@runbot/stats/use_bus';
|
||||
import { useConfig } from '@runbot/stats/use_config';
|
||||
|
||||
|
||||
@ -44,14 +43,6 @@ export class StatsConfig extends Component {
|
||||
this.config = useConfig();
|
||||
}
|
||||
|
||||
onClickPrevious() {
|
||||
this.env.bus.trigger('click-previous', {});
|
||||
}
|
||||
|
||||
onClickNext() {
|
||||
this.env.bus.trigger('click-next', {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the trigger selection is changed.
|
||||
* This changes the config to remove trigger specific keys and redirects
|
||||
|
@ -1,40 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<t t-name="runbot.StatsConfig">
|
||||
<nav class="navbar navbar-light">
|
||||
<div class="container">
|
||||
<b>Bundle:</b><t t-out="props.bundle.name"/>
|
||||
<b>Trigger:</b>
|
||||
<select class="form-select text-capitalize" aria-label="Select Trigger" t-on-change="(ev) => this.onChangeTrigger(ev)">
|
||||
<optgroup t-foreach="Object.entries(props.triggers_by_category)" t-as="entry" t-key="entry[0]" t-att-label="entry[0]">
|
||||
<option t-foreach="entry[1]" t-as="trigger" t-key="trigger.slug" t-out="trigger.name" t-att-value="trigger.slug" t-att-selected="trigger.id === props.trigger.id ? 'selected' : undefined"/>
|
||||
</optgroup>
|
||||
</select>
|
||||
<b>Stat Category:</b>
|
||||
<select class="form-select text-capitalize" aria-label="Stat Category" t-model="config.key_category">
|
||||
<option t-foreach="props.stats_categories" t-as="category" t-key="category" t-attf-value="{{category}}">
|
||||
<t t-out="category.replaceAll('_', ' ')"/>
|
||||
</option>
|
||||
</select>
|
||||
<b>Nb of builds:</b>
|
||||
<select class="form-select" aria-label="Number Of Builds" t-model.number="config.limit">
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
<option value="250">250</option>
|
||||
</select>
|
||||
<button class="btn btn-default" title="Previous Builds" aria-label="Previous Builds" t-on-click="() => this.onClickPrevious()">
|
||||
<i t-attf-class="fa fa-backward"/>
|
||||
</button>
|
||||
<button class="btn btn-default" title="Next Builds" aria-label="Next Builds" t-on-click="() => this.onClickNext()">
|
||||
<i t-attf-class="fa fa-forward"/>
|
||||
</button>
|
||||
<button t-if="config.center_build_id != 0" class="btn btn-default" title="Latest Builds" aria-label="Latest Builds" t-on-click="() => this.config.center_build_id = 0">
|
||||
<i t-attf-class="fa fa-fast-forward"/>
|
||||
</button>
|
||||
<p>Tips: click a bullet to see corresponding build stats, shift+click to center the graph on this build</p>
|
||||
<div class="container mb-4">
|
||||
<div class="row mt-2 g-1">
|
||||
<div class="col-12">
|
||||
<label for="bundle" class="form-label fw-bold mb-0">Bundle</label>
|
||||
<input id="bundle" class="form-control" disabled="" t-att-value="props.bundle.name" aria-label="Bundle"/>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="trigger" class="form-label fw-bold mb-0">Trigger</label>
|
||||
<select class="form-select text-capitalize" id="trigger" aria-label="Select Trigger" t-on-change="(ev) => this.onChangeTrigger(ev)">
|
||||
<optgroup t-foreach="Object.entries(props.triggers_by_category)" t-as="entry" t-key="entry[0]" t-att-label="entry[0]">
|
||||
<option t-foreach="entry[1]" t-as="trigger" t-key="trigger.slug" t-out="trigger.name" t-att-value="trigger.slug" t-att-selected="trigger.id === props.trigger.id ? 'selected' : undefined"/>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="key_category" class="form-label fw-bold mb-0">Stat Category</label>
|
||||
<select class="form-select text-capitalize" id="key_category" aria-label="Stat Category" t-model="config.key_category">
|
||||
<option t-foreach="props.stats_categories" t-as="category" t-key="category" t-attf-value="{{category}}">
|
||||
<t t-out="category.replaceAll('_', ' ')"/>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="limit" class="form-label fw-bold mb-0">Number of builds:</label>
|
||||
<select class="form-select" id="limit" aria-label="Number Of Builds" t-model.number="config.limit">
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
<option value="250">250</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</t>
|
||||
</odoo>
|
||||
|
@ -1,6 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Component, whenReady, App, EventBus, useSubEnv } from '@odoo/owl';
|
||||
import { Component, whenReady, App } from '@odoo/owl';
|
||||
import { getTemplate } from '@web/core/templates';
|
||||
|
||||
import { StatsConfig } from '@runbot/stats/stats_config';
|
||||
@ -47,11 +47,6 @@ export class StatsRoot extends Component {
|
||||
setup() {
|
||||
// Initialize shared configuration for children components.
|
||||
useConfig(false);
|
||||
|
||||
// Bus for communicating between children
|
||||
useSubEnv({
|
||||
bus: new EventBus(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,22 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useComponent, useEffect } from '@odoo/owl';
|
||||
|
||||
/**
|
||||
* Ensures a bus event listener is attached and cleared the proper way.
|
||||
*
|
||||
* @param {import("@odoo/owl").EventBus} bus
|
||||
* @param {string} eventName
|
||||
* @param {EventListener} callback
|
||||
*/
|
||||
export function useBus(bus, eventName, callback) {
|
||||
const component = useComponent();
|
||||
useEffect(
|
||||
() => {
|
||||
const listener = callback.bind(component);
|
||||
bus.addEventListener(eventName, listener);
|
||||
return () => bus.removeEventListener(eventName, listener);
|
||||
},
|
||||
() => [],
|
||||
);
|
||||
}
|
@ -8,7 +8,7 @@ import { reactive, useEffect, useState, useEnv, useSubEnv } from '@odoo/owl';
|
||||
*/
|
||||
export class Config {
|
||||
constructor({
|
||||
limit = 25, center_build_id = 0, key_category = 'module_loading_queries',
|
||||
limit = 25, center_build_id = '0', key_category = 'module_loading_queries',
|
||||
mode = 'normal', nb_dataset = 20, display_aggregate = 'none', visible_keys = '',
|
||||
}) {
|
||||
this.limit = limit;
|
||||
@ -27,7 +27,7 @@ export class Config {
|
||||
*/
|
||||
static fromSearchParams() {
|
||||
const config = Object.fromEntries(new URLSearchParams(window.location.hash.substring(1)));
|
||||
const numberKeys = ['limit', 'center_build_id', 'nb_dataset'];
|
||||
const numberKeys = ['limit', 'nb_dataset'];
|
||||
numberKeys.forEach((key) => {
|
||||
if (!(key in config)) {
|
||||
return;
|
||||
|
Loading…
Reference in New Issue
Block a user