diff --git a/apps/dashboard/app/assets/stylesheets/xdmod.scss b/apps/dashboard/app/assets/stylesheets/xdmod.scss index 3035308a0d..d1b2249f5a 100644 --- a/apps/dashboard/app/assets/stylesheets/xdmod.scss +++ b/apps/dashboard/app/assets/stylesheets/xdmod.scss @@ -21,7 +21,7 @@ .job-analytics { display: flex; justify-content: space-between; - padding: 0.50rem 0.5rem; + padding: 0.75rem 0.5rem; strong { font-weight: 600; diff --git a/apps/dashboard/app/javascript/utils.js b/apps/dashboard/app/javascript/utils.js index d855275eb9..bd9d29adcd 100644 --- a/apps/dashboard/app/javascript/utils.js +++ b/apps/dashboard/app/javascript/utils.js @@ -1,3 +1,4 @@ +import {analyticsPath} from "./config"; export function cssBadgeForState(state){ switch (state) { @@ -80,3 +81,11 @@ export function openLinkInJs(event) { mainDiv.prepend(alertDiv); } } + +export function reportErrorForAnalytics(path, error) { + // error - report back for analytics purposes + const analyticsUrl = new URL(analyticsPath(path), document.location); + analyticsUrl.searchParams.append('error', error); + // Fire and Forget + fetch(analyticsUrl); +} diff --git a/apps/dashboard/app/javascript/xdmod.js b/apps/dashboard/app/javascript/xdmod.js index b219b3d8a5..41ca212dd5 100644 --- a/apps/dashboard/app/javascript/xdmod.js +++ b/apps/dashboard/app/javascript/xdmod.js @@ -1,8 +1,8 @@ import _ from 'lodash'; import {xdmodUrl, analyticsPath} from './config'; -import {today, startOfYear, thirtyDaysAgo} from './utils'; -import { jobsPanel, jobAnalyticsHtml } from './xdmod/jobs'; +import {today, startOfYear, thirtyDaysAgo, reportErrorForAnalytics} from './utils'; +import { jobsPanel } from './xdmod/jobs'; import Handlebars from 'handlebars'; const jobsPageLimit = 10; @@ -179,14 +179,6 @@ function jobsUrl(user){ return url; } -function jobAnalyticsUrl(jobId){ - let url = new URL(`${xdmodUrl()}/rest/v1.0/warehouse/search/jobs/analytics`); - url.searchParams.set('_dc', Date.now()); - url.searchParams.set('realm', jobHelpers.realm); - url.searchParams.set('jobid', jobId); - return url; -} - function aggregateDataUrl(user){ var url = new URL(`${xdmodUrl()}/rest/v1/warehouse/aggregatedata`); url.searchParams.set('_dc', Date.now()); @@ -220,11 +212,6 @@ function renderJobs(context) { panel.replaceChildren(jobs); } -function renderJobAnalytics(jobAnalytics, containerId) { - const analyticsHtml = jobAnalyticsHtml(jobAnalytics, jobHelpers) - $(containerId).html(analyticsHtml); -} - function renderJobsEfficiency(context) { const newConext = _.merge(context, {unit: "jobs", unit_title: "Jobs"}); const templateSource = $('#job-efficiency-template').html(); @@ -254,7 +241,7 @@ function createJobsWidget() { console.error(error); renderJobs({error: error}); - reportError('xdmod_jobs_widget_error', error); + reportErrorForAnalytics('xdmod_jobs_widget_error', error); }); } @@ -268,7 +255,7 @@ function addAnalyticsToJob(jobId) { console.error(error); renderJobAnalytics({error: error}, analyticsContainer); - reportError('xdmod_jobs_analytics_widget_error', error); + reportErrorForAnalytics('xdmod_jobs_analytics_widget_error', error); }); } @@ -313,26 +300,14 @@ function createEfficiencyWidgets() { renderJobsEfficiency({error: error}); renderCoreHoursEfficiency({error: error}); - reportError('xdmod_jobs_widget_error', error); + reportErrorForAnalytics('xdmod_jobs_widget_error', error); }); } -function reportError(path, error) { - // error - report back for analytics purposes - const analyticsUrl = new URL(analyticsPath(path), document.location); - analyticsUrl.searchParams.append('error', error); - fetch(analyticsUrl); -} - jQuery(() => { createJobsWidget(); createEfficiencyWidgets(); // initialize the panels renderJobs({ loading: true }); - - $(`#${jobPanelId}`).on('click', 'tr[data-xdmod-jobid][aria-expanded="true"]', function(event) { - const jobId = event.currentTarget.getAttribute("data-xdmod-jobid"); - addAnalyticsToJob(jobId) - }); }); \ No newline at end of file diff --git a/apps/dashboard/app/javascript/xdmod/jobs.js b/apps/dashboard/app/javascript/xdmod/jobs.js index 6c3cc1cb7a..54401f59af 100644 --- a/apps/dashboard/app/javascript/xdmod/jobs.js +++ b/apps/dashboard/app/javascript/xdmod/jobs.js @@ -1,5 +1,7 @@ 'use strict'; +import {reportErrorForAnalytics} from '../utils'; + export function jobsPanel(context, helpers){ const div = document.createElement('div'); div.classList.add('xdmod'); @@ -9,26 +11,6 @@ export function jobsPanel(context, helpers){ return div; } -export function jobAnalyticsHtml(context, jobHelpers) { - if(context.error !== undefined) { - return errorBody(context.error, jobHelpers); - } - - const dataByKey = context.data.reduce((acc, obj) => { - acc[obj.key] = obj; - return acc; - }, {}); - const cpuEfficiency = jobHelpers.efficiency_label(dataByKey['CPU User']?.value, false) - const memEfficiency = jobHelpers.efficiency_label(dataByKey['Memory Headroom']?.value, true) - const walltimeEfficiency = jobHelpers.efficiency_label(dataByKey['Walltime Accuracy']?.value, false) - const analyticsContent = `
- CPU: ${cpuEfficiency} - Mem: ${memEfficiency} - Walltime: ${walltimeEfficiency} -
`; - return analyticsContent -} - function card(context, helpers) { const div = document.createElement('div'); div.classList.add('card', 'mt-3'); @@ -140,6 +122,11 @@ function tableRows(context, helpers) { tr.setAttribute('data-bs-target', `#details_${job.jobid}`); tr.setAttribute('aria-expanded', 'false'); + tr.addEventListener('click', function(event) { + const jobId = event.currentTarget.getAttribute("data-xdmod-jobid"); + getJobAnalytics(jobId, helpers) + }, { once: true }); + // Job analytics collapse icons const td0 = document.createElement('td'); td0.innerHTML = ` @@ -213,3 +200,48 @@ function noDataRow() { return tr; } + +function renderJobAnalytics(context, containerId, helpers) { + if(context.error !== undefined) { + const errorMessage = errorBody(context.error, helpers); + document.getElementById(containerId).replaceChildren(errorMessage); + return; + } + + const dataByKey = context.data.reduce((acc, obj) => { + acc[obj.key] = obj; + return acc; + }, {}); + const cpuEfficiency = helpers.efficiency_label(dataByKey['CPU User']?.value, false) + const memEfficiency = helpers.efficiency_label(dataByKey['Memory Headroom']?.value, true) + const walltimeEfficiency = helpers.efficiency_label(dataByKey['Walltime Accuracy']?.value, false) + const div = document.createElement('div'); + div.classList.add('job-analytics'); + div.innerHTML = `CPU: ${cpuEfficiency} + Mem: ${memEfficiency} + Walltime: ${walltimeEfficiency}`; + + document.getElementById(containerId).replaceChildren(div); +} + +function jobAnalyticsUrl(jobId, helpers){ + let url = new URL(`${helpers.xdmod_url()}/rest/v1.0/warehouse/search/jobs/analytics`); + url.searchParams.set('_dc', Date.now()); + url.searchParams.set('realm', helpers.realm); + url.searchParams.set('jobid', jobId); + return url; +} + +function getJobAnalytics(jobId, helpers) { + const analyticsContainer = `details_${jobId}`; + fetch(jobAnalyticsUrl(jobId, helpers), { credentials: 'include' }) + .then(response => response.ok ? Promise.resolve(response) : Promise.reject(new Error(response.statusText))) + .then(response => response.json()) + .then((data) => renderJobAnalytics(data, analyticsContainer, helpers)) + .catch((error) => { + console.error(error); + renderJobAnalytics({error: error}, analyticsContainer, helpers); + + reportErrorForAnalytics('xdmod_jobs_analytics_widget_error', error); + }); +}