Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ updates:
directory: "/" # Workflow files stored in the default location of `.github/workflows`
schedule:
interval: "weekly"
groups:
all-dependencies:
patterns:
- "*" # include all actions in a single PR
84 changes: 7 additions & 77 deletions src/webmon_app/reporting/report/view_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,10 +577,7 @@ def get_plot_template_dict(run_object=None, instrument=None, run_id=None):
:param run_id: run_number
"""
plot_dict = {
"plot_data": None,
"html_data": None,
"plot_label_x": "",
"plot_label_y": "",
"update_url": None,
"key": generate_key(instrument, run_id),
}
Expand All @@ -589,104 +586,37 @@ def get_plot_template_dict(run_object=None, instrument=None, run_id=None):
live_data_url = url_template.substitute(instrument=instrument, run_number=run_id)
live_data_url = f"https://{settings.LIVE_DATA_SERVER_DOMAIN}:{settings.LIVE_DATA_SERVER_PORT}{live_data_url}"

# First option: html data
html_data = get_plot_data_from_server(instrument, run_id, "html")
html_data = get_plot_data_from_server(instrument, run_id)
if html_data is not None:
plot_dict["html_data"] = html_data
plot_dict["update_url"] = append_key(live_data_url + "/html/", instrument, run_id)
if extract_ascii_from_div(html_data) is not None:
plot_dict["data_url"] = reverse("report:download_reduced_data", args=[instrument, run_id])
return plot_dict

# Second, json data from the plot server
json_data = get_plot_data_from_server(instrument, run_id, "json")

# Third, local json data for the d3 plots
if json_data:
plot_dict["update_url"] = append_key(live_data_url + "/json/", instrument, run_id)

plot_data, x_label, y_label = extract_d3_data_from_json(json_data)
if plot_data is not None:
plot_dict["plot_data"] = plot_data
plot_dict["plot_label_x"] = x_label
plot_dict["plot_label_y"] = y_label
return plot_dict

return plot_dict


def get_plot_data_from_server(instrument, run_id, data_type="json"):
def get_plot_data_from_server(instrument, run_id):
"""
Get json data from the live data server
Get html data from the live data server

:param instrument: instrument name
:param run_id: run number
:param data_type: data type, either 'json' or 'html'
"""
json_data = None
html_data = None
try:
url_template = string.Template(settings.LIVE_DATA_SERVER)
live_data_url = url_template.substitute(instrument=instrument, run_number=run_id)
live_data_url = (
f"https://{settings.LIVE_DATA_SERVER_DOMAIN}:{settings.LIVE_DATA_SERVER_PORT}{live_data_url}/{data_type}/"
f"https://{settings.LIVE_DATA_SERVER_DOMAIN}:{settings.LIVE_DATA_SERVER_PORT}{live_data_url}/html/"
)
live_data_url = append_key(live_data_url, instrument, run_id)
data_request = requests.get(live_data_url, timeout=1.5)
if data_request.status_code == 200:
json_data = data_request.text
html_data = data_request.text
except: # noqa: E722
logging.exception("Could not pull data from live data server: %s", str(instrument))
return json_data


def extract_d3_data_from_json(json_data):
"""
DEPRECATED

For backward compatibility, extract D3 data from json data for
the old-style interactive plots.

:param json_data: json data block
"""
plot_data = None
x_label = "Q [1/\u00c5]"
y_label = "Absolute reflectivity"

# Return dummy data if not data is coming in.
if json_data is None:
return plot_data, x_label, y_label

try:
data_dict = json.loads(json_data)
if "main_output" in data_dict:
# Old format
if "x" in data_dict["main_output"]:
x_values = data_dict["main_output"]["x"]
y_values = data_dict["main_output"]["y"]
e_values = data_dict["main_output"]["e"]
if "x_label" in data_dict["main_output"]:
x_label = data_dict["main_output"]["x_label"]
if "y_label" in data_dict["main_output"]:
y_label = data_dict["main_output"]["y_label"]
plot_data = [[x_values[i], y_values[i], e_values[i]] for i in range(len(y_values))]
# New format from Mantid
elif "data" in data_dict["main_output"]:
x_values = data_dict["main_output"]["data"]["1"][0]
y_values = data_dict["main_output"]["data"]["1"][1]
e_values = data_dict["main_output"]["data"]["1"][2]
if "axes" in data_dict["main_output"]:
if "xlabel" in data_dict["main_output"]["axes"]:
x_label = data_dict["main_output"]["axes"]["xlabel"]
if "ylabel" in data_dict["main_output"]["axes"]:
y_label = data_dict["main_output"]["axes"]["ylabel"]
if len(data_dict["main_output"]["data"]["1"]) > 3:
dx = data_dict["main_output"]["data"]["1"][3]
plot_data = [[x_values[i], y_values[i], e_values[i], dx[i]] for i in range(len(y_values))]
else:
plot_data = [[x_values[i], y_values[i], e_values[i]] for i in range(len(y_values))]
except: # noqa: E722
logging.exception("Error finding reduced json data:")
return plot_data, x_label, y_label
return html_data


def find_skipped_runs(instrument_id, start_run_number=0):
Expand Down
2 changes: 1 addition & 1 deletion src/webmon_app/reporting/report/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def download_reduced_data(request, instrument, run_id):
:param instrument: instrument name
:param run_id: run number
"""
html_data = view_util.get_plot_data_from_server(instrument, run_id, "html")
html_data = view_util.get_plot_data_from_server(instrument, run_id)
ascii_data = view_util.extract_ascii_from_div(html_data)
if ascii_data is None:
error_msg = "No data available for %s %s" % (instrument, run_id)
Expand Down
21 changes: 21 additions & 0 deletions src/webmon_app/reporting/static/plot_init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Plot Initialization
* Reads plot configuration from JSON script tag and initializes dynamic plot updates
*/

(function() {
'use strict';

function getJsonScriptData(elementId) {
var element = document.getElementById(elementId);
if (element) {
return JSON.parse(element.textContent);
}
return null;
}

var updateUrl = getJsonScriptData('plot-update-url');
if (updateUrl) {
PlotlyDynamicLoader.initializePlotUpdates(updateUrl, 60000);
}
})();
115 changes: 115 additions & 0 deletions src/webmon_app/reporting/static/plotly_dynamic_loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* Plotly Dynamic Loader
* Dynamically loads appropriate Plotly.js versions based on plot requirements
* and handles live plot updates from a REST API endpoint
*/

const PlotlyDynamicLoader = (function() {
'use strict';

const loadedPlotlyScriptUrls = new Set();
const defaultPlotlyVersion = '3.0.1';
const plotlyBaseUrl = '/static/thirdparty/';
const plotlyV2ScriptPath = plotlyBaseUrl + 'plotly-2.9.0.min.js';
const plotlyV3ScriptPath = plotlyBaseUrl + 'plotly-3.0.1.min.js';
let currentPlotHtml = '';

function insertPlotHtml(plotHtml) {
const graphContainer = $('#graph');
if (graphContainer.length === 0) {
console.error("#graph container not found in DOM for plot insertion.");
return;
}
graphContainer.html(plotHtml);
console.log("Plot HTML inserted into #graph.");
}

function loadPlotlyScriptAndRender(requiredVersion, plotHtmlToRender) {
if (!requiredVersion || typeof requiredVersion !== 'string' || requiredVersion.trim() === '') {
console.warn("Plotly version requirement is missing or invalid. Using default:", defaultPlotlyVersion);
requiredVersion = defaultPlotlyVersion;
}

const majorVersion = parseInt(requiredVersion.trim().split('.')[0], 10);
let scriptUrlToLoad = (majorVersion >= 3) ? plotlyV3ScriptPath : plotlyV2ScriptPath;
if (requiredVersion.trim().startsWith('3.')) {
scriptUrlToLoad = plotlyV3ScriptPath;
}

console.log("Plotly.js version required by plot data:", requiredVersion, "-> Mapped to script URL:", scriptUrlToLoad);

if (loadedPlotlyScriptUrls.has(scriptUrlToLoad)) {
console.log("Required Plotly script already loaded:", scriptUrlToLoad);
insertPlotHtml(plotHtmlToRender);
return;
}

console.log("Attempting to load Plotly.js from:", scriptUrlToLoad);
const script = document.createElement('script');
script.src = scriptUrlToLoad;
script.async = false;
script.onload = function() {
console.log("Successfully loaded Plotly script:", scriptUrlToLoad);
loadedPlotlyScriptUrls.add(scriptUrlToLoad);
insertPlotHtml(plotHtmlToRender);
};
script.onerror = function() {
console.error("CRITICAL ERROR: Failed to load Plotly script:", scriptUrlToLoad);
$('#graph').html('<p class="error" style="color:red; font-weight:bold;">Error: Could not load required plotting library. Plot cannot be displayed.</p>');
};
document.head.appendChild(script);
}

function getPlotUpdate(updateUrl) {
$.ajax({
type: "GET",
url: updateUrl,
success: function(newPlotHtml) {
if (currentPlotHtml.localeCompare(newPlotHtml) !== 0) {
currentPlotHtml = newPlotHtml;
console.log("New plot HTML received from server. Determining Plotly.js version requirement...");

const tempDiv = $('<div>').html(newPlotHtml);
const plotGraphDiv = tempDiv.find('div.plotly-graph-div');
let requiredVersion = null;

if (plotGraphDiv.length > 0 && plotGraphDiv.attr('plotlyjs-version')) {
requiredVersion = String(plotGraphDiv.attr('plotlyjs-version'));
console.log("Extracted 'plotlyjs-version' from plot div:", requiredVersion);
} else {
console.log("No 'plotlyjs-version' attribute found on the main plot div. Will use default.");
requiredVersion = defaultPlotlyVersion;
}

tempDiv.remove();

loadPlotlyScriptAndRender(requiredVersion, newPlotHtml);
}
},
error: function(jqXHR, textStatus, errorThrown) {
console.error("Failed to fetch plot update from " + updateUrl + ":", textStatus, errorThrown);
$('#graph').html('<p class="error" style="color:red;">Failed to load plot data. Please try again later.</p>');
},
dataType: "html",
timeout: 25000
});
}

function initializePlotUpdates(updateUrl, intervalMs) {
if (!updateUrl) {
console.warn("PlotlyDynamicLoader: No update URL provided, skipping initialization.");
return;
}

intervalMs = intervalMs || 60000;
getPlotUpdate(updateUrl);
setInterval(function() {
getPlotUpdate(updateUrl);
}, intervalMs);
}

return {
initializePlotUpdates: initializePlotUpdates,
getPlotUpdate: getPlotUpdate
};
})();
Loading