From 541d0f7faa1b349209ba7fdc497499dc8f2eef39 Mon Sep 17 00:00:00 2001 From: Jaden Diefenbaugh <blakcap@users.noreply.github.com> Date: Mon, 3 Apr 2017 11:08:55 +0200 Subject: [PATCH] documentation & minor tweaks --- .../app/controllers/reportController.js | 3 +- .../app/services/experimentsService.js | 9 ++++ .../reports/app/services/groupsService.js | 47 +++++++++++++------ .../reports/app/services/plotService.js | 29 ++++++++++-- .../reports/app/services/reportService.js | 27 +++++++++-- .../web/reports/templates/reports/report.html | 1 + 6 files changed, 90 insertions(+), 26 deletions(-) diff --git a/beat/web/reports/static/reports/app/controllers/reportController.js b/beat/web/reports/static/reports/app/controllers/reportController.js index 677bc8518..2c4700588 100644 --- a/beat/web/reports/static/reports/app/controllers/reportController.js +++ b/beat/web/reports/static/reports/app/controllers/reportController.js @@ -27,8 +27,7 @@ * * This controller is "deprecated" - it needs to be completely removed, and should not be expanded/built upon. */ -angular.module('reportApp').controller('reportController',['$scope', 'reportFactory', function ($scope, reportFactory){ - $scope.q = $q; +angular.module('reportApp').controller('reportController',['$scope', 'reportFactory', 'ReportService', function ($scope, reportFactory, ReportService){ $scope.user; $scope.report_id; $scope.url_prefix; diff --git a/beat/web/reports/static/reports/app/services/experimentsService.js b/beat/web/reports/static/reports/app/services/experimentsService.js index 90a413959..98a1999f0 100644 --- a/beat/web/reports/static/reports/app/services/experimentsService.js +++ b/beat/web/reports/static/reports/app/services/experimentsService.js @@ -146,6 +146,7 @@ angular.module('reportApp').factory('ExperimentsService', ['experimentFactory', }; // make sure there are no invalid experiments in the report's groups + // invalid -> in a group but not in the report const cleanGroups = (validExpNames) => { GroupsService.groups.forEach(g => { g.experiments.forEach(e => { @@ -163,6 +164,7 @@ angular.module('reportApp').factory('ExperimentsService', ['experimentFactory', }); }; + // fetch the exp data and process it const loadExperiments = () => { let expFetch; const namePath = UrlService.getByNamePath(); @@ -181,10 +183,17 @@ angular.module('reportApp').factory('ExperimentsService', ['experimentFactory', return expFetch .then((exps) => { + // save the exp names as they were given by the server Object.keys(exps).forEach(e => rawExpNames.push(e)); + // process the given exp data Object.entries(exps) .forEach(([expName, exp]) => { + // depending on the permissions of the user + // when viewing this report + // the author/toolchain may be hidden. + // to be safe, always process + // both the given name (either the full or short name) const shortName = expName.split('/').pop(); for(let n of [shortName, expName]){ expData[n] = exp; diff --git a/beat/web/reports/static/reports/app/services/groupsService.js b/beat/web/reports/static/reports/app/services/groupsService.js index 6e4460200..e4f8908f9 100644 --- a/beat/web/reports/static/reports/app/services/groupsService.js +++ b/beat/web/reports/static/reports/app/services/groupsService.js @@ -23,8 +23,8 @@ /* * GroupsService * Desc: - * The datastore for the reports app, including data fetched from the - * server and cross-component app state + * The main datastore for the reports app, holding the tree of + * relationships between groups, experiments, aliases, and report items */ angular.module('reportApp').factory('GroupsService', ['reportFactory', function(reportFactory){ let groupsServiceInstance = {}; @@ -47,6 +47,9 @@ angular.module('reportApp').factory('GroupsService', ['reportFactory', function( /* * deepFreeze func found http://stackoverflow.com/a/34776962 * changed to use ES6 functionality + * recursively freezes a JS obj so it cant be altered + * just a nice layer of safety to make sure + * the frontend doesnt change if the backend wont change */ const deepFreeze = (o) => { Object.freeze(o); @@ -114,6 +117,8 @@ angular.module('reportApp').factory('GroupsService', ['reportFactory', function( } // add an exp to this group + // optionally sets the analyzer + // initializes the new exp's alias to its name addExperiment (expName, analyzer) { let res = this._experimentNames.add(expName); if(this._experimentNames.size === 1 && analyzer && analyzer.length > 0){ @@ -127,7 +132,7 @@ angular.module('reportApp').factory('GroupsService', ['reportFactory', function( return res; } - // rm an exp from this group + // rm an exp from this group as well as its alias removeExperiment (expName) { let res = this._experimentNames.delete(expName); if(this._experimentNames.size === 0){ @@ -139,7 +144,7 @@ angular.module('reportApp').factory('GroupsService', ['reportFactory', function( return res; } - // add an item (table or plot) to this group + // add an item (table, plot, or text block) to this group // if the id already exists, it just replaces the old // element with the new one addReportItem (id, content) { @@ -157,7 +162,7 @@ angular.module('reportApp').factory('GroupsService', ['reportFactory', function( } } - // rm an exp from this group + // rm a report item from this group removeReportItem (id) { let idx = this._reportItems .indexOf(this._reportItems.find(o => o.id === id)); @@ -185,9 +190,16 @@ angular.module('reportApp').factory('GroupsService', ['reportFactory', function( // serializes groups as an object with form: // { - // <group name 1>: { experiments: [], reportItems: [], analyzer: '' }, + // <group name 1>: { + // experiments: [], + // reportItems: [], + // analyzer: '', + // aliases: {}, + // idx: 1 + // }, // ... // } + // the 'idx' saves the ordering of the groups groupsServiceInstance.serializeGroups = () => { return groupData .map((g, i) => { return { @@ -222,6 +234,7 @@ angular.module('reportApp').factory('GroupsService', ['reportFactory', function( }; // delete a group + // via MUTATING the groupdata groupsServiceInstance.deleteGroup = (name) => { let idx = groupData.indexOf(groupData.find(g => g.name === name)); if (idx > -1) { @@ -231,11 +244,17 @@ angular.module('reportApp').factory('GroupsService', ['reportFactory', function( // load group info from the serialized format: // { - // <group name 1>: { experiments: [], reportItems: [], analyzer: '' }, + // <group name 1>: { + // experiments: [], + // reportItems: [], + // analyzer: '', + // aliases: {}, + // idx: 1 + // }, // ... // } groupsServiceInstance.loadGroups = (data) => { - // wipe data and load groups + // wipe data groupData.splice(0, groupData.length); let safeData = data || {}; @@ -251,10 +270,15 @@ angular.module('reportApp').factory('GroupsService', ['reportFactory', function( }) .forEach(([groupName, gData]) => { let g = groupsServiceInstance.createGroup(groupName); + + // default group data to empty let analyzer = gData.analyzer || ''; let experiments = gData.experiments || []; let reportItems = gData.reportItems || []; let aliases = gData.aliases || {}; + + // save fields to group + // by MUTATING the group obj g.analyzer = analyzer; experiments.forEach(n => g.addExperiment(n)); reportItems.forEach(i => g.addReportItem(i.id, i.content)); @@ -262,12 +286,5 @@ angular.module('reportApp').factory('GroupsService', ['reportFactory', function( }); }; - // helper to assert that a group exists - function checkForGroup (groupName) { - if(!groupData.find(g => g.name === groupName)){ - throw new Error(`Could not find group "${JSON.stringify(groupName)}"`); - } - } - return groupsServiceInstance; }]); diff --git a/beat/web/reports/static/reports/app/services/plotService.js b/beat/web/reports/static/reports/app/services/plotService.js index 996520b28..d5a6c1ff5 100644 --- a/beat/web/reports/static/reports/app/services/plotService.js +++ b/beat/web/reports/static/reports/app/services/plotService.js @@ -33,15 +33,20 @@ angular.module('reportApp').factory('PlotService', ['UrlService', function(UrlService){ const ps = {}; - // these are provided by reportService + // these are provided by ReportService let plotters; let defaultPlotters; let plotterParameters; let reportNumber; + // this 'queue' idea is the solution to trying to load plots before we receive all the data + // from the server that we need (report info, exp info, etc.). + // until the queue is processed (after which, no queue is needed), + // plots that want to be rendered are added to a queue to wait. const queue = []; let noQueueNeeded = false; + // constructs the payload to send to the server const constructPlotInfo = (group, itemId) => { const content = group.reportItems.find(i => i.id === itemId).content; @@ -57,12 +62,14 @@ angular.module('reportApp').factory('PlotService', ['UrlService', function(UrlSe console.log(config); // sanity check for the plotter & config + // the config's "plotter" field should be equal to the plotter's id field if(!(config.plotter === plotter.id)){ console.error(`Config plotter val "${config.plotter}" != plotter id "${plotter.id}":`); console.log(config); console.log(plotter); }; + // the data to be sent to the server const requestData = { report_number: reportNumber, // exps in the group @@ -82,11 +89,16 @@ angular.module('reportApp').factory('PlotService', ['UrlService', function(UrlSe merged: content.merged === undefined ? true : content.merged }; + // data to be used in to interact with the plot + // users can change the plotter used if theres more than 1 available for that + // plot type const possiblePlotters = plotters .filter(p => p.dataformat === content.type) .map(p => p.name) ; + // users can choose which params to use if more than 1 available + // for that plotter const possibleParameters = plotterParameters .filter(pp => plotter.id === pp.plotter) .map(pp => pp.name) @@ -106,10 +118,14 @@ angular.module('reportApp').factory('PlotService', ['UrlService', function(UrlSe return returnStruct; }; + // makes the call to the server, via some helper funcs found in the global namespace const fetchRenderUsingStruct = (requestData, containerId) => { - const urlPrefix = ''//UrlService.getApiSegment().split('/').filter(s => s.length > 0).join('/'); + // the 'url_prefix' field found throughout the BEAT code is confusing, + // but it seems to always be an empty string now + const urlPrefix = '';//UrlService.getApiSegment().split('/').filter(s => s.length > 0).join('/'); console.log(urlPrefix); - // promisify this utils func + + // promisify this utils func so we dont have to deal with callback hell return new Promise((resolve, reject) => { beat.experiments.utils.displayPlot( // url_prefix @@ -130,11 +146,13 @@ angular.module('reportApp').factory('PlotService', ['UrlService', function(UrlSe }); }; + // helper func to process a request to plot const processItem = (group, itemId, containerId) => { const reqData = constructPlotInfo(group, itemId); return fetchRenderUsingStruct(reqData, containerId); }; + // used if we arent ready to directly service requests const addItemToQueue = (group, itemId, containerId) => { queue.push([ group, @@ -144,7 +162,9 @@ angular.module('reportApp').factory('PlotService', ['UrlService', function(UrlSe }; // called by ReportService, - // or by someone having this report-level info + // or by someone having this report-level info. + // processes the queue and sets the plotService state such that we dont need to use + // the queue after this ps.processQueue = (rsPlotters, rsDefaultPlotters, rsPlotterParameters, rsReportNumber) => { plotters = rsPlotters; defaultPlotters = rsDefaultPlotters; @@ -158,6 +178,7 @@ angular.module('reportApp').factory('PlotService', ['UrlService', function(UrlSe return promises; }; + // chooses whether to add the plot request to a queue or service directly // args: group, itemId, containerId ps.addPlot = (...args) => noQueueNeeded ? processItem(...args) : addItemToQueue(...args); diff --git a/beat/web/reports/static/reports/app/services/reportService.js b/beat/web/reports/static/reports/app/services/reportService.js index 39c534e40..160963c2a 100644 --- a/beat/web/reports/static/reports/app/services/reportService.js +++ b/beat/web/reports/static/reports/app/services/reportService.js @@ -24,7 +24,7 @@ * ReportService * Desc: * Consumes the "report" object from the API and digests it into helper - * funcs and report-wide info. Basically an adaptor. + * funcs and report-wide info. Basically an adaptor & bootstrap-er. */ angular.module('reportApp').factory('ReportService', ['GroupsService', 'plotterFactory', 'PlotService', 'reportFactory', 'UrlService', function(GroupsService, plotterFactory, PlotService, reportFactory, UrlService){ const rs = {}; @@ -39,19 +39,30 @@ angular.module('reportApp').factory('ReportService', ['GroupsService', 'plotterF rs.defaultPlotters = []; rs.plotterParameters = []; + // processed the report data received from the server, + // and bootstraps the state of various services rs.processReport = (report) => { console.log(report); + // useful info about the app rs.isAnonymous = report.anonymous; rs.isOwner = report.is_owner; rs.status = report.status; rs.number = report.number; rs.user = report.user; + // start up our GroupsService GroupsService.loadGroups(report.content.groups); + // if the report should not change, + // add a nice layer of immutability by freezing the Group tree + // (see GroupsService for more info) const isEditable = rs.isOwner && rs.status === 'editable' && !rs.isAnonymous; GroupsService.setEditable(isEditable); + console.log(GroupsService.groups); + + // fetch all our plotter data + // these three fetches do not depend on eachother const pPlotters = plotterFactory.getPlotters('') .then(res => { res.data.forEach(p => rs.plotters.push(p)); @@ -67,31 +78,37 @@ angular.module('reportApp').factory('ReportService', ['GroupsService', 'plotterF res.data.forEach(p => rs.plotterParameters.push(p)); }); + // process the fetched plot info return Promise.all([pPlotters, pDefaults, pParams]) .then(() => PlotService.processQueue(rs.plotters, rs.defaultPlotters, rs.plotterParameters, rs.number)) + ; }; + // fetch the report data using either the by-name or by-number scheme, + // according to what URLService found rs.fetchReport = () => { const nameSeg = UrlService.getNameSegment(); const numSeg = UrlService.getNumberSegment(); if(nameSeg){ + // the nameSeg is something like 'report/<user>/<report name>/' let [user, reportId] = nameSeg.split('/').filter(s => s.length > 0).slice(1); - console.log(user); - console.log(reportId); - return reportFactory.getReportInformation(user, reportId, '') .then(res => rs.processReport(res.data)); } else if(numSeg) { + // the numSeg is something like 'report/<report number>/' let [number] = numSeg.split('/').filter(s => s.length > 0).slice(1); return reportFactory.getReportInformationFromNumber(number, '') .then(res => rs.processReport(res.data)); + } else { + console.error('UrlService could not parse the current URL'); } }; - rs.fetchReport(); + rs.fetchReport() + .catch(e => console.error(e)); console.log(rs); diff --git a/beat/web/reports/templates/reports/report.html b/beat/web/reports/templates/reports/report.html index ed18936f9..d27e338a1 100644 --- a/beat/web/reports/templates/reports/report.html +++ b/beat/web/reports/templates/reports/report.html @@ -77,6 +77,7 @@ <!-- controllers --> <script src="{% fingerprint "reports/app/controllers/reportController.js" %}" type="text/javascript" charset="utf-8"></script> + <script src="{% fingerprint "reports/app/controllers/groupsController.js" %}" type="text/javascript" charset="utf-8"></script> <!-- factories --> <script src="{% fingerprint "reports/app/factories/reportFactory.js" %}" type="text/javascript" charset="utf-8"></script> -- GitLab