mirror of
git://git.yoctoproject.org/yocto-autobuilder2.git
synced 2025-07-19 20:59:02 +02:00
yocto_console_view: create our own console
Modify existing console view plugin to recreate our own console view. Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
This commit is contained in:
parent
c2793d8bfd
commit
c8f1590596
|
@ -1,19 +0,0 @@
|
||||||
# This file is part of Buildbot. Buildbot is free software: you can
|
|
||||||
# redistribute it and/or modify it under the terms of the GNU General Public
|
|
||||||
# License as published by the Free Software Foundation, version 2.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# this program; if not, write to the Free Software Foundation, Inc., 51
|
|
||||||
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
#
|
|
||||||
# Copyright Buildbot Team Members
|
|
||||||
|
|
||||||
from buildbot.www.plugin import Application
|
|
||||||
|
|
||||||
# create the interface for the setuptools entry point
|
|
||||||
ep = Application(__package__, "Buildbot Console View plugin")
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"name": "buildbot-console-view",
|
"name": "yocto-console-view",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "buildbot_console_view/static/scripts.js",
|
"module": "yocto_console_view/static/scripts.js",
|
||||||
"style": "buildbot_console_view/static/styles.css",
|
"style": "yocto_console_view/static/styles.css",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
|
|
@ -28,12 +28,12 @@ except ImportError:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
setup_www_plugin(
|
setup_www_plugin(
|
||||||
name='buildbot-console-view',
|
name='yocto-console-view',
|
||||||
description='Buildbot Console View plugin',
|
description='Yocto Project Console View plugin.',
|
||||||
author='Pierre Tardy',
|
author=u'Richard Purdie',
|
||||||
author_email='tardyp@gmail.com',
|
author_email=u'richard.purdie@linuxfoundation.org',
|
||||||
url='http://buildbot.net/',
|
url='http://autobuilder.yoctoproject.org/',
|
||||||
packages=['buildbot_console_view'],
|
packages=['yocto_console_view'],
|
||||||
package_data={
|
package_data={
|
||||||
'': [
|
'': [
|
||||||
'VERSION',
|
'VERSION',
|
||||||
|
@ -43,7 +43,7 @@ setup_www_plugin(
|
||||||
},
|
},
|
||||||
entry_points="""
|
entry_points="""
|
||||||
[buildbot.www]
|
[buildbot.www]
|
||||||
console_view = yocto_console_view:ep
|
yocto_console_view = yocto_console_view:ep
|
||||||
""",
|
""",
|
||||||
classifiers=['License :: OSI Approved :: GNU General Public License v2 (GPLv2)'],
|
classifiers=['License :: OSI Approved :: GNU General Public License v2 (GPLv2)'],
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
|
$headcol-width: 20px;
|
||||||
|
|
||||||
tr.bb-console-table-first-row {
|
tr.bb-console-table-first-row {
|
||||||
background-color: #fff !important;
|
.column {
|
||||||
|
width: $headcol-width;
|
||||||
|
min-width: $headcol-width;
|
||||||
|
max-width: $headcol-width;
|
||||||
|
}
|
||||||
th {
|
th {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
@ -12,8 +17,8 @@ tr.bb-console-table-first-row {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
width: 1.5em;
|
width: 1.5em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transform: rotate(-25deg) ;
|
transform: rotate(-45deg) translate(.5em, 1em);
|
||||||
transform-origin: 0% 100%;
|
transform-origin: bottom left;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
@ -36,3 +41,9 @@ tr.bb-console-table-first-row {
|
||||||
.bb-console-changes-expand-icon {
|
.bb-console-changes-expand-icon {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bb-console {
|
||||||
|
td.column {
|
||||||
|
padding: .2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ import {
|
||||||
pushIntoMapOfArrays,
|
pushIntoMapOfArrays,
|
||||||
useWindowSize
|
useWindowSize
|
||||||
} from "buildbot-ui";
|
} from "buildbot-ui";
|
||||||
|
import {YoctoChangeDetails} from './YoctoChangeDetails.tsx';
|
||||||
|
|
||||||
type ChangeInfo = {
|
type ChangeInfo = {
|
||||||
change: Change;
|
change: Change;
|
||||||
|
@ -53,152 +54,71 @@ export type TagTreeItem = {
|
||||||
childItems: TagTreeItem[];
|
childItems: TagTreeItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TagItemConfig = {
|
export type BuilderGroup = {
|
||||||
tag: string,
|
name: string;
|
||||||
colSpan: number
|
tag: string;
|
||||||
|
builders: Builder[];
|
||||||
|
colspan: int;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TagLineConfig = TagItemConfig[];
|
// Sorts and groups builders together by their tags.
|
||||||
|
export function getBuildersGroups(builders: Builder[]) : [Builder[], BuilderGroup[]]
|
||||||
export function buildTagTree(builders: Builder[])
|
|
||||||
{
|
{
|
||||||
const buildersByTags = new Map<string, Builder[]>();
|
const buildersByTags = new Map<string, Builder[]>();
|
||||||
for (const builder of builders) {
|
for (const builder of builders) {
|
||||||
if (builder.tags === null) {
|
if (builder.name === "indexing") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (const tag of builder.tags) {
|
if ((builder.tags !== null) && (builder.tags.length != 0)) {
|
||||||
pushIntoMapOfArrays(buildersByTags, tag, builder);
|
for (const tag of builder.tags) {
|
||||||
}
|
pushIntoMapOfArrays(buildersByTags, tag, builder);
|
||||||
}
|
|
||||||
|
|
||||||
type TagInfo = {
|
|
||||||
tag: string;
|
|
||||||
builders: Builder[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const undecidedTags: TagInfo[] = [];
|
|
||||||
for (const [tag, tagBuilders] of buildersByTags) {
|
|
||||||
if (tagBuilders.length < builders.length) {
|
|
||||||
undecidedTags.push({tag: tag, builders: tagBuilders});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort the tags to first look at tags with the larger number of builders
|
|
||||||
// @FIXME maybe this is not the best method to find the best groups
|
|
||||||
undecidedTags.sort((a, b) => b.builders.length - a.builders.length);
|
|
||||||
|
|
||||||
const tagItems: TagTreeItem[] = [];
|
|
||||||
const builderIdToTag = new Map<number, string>();
|
|
||||||
|
|
||||||
// pick the tags one by one, by making sure we make non-overalaping groups
|
|
||||||
for (const tagInfo of undecidedTags) {
|
|
||||||
let excluded = false;
|
|
||||||
for (const builder of tagInfo.builders) {
|
|
||||||
if (builderIdToTag.has(builder.builderid)) {
|
|
||||||
excluded = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
if (!excluded) {
|
pushIntoMapOfArrays(buildersByTags, '', builder);
|
||||||
for (const builder of tagInfo.builders) {
|
|
||||||
builderIdToTag.set(builder.builderid, tagInfo.tag);
|
|
||||||
}
|
|
||||||
tagItems.push({tag: tagInfo.tag, builders: tagInfo.builders, childItems: []});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// some builders do not have tags, we put them in another group
|
const buildersGroups: BuilderGroup[] = [];
|
||||||
const remainingBuilders = [];
|
for (const [tag, builders] of buildersByTags) {
|
||||||
for (const builder of builders) {
|
builders.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
if (!builderIdToTag.has(builder.builderid)) {
|
if (tag !== '') {
|
||||||
remainingBuilders.push(builder);
|
buildersGroups.push({
|
||||||
|
name: builders[0].name,
|
||||||
|
tag: tag,
|
||||||
|
builders: builders,
|
||||||
|
colspan: builders.length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (buildersByTags.has('')) {
|
||||||
|
const builders = buildersByTags.get('');
|
||||||
|
builders.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
for (const builder of builders) {
|
||||||
|
buildersGroups.push({
|
||||||
|
name: builder.name,
|
||||||
|
tag: '',
|
||||||
|
builders: [builder],
|
||||||
|
colspan: 1
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remainingBuilders.length) {
|
buildersGroups.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
tagItems.push({tag: "", builders: remainingBuilders, childItems: []});
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there is more than one tag in this line, we need to recurse
|
const sortedBuilders: Builder[] = [];
|
||||||
if (tagItems.length > 1) {
|
for (const buildersGroup of buildersGroups) {
|
||||||
for (const tagItem of tagItems) {
|
for (const builder of buildersGroup.builders) {
|
||||||
tagItem.childItems = buildTagTree(tagItem.builders);
|
sortedBuilders.push(builder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tagItems;
|
|
||||||
|
return [sortedBuilders, buildersGroups];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sorts and groups builders together by their tags.
|
function resolveFakeChange(revision: string, whenTimestamp: number, comment: string,
|
||||||
export function sortBuildersByTags(builders: Builder[]) : [Builder[], TagLineConfig[]]
|
|
||||||
{
|
|
||||||
// we call recursive function, which finds non-overlapping groups
|
|
||||||
const tagLineItems = buildTagTree(builders);
|
|
||||||
|
|
||||||
// we get a tree of builders grouped by tags
|
|
||||||
// we now need to flatten the tree, in order to build several lines of tags
|
|
||||||
// (each line is representing a depth in the tag tree)
|
|
||||||
// we walk the tree left to right and build the list of builders in the tree order, and the tag_lines
|
|
||||||
// in the tree, there are groups of remaining builders, which could not be grouped together,
|
|
||||||
// those have the empty tag ''
|
|
||||||
const tagLineConfigAtDepth = new Map<number, TagLineConfig>();
|
|
||||||
|
|
||||||
const resultBuilders: Builder[] = [];
|
|
||||||
|
|
||||||
const setTagLine = (depth: number, tag: string, colSpan: number) => {
|
|
||||||
const lineConfig = tagLineConfigAtDepth.get(depth);
|
|
||||||
if (lineConfig === undefined) {
|
|
||||||
tagLineConfigAtDepth.set(depth, [{tag: tag, colSpan: colSpan}]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to merge identical tags
|
|
||||||
const lastItem = lineConfig[lineConfig.length - 1];
|
|
||||||
if (lastItem.tag === tag) {
|
|
||||||
lastItem.colSpan += colSpan;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lineConfig.push({tag: tag, colSpan: colSpan});
|
|
||||||
};
|
|
||||||
|
|
||||||
const walkItemTree = (tagItem: TagTreeItem, depth: number) => {
|
|
||||||
setTagLine(depth, tagItem.tag, tagItem.builders.length);
|
|
||||||
if (tagItem.childItems.length === 0) {
|
|
||||||
// this is the leaf of the tree, sort by buildername, and add them to the
|
|
||||||
// list of sorted builders
|
|
||||||
tagItem.builders.sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
|
|
||||||
resultBuilders.push(...tagItem.builders);
|
|
||||||
|
|
||||||
for (let i = 1; i <= 100; i++) {
|
|
||||||
// set the remaining depth of the tree to the same colspan
|
|
||||||
// (we hardcode the maximum depth for now :/ )
|
|
||||||
setTagLine(depth + i, '', tagItem.builders.length);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tagItem.childItems.map(item => walkItemTree(item, depth + 1));
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const tagItem of tagLineItems) {
|
|
||||||
walkItemTree(tagItem, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resultTagLineConfigs: TagLineConfig[] = [];
|
|
||||||
|
|
||||||
for (const tagLineItems of tagLineConfigAtDepth.values()) {
|
|
||||||
if (tagLineItems.length === 1 && tagLineItems[0].tag === "") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
resultTagLineConfigs.push(tagLineItems);
|
|
||||||
}
|
|
||||||
return [resultBuilders, resultTagLineConfigs];
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveFakeChange(codebase: string, revision: string, whenTimestamp: number,
|
|
||||||
changesByFakeId: Map<string, ChangeInfo>): ChangeInfo
|
changesByFakeId: Map<string, ChangeInfo>): ChangeInfo
|
||||||
{
|
{
|
||||||
const fakeId = `${codebase}-${revision}`;
|
const fakeId = `${revision}-${comment}`;
|
||||||
const existingChange = changesByFakeId.get(fakeId);
|
const existingChange = changesByFakeId.get(fakeId);
|
||||||
if (existingChange !== undefined) {
|
if (existingChange !== undefined) {
|
||||||
return existingChange;
|
return existingChange;
|
||||||
|
@ -206,11 +126,10 @@ function resolveFakeChange(codebase: string, revision: string, whenTimestamp: nu
|
||||||
|
|
||||||
const newChange = {
|
const newChange = {
|
||||||
change: new Change(undefined as unknown as IDataAccessor, "a/1", {
|
change: new Change(undefined as unknown as IDataAccessor, "a/1", {
|
||||||
changeid: 0,
|
changeid: revision,
|
||||||
author: "",
|
author: "",
|
||||||
branch: "",
|
branch: "",
|
||||||
codebase: codebase,
|
comments: comment,
|
||||||
comments: `Unknown revision ${revision}`,
|
|
||||||
files: [],
|
files: [],
|
||||||
parent_changeids: [],
|
parent_changeids: [],
|
||||||
project: "",
|
project: "",
|
||||||
|
@ -230,47 +149,63 @@ function resolveFakeChange(codebase: string, revision: string, whenTimestamp: nu
|
||||||
function selectChangeForBuild(build: Build, buildset: Buildset,
|
function selectChangeForBuild(build: Build, buildset: Buildset,
|
||||||
changesBySsid: Map<number, ChangeInfo>,
|
changesBySsid: Map<number, ChangeInfo>,
|
||||||
changesByRevision: Map<string, ChangeInfo>,
|
changesByRevision: Map<string, ChangeInfo>,
|
||||||
changesByFakeId: Map<string, ChangeInfo>) {
|
changesByFakeId: Map<string, ChangeInfo>,
|
||||||
if (buildset.sourcestamps !== null) {
|
revMapping: Map<int, string>,
|
||||||
for (const sourcestamp of buildset.sourcestamps) {
|
branchMapping: Map<int, string>)
|
||||||
const change = changesBySsid.get(sourcestamp.ssid);
|
{
|
||||||
if (change !== undefined) {
|
if ((build.properties !== null && ('yp_build_revision' in build.properties)) || (build.buildid in revMapping)) {
|
||||||
return change;
|
let revision;
|
||||||
}
|
let change = undefined;
|
||||||
|
if (build.properties !== null && ('yp_build_revision' in build.properties)) {
|
||||||
|
revision = build.properties['yp_build_revision'][0];
|
||||||
|
} else {
|
||||||
|
revision = revMapping[build.buildid];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (build.properties !== null && ('got_revision' in build.properties)) {
|
|
||||||
const revision = build.properties['got_revision'][0];
|
|
||||||
// got_revision can be per codebase or just the revision string
|
// got_revision can be per codebase or just the revision string
|
||||||
if (typeof(revision) === "string") {
|
if (typeof(revision) === "string") {
|
||||||
const change = changesByRevision.get(revision);
|
change = changesByRevision.get(revision);
|
||||||
if (change !== undefined) {
|
if (change === undefined) {
|
||||||
return change;
|
change = changesBySsid.get(revision);
|
||||||
|
}
|
||||||
|
if (change === undefined) {
|
||||||
|
change = resolveFakeChange(revision, build.started_at, revision, changesByFakeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolveFakeChange("", revision, build.started_at, changesByFakeId);
|
change.change.caption = "Commit";
|
||||||
}
|
if (build.properties !== null && ('yp_build_revision' in build.properties)) {
|
||||||
|
change.change.caption = build.properties['yp_build_branch'][0];
|
||||||
|
}
|
||||||
|
if (build.buildid in branchMapping) {
|
||||||
|
change.change.caption = branchMapping[build.buildid];
|
||||||
|
}
|
||||||
|
change.change.revlink = "http://git.yoctoproject.org/cgit.cgi/poky/commit/?id=" + revision;
|
||||||
|
change.change.errorlink = "http://errors.yoctoproject.org/Errors/Latest/?filter=" + revision + "&type=commit&limit=150";
|
||||||
|
|
||||||
const revisionMap = revision as {[codebase: string]: string};
|
let bid = build.buildid;
|
||||||
for (const codebase in revisionMap) {
|
if ((buildset !== null) && (buildset.parent_buildid != null)) {
|
||||||
const codebaseRevision = revisionMap[codebase];
|
bid = buildset.parent_buildid;
|
||||||
const change = changesByRevision.get(codebaseRevision);
|
}
|
||||||
if (change !== undefined) {
|
if (build.properties !== null && ('reason' in build.properties)) {
|
||||||
return change;
|
change.change.reason = build.properties['reason'][0];
|
||||||
|
}
|
||||||
|
if (build.properties !== null && ('publish_destination' in build.properties)) {
|
||||||
|
change.change.publishurl = build.properties['publish_destination'][0].replace("/srv/autobuilder/autobuilder.yoctoproject.org/", "https://autobuilder.yocto.io/");
|
||||||
|
change.change.publishurl = change.change.publishurl.replace("/srv/autobuilder/autobuilder.yocto.io/", "https://autobuilder.yocto.io/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const codebases = Object.keys(revisionMap);
|
return change;
|
||||||
if (codebases.length === 0) {
|
|
||||||
return resolveFakeChange("unknown codebase", "", build.started_at, changesByFakeId);
|
|
||||||
}
|
|
||||||
return resolveFakeChange(codebases[0], revisionMap[codebases[0]], build.started_at,
|
|
||||||
changesByFakeId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const revision = `unknown revision ${build.builderid}-${build.buildid}`;
|
const revision = `Unresolved Revision`
|
||||||
return resolveFakeChange("unknown codebase", revision, build.started_at, changesByFakeId);
|
const change = changesBySsid.get(revision);
|
||||||
|
if (change !== undefined) {
|
||||||
|
return change
|
||||||
|
}
|
||||||
|
|
||||||
|
const fakeChange = resolveFakeChange(revision, build.started_at, revision, changesByFakeId);
|
||||||
|
fakeChange.change.caption = revision;
|
||||||
|
return fakeChange
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConsoleView = observer(() => {
|
export const ConsoleView = observer(() => {
|
||||||
|
@ -300,7 +235,7 @@ export const ConsoleView = observer(() => {
|
||||||
const buildsQuery = useDataApiQuery(() => Build.getAll(accessor, {query: {
|
const buildsQuery = useDataApiQuery(() => Build.getAll(accessor, {query: {
|
||||||
limit: buildFetchLimit,
|
limit: buildFetchLimit,
|
||||||
order: '-started_at',
|
order: '-started_at',
|
||||||
property: ["got_revision"],
|
property: ["yp_build_revision", "yp_build_branch", "reason", "publish_destination"],
|
||||||
}}));
|
}}));
|
||||||
|
|
||||||
const windowSize = useWindowSize()
|
const windowSize = useWindowSize()
|
||||||
|
@ -313,13 +248,65 @@ export const ConsoleView = observer(() => {
|
||||||
buildrequestsQuery.resolved &&
|
buildrequestsQuery.resolved &&
|
||||||
buildsQuery.resolved;
|
buildsQuery.resolved;
|
||||||
|
|
||||||
|
// FIXME: fa-spin
|
||||||
|
if (!queriesResolved) {
|
||||||
|
return (
|
||||||
|
<div className="bb-console-container">
|
||||||
|
<LoadingIndicator/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const builderIdsWithBuilds = new Set<number>();
|
const builderIdsWithBuilds = new Set<number>();
|
||||||
for (const build of buildsQuery.array) {
|
for (const build of buildsQuery.array) {
|
||||||
builderIdsWithBuilds.add(build.builderid);
|
builderIdsWithBuilds.add(build.builderid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const revMapping = new Map<int, string>();
|
||||||
|
const branchMapping = new Map<int, string>();
|
||||||
|
for (const build of buildsQuery.array) {
|
||||||
|
let change = false;
|
||||||
|
let {
|
||||||
|
buildid
|
||||||
|
} = build;
|
||||||
|
if (build.properties !== null && ('yp_build_revision' in build.properties)) {
|
||||||
|
revMapping[build.buildid] = build.properties['yp_build_revision'][0];
|
||||||
|
change = true;
|
||||||
|
}
|
||||||
|
if (build.properties !== null && ('yp_build_branch' in build.properties)) {
|
||||||
|
branchMapping[build.buildid] = build.properties.yp_build_branch[0];
|
||||||
|
change = true;
|
||||||
|
}
|
||||||
|
if ((!revMapping[buildid] || !branchMapping[buildid]) && !build.complete_at) {
|
||||||
|
build.getProperties().onChange = properties => {
|
||||||
|
change = false;
|
||||||
|
buildid = properties.endpoint.split('/')[1];
|
||||||
|
if (!revMapping[buildid]) {
|
||||||
|
const rev = getBuildProperty(properties[0], 'yp_build_revision');
|
||||||
|
if (rev != null) {
|
||||||
|
revMapping[buildid] = rev;
|
||||||
|
change = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!branchMapping[buildid]) {
|
||||||
|
const branch = getBuildProperty(properties[0], 'yp_build_branch');
|
||||||
|
if (branch != null) {
|
||||||
|
branchMapping[buildid] = branch;
|
||||||
|
change = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBuildProperty(properties, property) {
|
||||||
|
const hasProperty = properties && properties.hasOwnProperty(property);
|
||||||
|
if (hasProperty) { return properties[property][0]; } else { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
const buildersWithBuilds = buildersQuery.array.filter(b => builderIdsWithBuilds.has(b.builderid));
|
const buildersWithBuilds = buildersQuery.array.filter(b => builderIdsWithBuilds.has(b.builderid));
|
||||||
const [buildersToShow, tagLineConfigs] = sortBuildersByTags(buildersWithBuilds);
|
const [buildersToShow, builderGroups] = getBuildersGroups(buildersWithBuilds);
|
||||||
|
|
||||||
const changesByRevision = new Map<string, ChangeInfo>();
|
const changesByRevision = new Map<string, ChangeInfo>();
|
||||||
const changesBySsid = new Map<number, ChangeInfo>();
|
const changesBySsid = new Map<number, ChangeInfo>();
|
||||||
|
@ -347,7 +334,7 @@ export const ConsoleView = observer(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const change = selectChangeForBuild(build, buildset, changesBySsid, changesByRevision,
|
const change = selectChangeForBuild(build, buildset, changesBySsid, changesByRevision,
|
||||||
changesByFakeId);
|
changesByFakeId, revMapping, branchMapping);
|
||||||
|
|
||||||
pushIntoMapOfArrays(change.buildsByBuilderId, build.builderid, build);
|
pushIntoMapOfArrays(change.buildsByBuilderId, build.builderid, build);
|
||||||
}
|
}
|
||||||
|
@ -393,21 +380,11 @@ export const ConsoleView = observer(() => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: fa-spin
|
if (buildsQuery.array.length === 0) {
|
||||||
if (!queriesResolved) {
|
|
||||||
return (
|
|
||||||
<div className="bb-console-container">
|
|
||||||
<LoadingIndicator/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changesQuery.array.length === 0) {
|
|
||||||
return (
|
return (
|
||||||
<div className="bb-console-container">
|
<div className="bb-console-container">
|
||||||
<p>
|
<p>
|
||||||
No changes. Console View needs a changesource to be setup,
|
No builds. Console View needs run builds to be setup.
|
||||||
and <Link to="/changes">changes</Link> to be in the system.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -423,23 +400,15 @@ export const ConsoleView = observer(() => {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
const tagLineRows = tagLineConfigs.map((tagLineConfig, i) => {
|
const tagLineColumns = builderGroups.map((builderGroup, i) => {
|
||||||
const columns = tagLineConfig.map((item, i) => {
|
|
||||||
return (
|
|
||||||
<td key={i} colSpan={item.colSpan}>
|
|
||||||
<span style={{width: item.colSpan * 50}}>{item.tag}</span>
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className="bb-console-tag-row" key={`tag-${i}`}>
|
<td key={i} colSpan={builderGroup.colspan} style={{textAlign: 'center'}}>
|
||||||
<td className="row-header"></td>
|
{builderGroup.tag}
|
||||||
{columns}
|
</td>
|
||||||
</tr>
|
);
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const changeRows = changesToShow.map(changeInfo => {
|
const changeRows = changesToShow.map(changeInfo => {
|
||||||
const change = changeInfo.change;
|
const change = changeInfo.change;
|
||||||
|
|
||||||
|
@ -460,7 +429,7 @@ export const ConsoleView = observer(() => {
|
||||||
return (
|
return (
|
||||||
<tr key={`change-${change.changeid}-${change.codebase}-${change.revision ?? ''}`}>
|
<tr key={`change-${change.changeid}-${change.codebase}-${change.revision ?? ''}`}>
|
||||||
<td>
|
<td>
|
||||||
<ChangeDetails change={change} compact={true}
|
<YoctoChangeDetails change={change} compact={true}
|
||||||
showDetails={changeIsExpandedByChangeId.get(change.changeid) ?? false}
|
showDetails={changeIsExpandedByChangeId.get(change.changeid) ?? false}
|
||||||
setShowDetails={(show: boolean) => changeIsExpandedByChangeId.set(change.changeid, show)}/>
|
setShowDetails={(show: boolean) => changeIsExpandedByChangeId.set(change.changeid, show)}/>
|
||||||
</td>
|
</td>
|
||||||
|
@ -474,28 +443,16 @@ export const ConsoleView = observer(() => {
|
||||||
<Table striped bordered className={(isBigTable() ? 'table-fixedwidth' : '')}>
|
<Table striped bordered className={(isBigTable() ? 'table-fixedwidth' : '')}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="bb-console-table-first-row first-row">
|
<tr className="bb-console-table-first-row first-row">
|
||||||
<th className="row-header" style={{width: rowHeaderWidth}}>
|
<th className="row-header">
|
||||||
<OverlayTrigger trigger="click" placement="top" overlay={
|
|
||||||
<Tooltip id="bb-console-view-open-all-changes">
|
|
||||||
Open information for all changes
|
|
||||||
</Tooltip>
|
|
||||||
} rootClose={true}>
|
|
||||||
<FaPlusCircle onClick={e => openAllChanges()} className="bb-console-changes-expand-icon clickable"/>
|
|
||||||
</OverlayTrigger>
|
|
||||||
|
|
||||||
<OverlayTrigger trigger="click" placement="top" overlay={
|
|
||||||
<Tooltip id="bb-console-view-close-all-changes">
|
|
||||||
Close information for all changes
|
|
||||||
</Tooltip>
|
|
||||||
} rootClose={true}>
|
|
||||||
<FaMinusCircle onClick={e => closeAllChanges()} className="bb-console-changes-expand-icon clickable"/>
|
|
||||||
</OverlayTrigger>
|
|
||||||
</th>
|
</th>
|
||||||
{builderColumns}
|
{builderColumns}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{tagLineRows}
|
<tr className="bb-console-tag-row" key="tag">
|
||||||
|
<td className="row-header"></td>
|
||||||
|
{tagLineColumns}
|
||||||
|
</tr>
|
||||||
{changeRows}
|
{changeRows}
|
||||||
</tbody>
|
</tbody>
|
||||||
</Table>
|
</Table>
|
||||||
|
@ -506,7 +463,7 @@ export const ConsoleView = observer(() => {
|
||||||
buildbotSetupPlugin(reg => {
|
buildbotSetupPlugin(reg => {
|
||||||
reg.registerMenuGroup({
|
reg.registerMenuGroup({
|
||||||
name: 'console',
|
name: 'console',
|
||||||
caption: 'Console View',
|
caption: 'Yocto Console View',
|
||||||
icon: <FaExclamationCircle/>,
|
icon: <FaExclamationCircle/>,
|
||||||
order: 5,
|
order: 5,
|
||||||
route: '/console',
|
route: '/console',
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
.yoctochangedetails {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yoctochangedetails-heading {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yoctochangedetails-heading > * {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.changedetails-properties {
|
||||||
|
padding: unset;
|
||||||
|
margin: unset;
|
||||||
|
border: unset;
|
||||||
|
background-color: unset
|
||||||
|
}
|
149
yocto_console_view/src/views/ConsoleView/YoctoChangeDetails.tsx
Normal file
149
yocto_console_view/src/views/ConsoleView/YoctoChangeDetails.tsx
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
/*
|
||||||
|
This file is part of Buildbot. Buildbot is free software: you can
|
||||||
|
redistribute it and/or modify it under the terms of the GNU General Public
|
||||||
|
License as published by the Free Software Foundation, version 2.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
this program; if not, write to the Free Software Foundation, Inc., 51
|
||||||
|
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
Copyright Buildbot Team Members
|
||||||
|
*/
|
||||||
|
|
||||||
|
import './YoctoChangeDetails.scss';
|
||||||
|
import {useState} from "react";
|
||||||
|
import {observer} from "mobx-react";
|
||||||
|
import {OverlayTrigger, Popover, Table} from "react-bootstrap";
|
||||||
|
import {Change, parseChangeAuthorNameAndEmail} from "buildbot-data-js";
|
||||||
|
import {dateFormat, durationFromNowFormat, useCurrentTime} from "buildbot-ui";
|
||||||
|
import {ArrowExpander} from "buildbot-ui";
|
||||||
|
|
||||||
|
type ChangeDetailsProps = {
|
||||||
|
change: Change;
|
||||||
|
compact: boolean;
|
||||||
|
showDetails: boolean;
|
||||||
|
setShowDetails: ((show: boolean) => void) | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const YoctoChangeDetails = observer(({change, compact, showDetails, setShowDetails}: ChangeDetailsProps) => {
|
||||||
|
const now = useCurrentTime();
|
||||||
|
const [showProps, setShowProps] = useState(false);
|
||||||
|
|
||||||
|
const renderChangeDetails = () => (
|
||||||
|
<div className="anim-yoctochangedetails">
|
||||||
|
<Table striped size="sm">
|
||||||
|
<tbody>
|
||||||
|
{ change.reason !== null
|
||||||
|
? <tr>
|
||||||
|
<td>Reason</td>
|
||||||
|
<td>{change.reason}</td>
|
||||||
|
</tr>
|
||||||
|
: <></>
|
||||||
|
}
|
||||||
|
{ change.author != null
|
||||||
|
? <tr>
|
||||||
|
<td>Author</td>
|
||||||
|
<td>{change.author}</td>
|
||||||
|
</tr>
|
||||||
|
: <></>
|
||||||
|
}
|
||||||
|
<tr>
|
||||||
|
<td>Date</td>
|
||||||
|
<td>{dateFormat(change.when_timestamp)} ({durationFromNowFormat(change.when_timestamp, now)})</td>
|
||||||
|
</tr>
|
||||||
|
{ change.repository !== null
|
||||||
|
? <tr>
|
||||||
|
<td>Repository</td>
|
||||||
|
<td>{change.repository}</td>
|
||||||
|
</tr>
|
||||||
|
: <></>
|
||||||
|
}
|
||||||
|
{ change.branch !== null
|
||||||
|
? <tr>
|
||||||
|
<td>Branch</td>
|
||||||
|
<td>{change.branch}</td>
|
||||||
|
</tr>
|
||||||
|
: <></>
|
||||||
|
}
|
||||||
|
<tr>
|
||||||
|
<td>Revision</td>
|
||||||
|
<td>
|
||||||
|
{
|
||||||
|
change.revlink
|
||||||
|
? <a href={change.revlink}>{change.revision}</a>
|
||||||
|
: <></>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
<h5>Comment</h5>
|
||||||
|
<pre>{change.comments}</pre>
|
||||||
|
<h5>Changed files</h5>
|
||||||
|
{change.files.length === 0
|
||||||
|
? <p>No files</p>
|
||||||
|
: <ul>{change.files.map(file => (<li key={file}>{file}</li>))}</ul>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const [changeAuthorName, changeEmail] = parseChangeAuthorNameAndEmail(change.author);
|
||||||
|
|
||||||
|
const popoverWithText = (id: string, text: string) => {
|
||||||
|
return (
|
||||||
|
<Popover id={"bb-popover-change-details-" + id}>
|
||||||
|
<Popover.Content>
|
||||||
|
{text}
|
||||||
|
</Popover.Content>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onHeadingClicked = () => {
|
||||||
|
if (setShowDetails === null)
|
||||||
|
return;
|
||||||
|
setShowDetails(!showDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="yoctochangedetails">
|
||||||
|
<div className="yoctochangedetails-heading" onClick={onHeadingClicked}>
|
||||||
|
<OverlayTrigger placement="top"
|
||||||
|
overlay={popoverWithText("comments-" + change.id, change.caption)}>
|
||||||
|
<React.Fragment>
|
||||||
|
{
|
||||||
|
change.revlink
|
||||||
|
? <a href={change.revlink}>{change.caption}</a>
|
||||||
|
: <span>{change.caption}</span>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
change.errorlink
|
||||||
|
? <a href={change.errorlink}>Error</a>
|
||||||
|
: <></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
change.publishurl
|
||||||
|
? <a href={change.publishurl}>Output</a>
|
||||||
|
: <></>
|
||||||
|
}
|
||||||
|
</React.Fragment>
|
||||||
|
</OverlayTrigger>
|
||||||
|
{ !compact
|
||||||
|
? <OverlayTrigger placement="top"
|
||||||
|
overlay={popoverWithText("date-" + change.id,
|
||||||
|
dateFormat(change.when_timestamp))}>
|
||||||
|
<span>({durationFromNowFormat(change.when_timestamp, now)})</span>
|
||||||
|
</OverlayTrigger>
|
||||||
|
: <></>
|
||||||
|
}
|
||||||
|
{setShowDetails !== null ? <ArrowExpander isExpanded={showDetails}/> : <></>}
|
||||||
|
</div>
|
||||||
|
{showDetails ? renderChangeDetails() : <></>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
|
@ -2,7 +2,7 @@ import {resolve} from "path";
|
||||||
import {defineConfig} from "vite";
|
import {defineConfig} from "vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
const outDir = 'buildbot_console_view/static';
|
const outDir = 'yocto_console_view/static';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
Loading…
Reference in New Issue
Block a user