mirror of
https://github.com/odoo/runbot.git
synced 2025-03-27 13:25:47 +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 { Component, useEffect, useRef, useState } from '@odoo/owl';
|
||||||
|
|
||||||
import { debounce, filterKeys, randomColor } from '@runbot/utils';
|
import { debounce, filterKeys, randomColor } from '@runbot/utils';
|
||||||
import { useBus } from '@runbot/stats/use_bus';
|
|
||||||
import { useConfig, onConfigChange } from '@runbot/stats/use_config';
|
import { useConfig, onConfigChange } from '@runbot/stats/use_config';
|
||||||
import { Chart } from '@runbot/chartjs';
|
import { Chart } from '@runbot/chartjs';
|
||||||
|
|
||||||
@ -70,8 +69,6 @@ export class StatsChart extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onConfigChange(() => this.fetchStats(), true);
|
onConfigChange(() => this.fetchStats(), true);
|
||||||
useBus(this.env.bus, 'click-previous', () => this.selectPrevious());
|
|
||||||
useBus(this.env.bus, 'click-next', () => this.selectNext());
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
this.updateChart();
|
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
|
* Called before actually fetching stat, this triggers the spinner while waiting
|
||||||
* on the debounced _fetchStat.
|
* 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.
|
* Selects the first build as the center build for the next fetch.
|
||||||
*/
|
*/
|
||||||
selectPrevious() {
|
onClickPrevious() {
|
||||||
const builds = Object.keys(this.state.data);
|
const builds = Object.keys(this.state.data);
|
||||||
if (!builds || !builds.length) {
|
if (!builds || !builds.length) {
|
||||||
return
|
return
|
||||||
@ -292,7 +311,7 @@ export class StatsChart extends Component {
|
|||||||
/**
|
/**
|
||||||
* Selects the last build as the center build for the next fetch.
|
* Selects the last build as the center build for the next fetch.
|
||||||
*/
|
*/
|
||||||
selectNext() {
|
onClickNext() {
|
||||||
const builds = Object.keys(this.state.data);
|
const builds = Object.keys(this.state.data);
|
||||||
if (!builds || !builds.length) {
|
if (!builds || !builds.length) {
|
||||||
return
|
return
|
||||||
|
@ -1,9 +1,59 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<t t-name="runbot.StatsChart">
|
<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="row">
|
||||||
<div class="col-xs-9 col-md-10">
|
<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">
|
<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"/>
|
<i class="fa fa-2x fa-circle-o-notch fa-spin"/>
|
||||||
</div>
|
</div>
|
||||||
@ -11,29 +61,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-3 col-md-2">
|
<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 -->
|
<!-- legend -->
|
||||||
<div class="chart-legend" t-if="chartConfig.data?.datasets">
|
<div class="chart-legend" t-if="chartConfig.data?.datasets">
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
/** @odoo-module **/
|
/** @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';
|
import { useConfig } from '@runbot/stats/use_config';
|
||||||
|
|
||||||
|
|
||||||
@ -44,14 +43,6 @@ export class StatsConfig extends Component {
|
|||||||
this.config = useConfig();
|
this.config = useConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickPrevious() {
|
|
||||||
this.env.bus.trigger('click-previous', {});
|
|
||||||
}
|
|
||||||
|
|
||||||
onClickNext() {
|
|
||||||
this.env.bus.trigger('click-next', {});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the trigger selection is changed.
|
* Called when the trigger selection is changed.
|
||||||
* This changes the config to remove trigger specific keys and redirects
|
* This changes the config to remove trigger specific keys and redirects
|
||||||
|
@ -1,40 +1,39 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<t t-name="runbot.StatsConfig">
|
<t t-name="runbot.StatsConfig">
|
||||||
<nav class="navbar navbar-light">
|
<div class="container mb-4">
|
||||||
<div class="container">
|
<div class="row mt-2 g-1">
|
||||||
<b>Bundle:</b><t t-out="props.bundle.name"/>
|
<div class="col-12">
|
||||||
<b>Trigger:</b>
|
<label for="bundle" class="form-label fw-bold mb-0">Bundle</label>
|
||||||
<select class="form-select text-capitalize" aria-label="Select Trigger" t-on-change="(ev) => this.onChangeTrigger(ev)">
|
<input id="bundle" class="form-control" disabled="" t-att-value="props.bundle.name" aria-label="Bundle"/>
|
||||||
<optgroup t-foreach="Object.entries(props.triggers_by_category)" t-as="entry" t-key="entry[0]" t-att-label="entry[0]">
|
</div>
|
||||||
<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"/>
|
<div class="col-12">
|
||||||
</optgroup>
|
<label for="trigger" class="form-label fw-bold mb-0">Trigger</label>
|
||||||
</select>
|
<select class="form-select text-capitalize" id="trigger" aria-label="Select Trigger" t-on-change="(ev) => this.onChangeTrigger(ev)">
|
||||||
<b>Stat Category:</b>
|
<optgroup t-foreach="Object.entries(props.triggers_by_category)" t-as="entry" t-key="entry[0]" t-att-label="entry[0]">
|
||||||
<select class="form-select text-capitalize" aria-label="Stat Category" t-model="config.key_category">
|
<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"/>
|
||||||
<option t-foreach="props.stats_categories" t-as="category" t-key="category" t-attf-value="{{category}}">
|
</optgroup>
|
||||||
<t t-out="category.replaceAll('_', ' ')"/>
|
</select>
|
||||||
</option>
|
</div>
|
||||||
</select>
|
<div class="col-12">
|
||||||
<b>Nb of builds:</b>
|
<label for="key_category" class="form-label fw-bold mb-0">Stat Category</label>
|
||||||
<select class="form-select" aria-label="Number Of Builds" t-model.number="config.limit">
|
<select class="form-select text-capitalize" id="key_category" aria-label="Stat Category" t-model="config.key_category">
|
||||||
<option value="10">10</option>
|
<option t-foreach="props.stats_categories" t-as="category" t-key="category" t-attf-value="{{category}}">
|
||||||
<option value="25">25</option>
|
<t t-out="category.replaceAll('_', ' ')"/>
|
||||||
<option value="50">50</option>
|
</option>
|
||||||
<option value="100">100</option>
|
</select>
|
||||||
<option value="250">250</option>
|
</div>
|
||||||
</select>
|
<div class="col-12">
|
||||||
<button class="btn btn-default" title="Previous Builds" aria-label="Previous Builds" t-on-click="() => this.onClickPrevious()">
|
<label for="limit" class="form-label fw-bold mb-0">Number of builds:</label>
|
||||||
<i t-attf-class="fa fa-backward"/>
|
<select class="form-select" id="limit" aria-label="Number Of Builds" t-model.number="config.limit">
|
||||||
</button>
|
<option value="10">10</option>
|
||||||
<button class="btn btn-default" title="Next Builds" aria-label="Next Builds" t-on-click="() => this.onClickNext()">
|
<option value="25">25</option>
|
||||||
<i t-attf-class="fa fa-forward"/>
|
<option value="50">50</option>
|
||||||
</button>
|
<option value="100">100</option>
|
||||||
<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">
|
<option value="250">250</option>
|
||||||
<i t-attf-class="fa fa-fast-forward"/>
|
</select>
|
||||||
</button>
|
</div>
|
||||||
<p>Tips: click a bullet to see corresponding build stats, shift+click to center the graph on this build</p>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/** @odoo-module **/
|
/** @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 { getTemplate } from '@web/core/templates';
|
||||||
|
|
||||||
import { StatsConfig } from '@runbot/stats/stats_config';
|
import { StatsConfig } from '@runbot/stats/stats_config';
|
||||||
@ -47,11 +47,6 @@ export class StatsRoot extends Component {
|
|||||||
setup() {
|
setup() {
|
||||||
// Initialize shared configuration for children components.
|
// Initialize shared configuration for children components.
|
||||||
useConfig(false);
|
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 {
|
export class Config {
|
||||||
constructor({
|
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 = '',
|
mode = 'normal', nb_dataset = 20, display_aggregate = 'none', visible_keys = '',
|
||||||
}) {
|
}) {
|
||||||
this.limit = limit;
|
this.limit = limit;
|
||||||
@ -27,7 +27,7 @@ export class Config {
|
|||||||
*/
|
*/
|
||||||
static fromSearchParams() {
|
static fromSearchParams() {
|
||||||
const config = Object.fromEntries(new URLSearchParams(window.location.hash.substring(1)));
|
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) => {
|
numberKeys.forEach((key) => {
|
||||||
if (!(key in config)) {
|
if (!(key in config)) {
|
||||||
return;
|
return;
|
||||||
|
Loading…
Reference in New Issue
Block a user