Skip to content

Commit

Permalink
feat: Job table for use in event/PR view (#1289)
Browse files Browse the repository at this point in the history
Co-authored-by: Vonny Jap <[email protected]>
  • Loading branch information
minghay and VonnyJap authored Dec 13, 2024
1 parent 0bf450a commit 18fb9a1
Show file tree
Hide file tree
Showing 11 changed files with 419 additions and 199 deletions.
211 changes: 139 additions & 72 deletions app/components/pipeline/jobs/table/component.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Component from '@glimmer/component';
import { service } from '@ember/service';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { dom } from '@fortawesome/fontawesome-svg-core';
import DataReloader from './dataReloader';
import getDisplayName from './util';
Expand All @@ -10,86 +11,145 @@ const INITIAL_PAGE_SIZE = 10;
export default class PipelineJobsTableComponent extends Component {
@service shuttle;

@service workflowDataReload;

@service('emt-themes/ember-bootstrap-v5') emberModelTableBootstrapTheme;

pipeline;

userSettings;

event;

jobs;

dataReloader;

data = [];

columns = [
{
title: 'JOB',
propertyName: 'jobName',
className: 'job-column',
component: 'jobCell',
filteredBy: 'jobName'
},
{
numBuilds;

@tracked data;

columns;

constructor() {
super(...arguments);

this.pipeline = this.args.pipeline;
this.event = this.args.event;
this.jobs = this.args.jobs;
this.userSettings = this.args.userSettings;
this.numBuilds = this.args.numBuilds;
this.data = null;

this.setColumnData();
}

setColumnData() {
const historyColumnConfiguration = {
title: 'HISTORY',
propertyName: 'history',
className: 'history-column',
component: 'historyCell',
filterFunction: async (_, filterVal) => {
component: 'historyCell'
};

if (!this.event) {
historyColumnConfiguration.propertyName = 'history';
historyColumnConfiguration.filterWithSelect = true;
historyColumnConfiguration.predefinedFilterOptions = [
'5',
'10',
'15',
'20',
'25',
'30'
];
historyColumnConfiguration.filterFunction = async (_, filterVal) => {
await this.dataReloader.setNumBuilds(filterVal);
},
filterWithSelect: true,
predefinedFilterOptions: ['5', '10', '15', '20', '25', '30']
},
{
title: 'DURATION',
className: 'duration-column',
component: 'durationCell'
},
{
title: 'START TIME',
className: 'start-time-column',
component: 'startTimeCell'
},
{
title: 'COVERAGE',
className: 'coverage-column',
component: 'coverageCell'
},
{
title: 'STAGE',
propertyName: 'stageName',
className: 'stage-column',
component: 'stageCell',
filteredBy: 'stageName'
},
{
title: 'METRICS',
className: 'metrics-column',
component: 'metricsCell'
},
{
title: 'ACTIONS',
className: 'actions-column',
component: 'actionsCell'
};
}
];

constructor() {
super(...arguments);
this.columns = [
{
title: 'JOB',
propertyName: 'jobName',
className: 'job-column',
component: 'jobCell',
filteredBy: 'jobName'
},
historyColumnConfiguration,
{
title: 'DURATION',
className: 'duration-column',
component: 'durationCell'
},
{
title: 'START TIME',
className: 'start-time-column',
component: 'startTimeCell'
},
{
title: 'COVERAGE',
className: 'coverage-column',
component: 'coverageCell'
},
{
title: 'STAGE',
propertyName: 'stageName',
className: 'stage-column',
component: 'stageCell',
filteredBy: 'stageName'
},
{
title: 'METRICS',
className: 'metrics-column',
component: 'metricsCell'
},
{
title: 'ACTIONS',
className: 'actions-column',
component: 'actionsCell'
}
];
}

willDestroy() {
super.willDestroy();

const jobIds = this.args.jobs.map(job => job.id);
this.dataReloader.stop(this.event?.id);
}

@action
initialize(element) {
dom.i2svg({ node: element });
this.initializeDataLoader().then(() => {});
}

@action
async initializeDataLoader() {
const prNum = this.event?.prNum;

if (prNum) {
this.jobs = this.workflowDataReload.getJobsForPr(prNum);
}

const jobIds = this.jobs.map(job => job.id);

this.dataReloader = new DataReloader(
this.shuttle,
{ shuttle: this.shuttle, workflowDataReload: this.workflowDataReload },
jobIds,
INITIAL_PAGE_SIZE,
this.args.numBuilds
this.numBuilds
);
const initialJobIds = this.dataReloader.newJobIds();

this.args.jobs.forEach(job => {
this.data = [];

this.jobs.forEach(job => {
this.data.push({
job,
jobName: getDisplayName(job),
jobName: getDisplayName(job, prNum),
stageName: job?.permutations?.[0]?.stage?.name || 'N/A',
pipeline: this.args.pipeline,
jobs: this.args.jobs,
timestampFormat: this.args.userSettings.timestampFormat,
pipeline: this.pipeline,
jobs: this.jobs,
timestampFormat: this.userSettings.timestampFormat,
onCreate: (jobToMonitor, buildsCallback) => {
this.dataReloader.addCallbackForJobId(
jobToMonitor.id,
Expand All @@ -102,15 +162,27 @@ export default class PipelineJobsTableComponent extends Component {
});
});

this.dataReloader.fetchBuildsForJobs(initialJobIds).then(() => {
this.dataReloader.start();
});
const eventId = this.event?.id;

if (!eventId) {
const initialJobIds = this.dataReloader.newJobIds();

await this.dataReloader.fetchBuildsForJobs(initialJobIds);
}

this.dataReloader.start(eventId);
}

willDestroy() {
super.willDestroy();
@action
update(element, [event]) {
this.data = [];

this.dataReloader.destroy();
if (event) {
this.dataReloader.stop(this.event?.id);
this.event = event;
}

this.initializeDataLoader().then(() => {});
}

get theme() {
Expand All @@ -135,9 +207,4 @@ export default class PipelineJobsTableComponent extends Component {
.fetchBuildsForJobs(this.dataReloader.newJobIds())
.then(() => {});
}

@action
handleDidInsert(element) {
dom.i2svg({ node: element });
}
}
53 changes: 44 additions & 9 deletions app/components/pipeline/jobs/table/dataReloader.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import ENV from 'screwdriver-ui/config/environment';
import { setBuildStatus } from 'screwdriver-ui/utils/pipeline/build';

const QUEUE_NAME = 'jobs-table-data-reloader';

export default class DataReloader {
shuttle;

workflowDataReload;

pageSize;

jobIdsMatchingFilter = [];
Expand All @@ -16,8 +20,11 @@ export default class DataReloader {

numBuilds;

constructor(shuttle, jobIds, pageSize, numBuilds) {
constructor(apiFetchers, jobIds, pageSize, numBuilds) {
const { shuttle, workflowDataReload } = apiFetchers;

this.shuttle = shuttle;
this.workflowDataReload = workflowDataReload;
this.jobIdsMatchingFilter = jobIds.slice(0, pageSize);

jobIds.forEach(jobId => {
Expand Down Expand Up @@ -68,6 +75,14 @@ export default class DataReloader {
this.jobCallbacks[jobId].push(buildsCallback);
}

sendBuildsToCallbacks(jobId, builds) {
if (this.jobCallbacks[jobId]) {
this.jobCallbacks[jobId].forEach(callback => {
callback(builds);
});
}
}

async fetchBuildsForJobs(jobIds) {
if (jobIds.length === 0) {
return;
Expand All @@ -87,12 +102,7 @@ export default class DataReloader {
const { jobId } = buildsForJob;

this.builds[jobId] = buildsForJob.builds;

if (this.jobCallbacks[jobId]) {
this.jobCallbacks[jobId].forEach(callback => {
callback(buildsForJob.builds);
});
}
this.sendBuildsToCallbacks(jobId, buildsForJob.builds);
});
});
}
Expand All @@ -105,7 +115,19 @@ export default class DataReloader {
await this.fetchBuildsForJobs(this.jobIdsMatchingFilter);
}

start() {
start(eventId) {
if (eventId) {
this.workflowDataReload.registerBuildsCallback(
QUEUE_NAME,
eventId,
builds => {
this.parseEventBuilds(builds);
}
);

return;
}

this.intervalId = setInterval(() => {
if (Object.keys(this.jobCallbacks).length === 0) {
return;
Expand All @@ -115,11 +137,24 @@ export default class DataReloader {
}, ENV.APP.BUILD_RELOAD_TIMER);
}

destroy() {
stop(eventId) {
if (this.intervalId) {
clearInterval(this.intervalId);
}
if (eventId) {
this.workflowDataReload.removeBuildsCallback(QUEUE_NAME, eventId);
}

this.intervalId = null;
}

parseEventBuilds(eventBuilds) {
eventBuilds.forEach(eventBuild => {
const { jobId } = eventBuild;
const builds = [eventBuild];

this.builds[jobId] = builds;
this.sendBuildsToCallbacks(jobId, builds);
});
}
}
Loading

0 comments on commit 18fb9a1

Please sign in to comment.