mirror of
				https://github.com/aclindsa/moneygo.git
				synced 2025-10-30 17:33:26 -04:00 
			
		
		
		
	Add lots of backend and back-frontend report infrastructure
This commit is contained in:
		
							
								
								
									
										1
									
								
								db.go
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								db.go
									
									
									
									
									
								
							| @@ -22,6 +22,7 @@ func initDB() *gorp.DbMap { | ||||
| 	dbmap.AddTableWithName(Security{}, "securities").SetKeys(true, "SecurityId") | ||||
| 	dbmap.AddTableWithName(Transaction{}, "transactions").SetKeys(true, "TransactionId") | ||||
| 	dbmap.AddTableWithName(Split{}, "splits").SetKeys(true, "SplitId") | ||||
| 	dbmap.AddTableWithName(Report{}, "reports").SetKeys(true, "ReportId") | ||||
|  | ||||
| 	err = dbmap.CreateTablesIfNotExists() | ||||
| 	if err != nil { | ||||
|   | ||||
| @@ -4,55 +4,116 @@ var ErrorActions = require('./ErrorActions'); | ||||
|  | ||||
| var models = require('../models.js'); | ||||
| var Report = models.Report; | ||||
| var Tabulation = models.Tabulation; | ||||
| var Error = models.Error; | ||||
|  | ||||
| function fetchReport(reportName) { | ||||
| function fetchReports() { | ||||
| 	return { | ||||
| 		type: ReportConstants.FETCH_REPORT, | ||||
| 		reportName: reportName | ||||
| 		type: ReportConstants.FETCH_REPORTS | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function reportFetched(report) { | ||||
| function reportsFetched(reports) { | ||||
| 	return { | ||||
| 		type: ReportConstants.REPORT_FETCHED, | ||||
| 		type: ReportConstants.REPORTS_FETCHED, | ||||
| 		reports: reports | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function createReport() { | ||||
| 	return { | ||||
| 		type: ReportConstants.CREATE_REPORT | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function reportCreated(report) { | ||||
| 	return { | ||||
| 		type: ReportConstants.REPORT_CREATED, | ||||
| 		report: report | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function selectReport(report, seriesTraversal) { | ||||
| function updateReport() { | ||||
| 	return { | ||||
| 		type: ReportConstants.SELECT_REPORT, | ||||
| 		report: report, | ||||
| 		seriesTraversal: seriesTraversal | ||||
| 		type: ReportConstants.UPDATE_REPORT | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function reportSelected(flattenedReport, seriesTraversal) { | ||||
| function reportUpdated(report) { | ||||
| 	return { | ||||
| 		type: ReportConstants.REPORT_UPDATED, | ||||
| 		report: report | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function removeReport() { | ||||
| 	return { | ||||
| 		type: ReportConstants.REMOVE_REPORT | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function reportRemoved(reportId) { | ||||
| 	return { | ||||
| 		type: ReportConstants.REPORT_REMOVED, | ||||
| 		reportId: reportId | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function reportSelected(report) { | ||||
| 	return { | ||||
| 		type: ReportConstants.REPORT_SELECTED, | ||||
| 		report: flattenedReport, | ||||
| 		report: report | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function tabulateReport(report) { | ||||
| 	return { | ||||
| 		type: ReportConstants.TABULATE_REPORT, | ||||
| 		report: report | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function reportTabulated(report, tabulation) { | ||||
| 	return { | ||||
| 		type: ReportConstants.REPORT_TABULATED, | ||||
| 		report: report, | ||||
| 		tabulation: tabulation | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function selectionCleared() { | ||||
| 	return { | ||||
| 		type: ReportConstants.SELECTION_CLEARED | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function seriesSelected(flattenedTabulation, seriesTraversal) { | ||||
| 	return { | ||||
| 		type: ReportConstants.SERIES_SELECTED, | ||||
| 		tabulation: flattenedTabulation, | ||||
| 		seriesTraversal: seriesTraversal | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function fetch(report) { | ||||
| function fetchAll() { | ||||
| 	return function (dispatch) { | ||||
| 		dispatch(fetchReport(report)); | ||||
| 		dispatch(fetchReports()); | ||||
|  | ||||
| 		$.ajax({ | ||||
| 			type: "GET", | ||||
| 			dataType: "json", | ||||
| 			url: "report/"+report+"/", | ||||
| 			url: "report/", | ||||
| 			success: function(data, status, jqXHR) { | ||||
| 				var e = new Error(); | ||||
| 				e.fromJSON(data); | ||||
| 				if (e.isError()) { | ||||
| 					dispatch(ErrorActions.serverError(e)); | ||||
| 				} else { | ||||
| 					var r = new Report(); | ||||
| 					r.fromJSON(data); | ||||
| 					dispatch(reportFetched(r)); | ||||
| 					dispatch(reportsFetched(data.reports.map(function(json) { | ||||
| 						var r = new Report(); | ||||
| 						r.fromJSON(json); | ||||
| 						return r; | ||||
| 					}))); | ||||
| 				} | ||||
| 			}, | ||||
| 			error: function(jqXHR, status, error) { | ||||
| @@ -62,14 +123,117 @@ function fetch(report) { | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| function select(report, seriesTraversal) { | ||||
| function create(report) { | ||||
| 	return function (dispatch) { | ||||
| 		dispatch(createReport()); | ||||
|  | ||||
| 		$.ajax({ | ||||
| 			type: "POST", | ||||
| 			dataType: "json", | ||||
| 			url: "report/", | ||||
| 			data: {report: report.toJSON()}, | ||||
| 			success: function(data, status, jqXHR) { | ||||
| 				var e = new Error(); | ||||
| 				e.fromJSON(data); | ||||
| 				if (e.isError()) { | ||||
| 					dispatch(ErrorActions.serverError(e)); | ||||
| 				} else { | ||||
| 					var a = new Report(); | ||||
| 					a.fromJSON(data); | ||||
| 					dispatch(reportCreated(a)); | ||||
| 				} | ||||
| 			}, | ||||
| 			error: function(jqXHR, status, error) { | ||||
| 				dispatch(ErrorActions.ajaxError(error)); | ||||
| 			} | ||||
| 		}); | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| function update(report) { | ||||
| 	return function (dispatch) { | ||||
| 		dispatch(updateReport()); | ||||
|  | ||||
| 		$.ajax({ | ||||
| 			type: "PUT", | ||||
| 			dataType: "json", | ||||
| 			url: "report/"+report.ReportId+"/", | ||||
| 			data: {report: report.toJSON()}, | ||||
| 			success: function(data, status, jqXHR) { | ||||
| 				var e = new Error(); | ||||
| 				e.fromJSON(data); | ||||
| 				if (e.isError()) { | ||||
| 					dispatch(ErrorActions.serverError(e)); | ||||
| 				} else { | ||||
| 					var a = new Report(); | ||||
| 					a.fromJSON(data); | ||||
| 					dispatch(reportUpdated(a)); | ||||
| 				} | ||||
| 			}, | ||||
| 			error: function(jqXHR, status, error) { | ||||
| 				dispatch(ErrorActions.ajaxError(error)); | ||||
| 			} | ||||
| 		}); | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| function remove(report) { | ||||
| 	return function(dispatch) { | ||||
| 		dispatch(removeReport()); | ||||
|  | ||||
| 		$.ajax({ | ||||
| 			type: "DELETE", | ||||
| 			dataType: "json", | ||||
| 			url: "report/"+report.ReportId+"/", | ||||
| 			success: function(data, status, jqXHR) { | ||||
| 				var e = new Error(); | ||||
| 				e.fromJSON(data); | ||||
| 				if (e.isError()) { | ||||
| 					dispatch(ErrorActions.serverError(e)); | ||||
| 				} else { | ||||
| 					dispatch(reportRemoved(report.ReportId)); | ||||
| 				} | ||||
| 			}, | ||||
| 			error: function(jqXHR, status, error) { | ||||
| 				dispatch(ErrorActions.ajaxError(error)); | ||||
| 			} | ||||
| 		}); | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| function tabulate(report) { | ||||
| 	return function (dispatch) { | ||||
| 		dispatch(tabulateReport(report)); | ||||
|  | ||||
| 		$.ajax({ | ||||
| 			type: "GET", | ||||
| 			dataType: "json", | ||||
| 			url: "report/"+report.ReportId+"/tabulation/", | ||||
| 			success: function(data, status, jqXHR) { | ||||
| 				var e = new Error(); | ||||
| 				e.fromJSON(data); | ||||
| 				if (e.isError()) { | ||||
| 					dispatch(ErrorActions.serverError(e)); | ||||
| 				} else { | ||||
| 					var t = new Tabulation(); | ||||
| 					t.fromJSON(data); | ||||
| 					dispatch(reportTabulated(report, t)); | ||||
| 				} | ||||
| 			}, | ||||
| 			error: function(jqXHR, status, error) { | ||||
| 				dispatch(ErrorActions.ajaxError(error)); | ||||
| 			} | ||||
| 		}); | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| function selectSeries(tabulation, seriesTraversal) { | ||||
| 	return function (dispatch) { | ||||
| 		if (!seriesTraversal) | ||||
| 			seriesTraversal = []; | ||||
| 		dispatch(selectReport(report, seriesTraversal)); | ||||
|  | ||||
| 		// Descend the tree to the right series to flatten | ||||
| 		var series = report; | ||||
| 		var series = tabulation; | ||||
| 		for (var i=0; i < seriesTraversal.length; i++) { | ||||
| 			if (!series.Series.hasOwnProperty(seriesTraversal[i])) { | ||||
| 				dispatch(ErrorActions.clientError("Invalid series")); | ||||
| @@ -87,23 +251,27 @@ function select(report, seriesTraversal) { | ||||
|  | ||||
| 		// Add back in any values from the current level | ||||
| 		if (series.hasOwnProperty('Values')) | ||||
| 			flattenedSeries[Report.topLevelAccountName()] = series.Values; | ||||
| 			flattenedSeries[Tabulation.topLevelSeriesName()] = series.Values; | ||||
|  | ||||
| 		var flattenedReport = new Report(); | ||||
| 		var flattenedTabulation = new Tabulation(); | ||||
|  | ||||
| 		flattenedReport.ReportId = report.ReportId; | ||||
| 		flattenedReport.Title = report.Title; | ||||
| 		flattenedReport.Subtitle = report.Subtitle; | ||||
| 		flattenedReport.XAxisLabel = report.XAxisLabel; | ||||
| 		flattenedReport.YAxisLabel = report.YAxisLabel; | ||||
| 		flattenedReport.Labels = report.Labels.slice(); | ||||
| 		flattenedReport.FlattenedSeries = flattenedSeries; | ||||
| 		flattenedTabulation.ReportId = tabulation.ReportId; | ||||
| 		flattenedTabulation.Title = tabulation.Title; | ||||
| 		flattenedTabulation.Subtitle = tabulation.Subtitle; | ||||
| 		flattenedTabulation.Units = tabulation.Units; | ||||
| 		flattenedTabulation.Labels = tabulation.Labels.slice(); | ||||
| 		flattenedTabulation.FlattenedSeries = flattenedSeries; | ||||
|  | ||||
| 		dispatch(reportSelected(flattenedReport, seriesTraversal)); | ||||
| 		dispatch(seriesSelected(flattenedTabulation, seriesTraversal)); | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
| 	fetch: fetch, | ||||
| 	select: select | ||||
| 	fetchAll: fetchAll, | ||||
| 	create: create, | ||||
| 	update: update, | ||||
| 	remove: remove, | ||||
| 	tabulate: tabulate, | ||||
| 	select: reportSelected, | ||||
| 	selectSeries: selectSeries | ||||
| }; | ||||
|   | ||||
| @@ -6,87 +6,101 @@ var Button = ReactBootstrap.Button; | ||||
| var Panel = ReactBootstrap.Panel; | ||||
|  | ||||
| var StackedBarChart = require('../components/StackedBarChart'); | ||||
| var PieChart = require('../components/PieChart'); | ||||
|  | ||||
| var models = require('../models') | ||||
| var Report = models.Report; | ||||
| var Tabulation = models.Tabulation; | ||||
|  | ||||
| class ReportsTab extends React.Component { | ||||
| 	constructor() { | ||||
| 		super(); | ||||
| 		this.state = { | ||||
| 			initialized: false | ||||
| 		} | ||||
| 		this.onSelectSeries = this.handleSelectSeries.bind(this); | ||||
| 	} | ||||
| 	componentWillMount() { | ||||
| 		this.props.onFetchReport("monthly_expenses"); | ||||
| 		this.props.onFetchAllReports(); | ||||
| 	} | ||||
| 	componentWillReceiveProps(nextProps) { | ||||
| 		if (nextProps.reports['monthly_expenses'] && !nextProps.selectedReport.report) { | ||||
| 			this.props.onSelectReport(nextProps.reports['monthly_expenses'], []); | ||||
| 		var selected = nextProps.reports.selected; | ||||
| 		if (!this.state.initialized) { | ||||
| 			if (selected == -1 && | ||||
| 					nextProps.reports.list.length > 0) | ||||
| 				nextProps.onSelectReport(nextProps.reports.map[nextProps.reports.list[0]]); | ||||
| 			this.setState({initialized: true}); | ||||
| 		} else if (selected != -1 && !nextProps.reports.tabulations.hasOwnProperty(selected)) { | ||||
| 			nextProps.onTabulateReport(nextProps.reports.map[nextProps.reports.list[0]]); | ||||
| 		} else if (selected != -1 && nextProps.reports.selectedTabulation == null) { | ||||
| 			nextProps.onSelectSeries(nextProps.reports.tabulations[nextProps.reports.list[0]]); | ||||
| 		} | ||||
| 	} | ||||
| 	handleSelectSeries(series) { | ||||
| 		if (series == Report.topLevelAccountName()) | ||||
| 		if (series == Tabulation.topLevelSeriesName()) | ||||
| 			return; | ||||
| 		var seriesTraversal = this.props.selectedReport.seriesTraversal.slice(); | ||||
| 		var seriesTraversal = this.props.selectedTabulation.seriesTraversal.slice(); | ||||
| 		seriesTraversal.push(series); | ||||
| 		this.props.onSelectReport(this.props.reports[this.props.selectedReport.report.ReportId], seriesTraversal); | ||||
| 		var selectedTabulation = this.props.reports.tabulations[this.props.reports.selected]; | ||||
| 		this.props.onSelectSeries(selectedTabulation, seriesTraversal); | ||||
| 	} | ||||
| 	render() { | ||||
| 		var report = []; | ||||
| 		if (this.props.selectedReport.report) { | ||||
| 			var titleTracks = []; | ||||
| 			var seriesTraversal = []; | ||||
|  | ||||
| 			for (var i = 0; i < this.props.selectedReport.seriesTraversal.length; i++) { | ||||
| 				var name = this.props.selectedReport.report.Title; | ||||
| 				if (i > 0) | ||||
| 					name = this.props.selectedReport.seriesTraversal[i-1]; | ||||
|  | ||||
| 				// Make a closure for going up the food chain | ||||
| 				var self = this; | ||||
| 				var navOnClick = function() { | ||||
| 					var onSelectReport = self.props.onSelectReport; | ||||
| 					var report = self.props.reports[self.props.selectedReport.report.ReportId]; | ||||
| 					var mySeriesTraversal = seriesTraversal.slice(); | ||||
| 					return function() { | ||||
| 						onSelectReport(report, mySeriesTraversal); | ||||
| 					}; | ||||
| 				}(); | ||||
| 				titleTracks.push(( | ||||
| 					<Button key={i*2} bsStyle="link" | ||||
| 							onClick={navOnClick}> | ||||
| 						{name} | ||||
| 					</Button> | ||||
| 				)); | ||||
| 				titleTracks.push((<span key={i*2+1}>/</span>)); | ||||
| 				seriesTraversal.push(this.props.selectedReport.seriesTraversal[i]); | ||||
| 			} | ||||
| 			if (titleTracks.length == 0) { | ||||
| 				titleTracks.push(( | ||||
| 					<Button key={0} bsStyle="link"> | ||||
| 						{this.props.selectedReport.report.Title} | ||||
| 					</Button> | ||||
| 				)); | ||||
| 			} else { | ||||
| 				var i = this.props.selectedReport.seriesTraversal.length-1; | ||||
| 				titleTracks.push(( | ||||
| 					<Button key={i*2+2} bsStyle="link"> | ||||
| 					{this.props.selectedReport.seriesTraversal[i]} | ||||
| 					</Button> | ||||
| 				)); | ||||
| 			} | ||||
|  | ||||
| 			report = (<Panel header={titleTracks}> | ||||
| 				<StackedBarChart | ||||
| 					report={this.props.selectedReport.report} | ||||
| 					onSelectSeries={this.onSelectSeries} | ||||
| 					seriesTraversal={this.props.selectedReport.seriesTraversal} /> | ||||
| 				</Panel> | ||||
| 		var selectedTabulation = this.props.reports.selectedTabulation; | ||||
| 		if (!selectedTabulation) { | ||||
| 			return ( | ||||
| 				<div></div> | ||||
| 			); | ||||
| 		} | ||||
|  | ||||
| 		var titleTracks = []; | ||||
| 		var seriesTraversal = []; | ||||
|  | ||||
| 		for (var i = 0; i < this.props.selectedTabulation.seriesTraversal.length; i++) { | ||||
| 			var name = this.props.selectedTabulation.tabulation.Title; | ||||
| 			if (i > 0) | ||||
| 				name = this.props.selectedTabulation.seriesTraversal[i-1]; | ||||
|  | ||||
| 			// Make a closure for going up the food chain | ||||
| 			var self = this; | ||||
| 			var navOnClick = function() { | ||||
| 				var onSelectTabulation = self.props.onSelectTabulation; | ||||
| 				var report = self.props.reports[self.props.selectedTabulation.tabulation.ReportId]; | ||||
| 				var mySeriesTraversal = seriesTraversal.slice(); | ||||
| 				return function() { | ||||
| 					onSelectTabulation(report, mySeriesTraversal); | ||||
| 				}; | ||||
| 			}(); | ||||
| 			titleTracks.push(( | ||||
| 				<Button key={i*2} bsStyle="link" | ||||
| 						onClick={navOnClick}> | ||||
| 					{name} | ||||
| 				</Button> | ||||
| 			)); | ||||
| 			titleTracks.push((<span key={i*2+1}>/</span>)); | ||||
| 			seriesTraversal.push(this.props.selectedTabulation.seriesTraversal[i]); | ||||
| 		} | ||||
| 		if (titleTracks.length == 0) { | ||||
| 			titleTracks.push(( | ||||
| 				<Button key={0} bsStyle="link"> | ||||
| 					{this.props.selectedTabulation.tabulation.Title} | ||||
| 				</Button> | ||||
| 			)); | ||||
| 		} else { | ||||
| 			var i = this.props.selectedTabulation.seriesTraversal.length-1; | ||||
| 			titleTracks.push(( | ||||
| 				<Button key={i*2+2} bsStyle="link"> | ||||
| 				{this.props.selectedTabulation.seriesTraversal[i]} | ||||
| 				</Button> | ||||
| 			)); | ||||
| 		} | ||||
|  | ||||
| 		return ( | ||||
| 			<div> | ||||
| 				{report} | ||||
| 			</div> | ||||
| 			<Panel header={titleTracks}> | ||||
| 				<PieChart | ||||
| 					report={this.props.selectedTabulation.tabulation} | ||||
| 					onSelectSeries={this.onSelectSeries} | ||||
| 					seriesTraversal={this.props.selectedTabulation.seriesTraversal} /> | ||||
| 			</Panel> | ||||
| 		); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,17 @@ | ||||
| var keyMirror = require('keymirror'); | ||||
|  | ||||
| module.exports = keyMirror({ | ||||
| 	FETCH_REPORT: null, | ||||
| 	REPORT_FETCHED: null, | ||||
| 	SELECT_REPORT: null, | ||||
| 	REPORT_SELECTED: null | ||||
| 	FETCH_REPORTS: null, | ||||
| 	REPORTS_FETCHED: null, | ||||
| 	CREATE_REPORT: null, | ||||
| 	REPORT_CREATED: null, | ||||
| 	UPDATE_REPORT: null, | ||||
| 	REPORT_UPDATED: null, | ||||
| 	REMOVE_REPORT: null, | ||||
| 	REPORT_REMOVED: null, | ||||
| 	TABULATE_REPORT: null, | ||||
| 	REPORT_TABULATED: null, | ||||
| 	REPORT_SELECTED: null, | ||||
| 	SELECTION_CLEARED: null, | ||||
| 	SERIES_SELECTED: null | ||||
| }); | ||||
|   | ||||
| @@ -5,15 +5,19 @@ var ReportsTab = require('../components/ReportsTab'); | ||||
|  | ||||
| function mapStateToProps(state) { | ||||
| 	return { | ||||
| 		reports: state.reports, | ||||
| 		selectedReport: state.selectedReport | ||||
| 		reports: state.reports | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function mapDispatchToProps(dispatch) { | ||||
| 	return { | ||||
| 		onFetchReport: function(reportname) {dispatch(ReportActions.fetch(reportname))}, | ||||
| 		onSelectReport: function(report, seriesTraversal) {dispatch(ReportActions.select(report, seriesTraversal))} | ||||
| 		onFetchAllReports: function() {dispatch(ReportActions.fetchAll())}, | ||||
| 		onCreateReport: function(report) {dispatch(ReportActions.create(report))}, | ||||
| 		onUpdateReport: function(report) {dispatch(ReportActions.update(report))}, | ||||
| 		onDeleteReport: function(report) {dispatch(ReportActions.remove(report))}, | ||||
| 		onSelectReport: function(report) {dispatch(ReportActions.select(report))}, | ||||
| 		onTabulateReport: function(report) {dispatch(ReportActions.tabulate(report))}, | ||||
| 		onSelectSeries: function(tabulation, seriesTraversal) {dispatch(ReportActions.selectSeries(tabulation, seriesTraversal))} | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										18
									
								
								js/models.js
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								js/models.js
									
									
									
									
									
								
							| @@ -494,18 +494,17 @@ class Series { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| class Report { | ||||
| class Tabulation { | ||||
| 	constructor() { | ||||
| 		this.ReportId = ""; | ||||
| 		this.Title = ""; | ||||
| 		this.Subtitle = ""; | ||||
| 		this.XAxisLabel = ""; | ||||
| 		this.YAxisLabel = ""; | ||||
| 		this.Units = ""; | ||||
| 		this.Labels = []; | ||||
| 		this.Series = {}; | ||||
| 		this.FlattenedSeries = {}; | ||||
| 	} | ||||
| 	static topLevelAccountName() { | ||||
| 	static topLevelSeriesName() { | ||||
| 		return "(top level)" | ||||
| 	} | ||||
| 	toJSON() { | ||||
| @@ -513,8 +512,7 @@ class Report { | ||||
| 		json_obj.ReportId = this.ReportId; | ||||
| 		json_obj.Title = this.Title; | ||||
| 		json_obj.Subtitle = this.Subtitle; | ||||
| 		json_obj.XAxisLabel = this.XAxisLabel; | ||||
| 		json_obj.YAxisLabel = this.YAxisLabel; | ||||
| 		json_obj.Units = this.Units; | ||||
| 		json_obj.Labels = this.Labels; | ||||
| 		json_obj.Series = {}; | ||||
| 		for (var series in this.Series) { | ||||
| @@ -532,10 +530,8 @@ class Report { | ||||
| 			this.Title = json_obj.Title; | ||||
| 		if (json_obj.hasOwnProperty("Subtitle")) | ||||
| 			this.Subtitle = json_obj.Subtitle; | ||||
| 		if (json_obj.hasOwnProperty("XAxisLabel")) | ||||
| 			this.XAxisLabel = json_obj.XAxisLabel; | ||||
| 		if (json_obj.hasOwnProperty("YAxisLabel")) | ||||
| 			this.YAxisLabel = json_obj.YAxisLabel; | ||||
| 		if (json_obj.hasOwnProperty("Units")) | ||||
| 			this.Units = json_obj.Units; | ||||
| 		if (json_obj.hasOwnProperty("Labels")) | ||||
| 			this.Labels = json_obj.Labels; | ||||
| 		if (json_obj.hasOwnProperty("Series")) { | ||||
| @@ -582,7 +578,7 @@ module.exports = { | ||||
| 	Account: Account, | ||||
| 	Split: Split, | ||||
| 	Transaction: Transaction, | ||||
| 	Report: Report, | ||||
| 	Tabulation: Tabulation, | ||||
| 	OFXDownload: OFXDownload, | ||||
| 	Error: Error, | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,6 @@ var SecurityTemplateReducer = require('./SecurityTemplateReducer'); | ||||
| var SelectedAccountReducer = require('./SelectedAccountReducer'); | ||||
| var SelectedSecurityReducer = require('./SelectedSecurityReducer'); | ||||
| var ReportReducer = require('./ReportReducer'); | ||||
| var SelectedReportReducer = require('./SelectedReportReducer'); | ||||
| var TransactionReducer = require('./TransactionReducer'); | ||||
| var TransactionPageReducer = require('./TransactionPageReducer'); | ||||
| var ImportReducer = require('./ImportReducer'); | ||||
| @@ -23,7 +22,6 @@ module.exports = Redux.combineReducers({ | ||||
| 	selectedAccount: SelectedAccountReducer, | ||||
| 	selectedSecurity: SelectedSecurityReducer, | ||||
| 	reports: ReportReducer, | ||||
| 	selectedReport: SelectedReportReducer, | ||||
| 	transactions: TransactionReducer, | ||||
| 	transactionPage: TransactionPageReducer, | ||||
| 	imports: ImportReducer, | ||||
|   | ||||
| @@ -3,15 +3,78 @@ var assign = require('object-assign'); | ||||
| var ReportConstants = require('../constants/ReportConstants'); | ||||
| var UserConstants = require('../constants/UserConstants'); | ||||
|  | ||||
| module.exports = function(state = {}, action) { | ||||
| const initialState = { | ||||
| 	map: {}, | ||||
| 	tabulations: {}, | ||||
| 	list: [], | ||||
| 	selected: -1, | ||||
| 	selectedTabulation: null, | ||||
| 	seriesTraversal: [] | ||||
| }; | ||||
|  | ||||
| module.exports = function(state = initialState, action) { | ||||
| 	switch (action.type) { | ||||
| 		case ReportConstants.REPORT_FETCHED: | ||||
| 			var report = action.report; | ||||
| 		case ReportConstants.REPORTS_FETCHED: | ||||
| 			var selected = -1; | ||||
| 			var reports = {}; | ||||
| 			var list = []; | ||||
| 			for (var i = 0; i < action.reports.length; i++) { | ||||
| 				var report = action.reports[i]; | ||||
| 				reports[report.ReportId] = report; | ||||
| 				list.push(report.ReportId); | ||||
| 				if (state.selected == report.ReportId) | ||||
| 					selected = state.selected; | ||||
| 			} | ||||
| 			return assign({}, state, { | ||||
| 				map: reports, | ||||
| 				list: list, | ||||
| 				tabulations: {}, | ||||
| 				selected: selected | ||||
| 			}); | ||||
| 		case ReportConstants.REPORT_CREATED: | ||||
| 		case ReportConstants.REPORT_UPDATED: | ||||
| 			var report = action.report; | ||||
| 			var reports = assign({}, state.map, { | ||||
| 				[report.ReportId]: report | ||||
| 			}); | ||||
|  | ||||
| 			var list = []; | ||||
| 			for (var reportId in reports) { | ||||
| 				if (reports.hasOwnProperty(reportId)) | ||||
| 					list.push(report.ReportId); | ||||
| 			} | ||||
| 			return assign({}, state, { | ||||
| 				map: reports, | ||||
| 				list: list | ||||
| 			}); | ||||
| 		case ReportConstants.REPORT_REMOVED: | ||||
| 			var selected = state.selected; | ||||
| 			if (action.reportId == selected) | ||||
| 				selected = -1; | ||||
| 			var reports = assign({}, state.map); | ||||
| 			delete reports[action.reportId]; | ||||
| 			return assign({}, state, { | ||||
| 				map: reports, | ||||
| 				selected: selected | ||||
| 			}); | ||||
| 		case ReportConstants.REPORT_SELECTED: | ||||
| 			return assign({}, state, { | ||||
| 				selected: action.report.ReportId, | ||||
| 				selectedTabulation: null, | ||||
| 				seriesTraversal: [] | ||||
| 			}); | ||||
| 		case ReportConstants.TABULATION_FETCHED: | ||||
| 			var tabulation = action.tabulation; | ||||
| 			return assign({}, state, { | ||||
| 				[tabulation.ReportId]: tabulation | ||||
| 			}); | ||||
| 		case ReportConstants.SERIES_SELECTED: | ||||
| 			return { | ||||
| 				selectedTabulation: action.tabulation, | ||||
| 				seriesTraversal: action.seriesTraversal | ||||
| 			}; | ||||
| 		case UserConstants.USER_LOGGEDOUT: | ||||
| 			return {}; | ||||
| 			return initialState; | ||||
| 		default: | ||||
| 			return state; | ||||
| 	} | ||||
|   | ||||
| @@ -1,23 +0,0 @@ | ||||
| var assign = require('object-assign'); | ||||
|  | ||||
| var ReportConstants = require('../constants/ReportConstants'); | ||||
| var UserConstants = require('../constants/UserConstants'); | ||||
|  | ||||
| const initialState = { | ||||
| 	report: null, | ||||
| 	seriesTraversal: [] | ||||
| }; | ||||
|  | ||||
| module.exports = function(state = initialState, action) { | ||||
| 	switch (action.type) { | ||||
| 		case ReportConstants.REPORT_SELECTED: | ||||
| 			return { | ||||
| 				report: action.report, | ||||
| 				seriesTraversal: action.seriesTraversal | ||||
| 			}; | ||||
| 		case UserConstants.USER_LOGGEDOUT: | ||||
| 			return initialState; | ||||
| 		default: | ||||
| 			return state; | ||||
| 	} | ||||
| }; | ||||
							
								
								
									
										263
									
								
								reports.go
									
									
									
									
									
								
							
							
						
						
									
										263
									
								
								reports.go
									
									
									
									
									
								
							| @@ -4,14 +4,21 @@ import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/yuin/gopher-lua" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| var reportTabulationRE *regexp.Regexp | ||||
|  | ||||
| func init() { | ||||
| 	reportTabulationRE = regexp.MustCompile(`^/report/[0-9]+/tabulation/?$`) | ||||
| } | ||||
|  | ||||
| //type and value to store user in lua's Context | ||||
| type key int | ||||
|  | ||||
| @@ -24,19 +31,11 @@ const ( | ||||
|  | ||||
| const luaTimeoutSeconds time.Duration = 30 // maximum time a lua request can run for | ||||
|  | ||||
| type Series struct { | ||||
| 	Values []float64 | ||||
| 	Series map[string]*Series | ||||
| } | ||||
|  | ||||
| type Report struct { | ||||
| 	ReportId   string | ||||
| 	Title      string | ||||
| 	Subtitle   string | ||||
| 	XAxisLabel string | ||||
| 	YAxisLabel string | ||||
| 	Labels     []string | ||||
| 	Series     map[string]*Series | ||||
| 	ReportId int64 | ||||
| 	UserId   int64 | ||||
| 	Name     string | ||||
| 	Lua      string | ||||
| } | ||||
|  | ||||
| func (r *Report) Write(w http.ResponseWriter) error { | ||||
| @@ -44,7 +43,90 @@ func (r *Report) Write(w http.ResponseWriter) error { | ||||
| 	return enc.Encode(r) | ||||
| } | ||||
|  | ||||
| func runReport(user *User, reportpath string) (*Report, error) { | ||||
| func (r *Report) Read(json_str string) error { | ||||
| 	dec := json.NewDecoder(strings.NewReader(json_str)) | ||||
| 	return dec.Decode(r) | ||||
| } | ||||
|  | ||||
| type ReportList struct { | ||||
| 	Reports *[]Report `json:"reports"` | ||||
| } | ||||
|  | ||||
| func (rl *ReportList) Write(w http.ResponseWriter) error { | ||||
| 	enc := json.NewEncoder(w) | ||||
| 	return enc.Encode(rl) | ||||
| } | ||||
|  | ||||
| type Series struct { | ||||
| 	Values []float64 | ||||
| 	Series map[string]*Series | ||||
| } | ||||
|  | ||||
| type Tabulation struct { | ||||
| 	ReportId int64 | ||||
| 	Title    string | ||||
| 	Subtitle string | ||||
| 	Units    string | ||||
| 	Labels   []string | ||||
| 	Series   map[string]*Series | ||||
| } | ||||
|  | ||||
| func (r *Tabulation) Write(w http.ResponseWriter) error { | ||||
| 	enc := json.NewEncoder(w) | ||||
| 	return enc.Encode(r) | ||||
| } | ||||
|  | ||||
| func GetReport(reportid int64, userid int64) (*Report, error) { | ||||
| 	var r Report | ||||
|  | ||||
| 	err := DB.SelectOne(&r, "SELECT * from reports where UserId=? AND ReportId=?", userid, reportid) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &r, nil | ||||
| } | ||||
|  | ||||
| func GetReports(userid int64) (*[]Report, error) { | ||||
| 	var reports []Report | ||||
|  | ||||
| 	_, err := DB.Select(&reports, "SELECT * from reports where UserId=?", userid) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &reports, nil | ||||
| } | ||||
|  | ||||
| func InsertReport(r *Report) error { | ||||
| 	err := DB.Insert(r) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func UpdateReport(r *Report) error { | ||||
| 	count, err := DB.Update(r) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if count != 1 { | ||||
| 		return errors.New("Updated more than one report") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func DeleteReport(r *Report) error { | ||||
| 	count, err := DB.Delete(r) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if count != 1 { | ||||
| 		return errors.New("Deleted more than one report") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func runReport(user *User, report *Report) (*Tabulation, error) { | ||||
| 	// Create a new LState without opening the default libs for security | ||||
| 	L := lua.NewState(lua.Options{SkipOpenLibs: true}) | ||||
| 	defer L.Close() | ||||
| @@ -78,9 +160,9 @@ func runReport(user *User, reportpath string) (*Report, error) { | ||||
| 	luaRegisterSecurities(L) | ||||
| 	luaRegisterBalances(L) | ||||
| 	luaRegisterDates(L) | ||||
| 	luaRegisterReports(L) | ||||
| 	luaRegisterTabulations(L) | ||||
|  | ||||
| 	err := L.DoFile(reportpath) | ||||
| 	err := L.DoString(report.Lua) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -96,13 +178,36 @@ func runReport(user *User, reportpath string) (*Report, error) { | ||||
|  | ||||
| 	value := L.Get(-1) | ||||
| 	if ud, ok := value.(*lua.LUserData); ok { | ||||
| 		if report, ok := ud.Value.(*Report); ok { | ||||
| 			return report, nil | ||||
| 		if tabulation, ok := ud.Value.(*Tabulation); ok { | ||||
| 			return tabulation, nil | ||||
| 		} else { | ||||
| 			return nil, errors.New("generate() in " + reportpath + " didn't return a report") | ||||
| 			return nil, fmt.Errorf("generate() for %s (Id: %d) didn't return a tabulation", report.Name, report.ReportId) | ||||
| 		} | ||||
| 	} else { | ||||
| 		return nil, errors.New("generate() in " + reportpath + " didn't return a report") | ||||
| 		return nil, fmt.Errorf("generate() for %s (Id: %d) didn't even return LUserData", report.Name, report.ReportId) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ReportTabulationHandler(w http.ResponseWriter, r *http.Request, user *User, reportid int64) { | ||||
| 	report, err := GetReport(reportid, user.UserId) | ||||
| 	if err != nil { | ||||
| 		WriteError(w, 3 /*Invalid Request*/) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	tabulation, err := runReport(user, report) | ||||
| 	if err != nil { | ||||
| 		// TODO handle different failure cases differently | ||||
| 		log.Print("runReport returned:", err) | ||||
| 		WriteError(w, 3 /*Invalid Request*/) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = tabulation.Write(w) | ||||
| 	if err != nil { | ||||
| 		WriteError(w, 999 /*Internal Error*/) | ||||
| 		log.Print(err) | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -113,34 +218,132 @@ func ReportHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if r.Method == "GET" { | ||||
| 		var reportname string | ||||
| 		n, err := GetURLPieces(r.URL.Path, "/report/%s", &reportname) | ||||
| 		if err != nil || n != 1 { | ||||
| 	if r.Method == "POST" { | ||||
| 		report_json := r.PostFormValue("report") | ||||
| 		if report_json == "" { | ||||
| 			WriteError(w, 3 /*Invalid Request*/) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		reportpath := path.Join(baseDir, "reports", reportname+".lua") | ||||
| 		report_stat, err := os.Stat(reportpath) | ||||
| 		if err != nil || !report_stat.Mode().IsRegular() { | ||||
| 		var report Report | ||||
| 		err := report.Read(report_json) | ||||
| 		if err != nil { | ||||
| 			WriteError(w, 3 /*Invalid Request*/) | ||||
| 			return | ||||
| 		} | ||||
| 		report.ReportId = -1 | ||||
| 		report.UserId = user.UserId | ||||
|  | ||||
| 		report, err := runReport(user, reportpath) | ||||
| 		err = InsertReport(&report) | ||||
| 		if err != nil { | ||||
| 			WriteError(w, 999 /*Internal Error*/) | ||||
| 			log.Print(err) | ||||
| 			return | ||||
| 		} | ||||
| 		report.ReportId = reportname | ||||
|  | ||||
| 		w.WriteHeader(201 /*Created*/) | ||||
| 		err = report.Write(w) | ||||
| 		if err != nil { | ||||
| 			WriteError(w, 999 /*Internal Error*/) | ||||
| 			log.Print(err) | ||||
| 			return | ||||
| 		} | ||||
| 	} else if r.Method == "GET" { | ||||
| 		if reportTabulationRE.MatchString(r.URL.Path) { | ||||
| 			var reportid int64 | ||||
| 			n, err := GetURLPieces(r.URL.Path, "/report/%d/tabulation", &reportid) | ||||
| 			if err != nil || n != 1 { | ||||
| 				WriteError(w, 999 /*InternalError*/) | ||||
| 				log.Print(err) | ||||
| 				return | ||||
| 			} | ||||
| 			ReportTabulationHandler(w, r, user, reportid) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		var reportid int64 | ||||
| 		n, err := GetURLPieces(r.URL.Path, "/report/%d", &reportid) | ||||
| 		if err != nil || n != 1 { | ||||
| 			//Return all Reports | ||||
| 			var rl ReportList | ||||
| 			reports, err := GetReports(user.UserId) | ||||
| 			if err != nil { | ||||
| 				WriteError(w, 999 /*Internal Error*/) | ||||
| 				log.Print(err) | ||||
| 				return | ||||
| 			} | ||||
| 			rl.Reports = reports | ||||
| 			err = (&rl).Write(w) | ||||
| 			if err != nil { | ||||
| 				WriteError(w, 999 /*Internal Error*/) | ||||
| 				log.Print(err) | ||||
| 				return | ||||
| 			} | ||||
| 		} else { | ||||
| 			// Return Report with this Id | ||||
| 			report, err := GetReport(reportid, user.UserId) | ||||
| 			if err != nil { | ||||
| 				WriteError(w, 3 /*Invalid Request*/) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			err = report.Write(w) | ||||
| 			if err != nil { | ||||
| 				WriteError(w, 999 /*Internal Error*/) | ||||
| 				log.Print(err) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		reportid, err := GetURLID(r.URL.Path) | ||||
| 		if err != nil { | ||||
| 			WriteError(w, 3 /*Invalid Request*/) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		if r.Method == "PUT" { | ||||
| 			report_json := r.PostFormValue("report") | ||||
| 			if report_json == "" { | ||||
| 				WriteError(w, 3 /*Invalid Request*/) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			var report Report | ||||
| 			err := report.Read(report_json) | ||||
| 			if err != nil || report.ReportId != reportid { | ||||
| 				WriteError(w, 3 /*Invalid Request*/) | ||||
| 				return | ||||
| 			} | ||||
| 			report.UserId = user.UserId | ||||
|  | ||||
| 			err = UpdateReport(&report) | ||||
| 			if err != nil { | ||||
| 				WriteError(w, 999 /*Internal Error*/) | ||||
| 				log.Print(err) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			err = report.Write(w) | ||||
| 			if err != nil { | ||||
| 				WriteError(w, 999 /*Internal Error*/) | ||||
| 				log.Print(err) | ||||
| 				return | ||||
| 			} | ||||
| 		} else if r.Method == "DELETE" { | ||||
| 			report, err := GetReport(reportid, user.UserId) | ||||
| 			if err != nil { | ||||
| 				WriteError(w, 3 /*Invalid Request*/) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			err = DeleteReport(report) | ||||
| 			if err != nil { | ||||
| 				WriteError(w, 999 /*Internal Error*/) | ||||
| 				log.Print(err) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			WriteSuccess(w) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| function account_series_map(accounts, report) | ||||
| function account_series_map(accounts, tabulation) | ||||
| 	map = {} | ||||
|  | ||||
| 	for i=1,100 do -- we're not messing with accounts more than 100 levels deep | ||||
| @@ -7,7 +7,7 @@ function account_series_map(accounts, report) | ||||
| 			if not map[id] then | ||||
| 				all_handled = false | ||||
| 				if not acct.parent then | ||||
| 					map[id] = report:series(acct.name) | ||||
| 					map[id] = tabulation:series(acct.name) | ||||
| 				elseif map[acct.parent.accountid] then | ||||
| 					map[id] = map[acct.parent.accountid]:series(acct.name) | ||||
| 				end | ||||
| @@ -26,7 +26,7 @@ function generate() | ||||
| 	account_type = account.Expense | ||||
|  | ||||
| 	accounts = get_accounts() | ||||
| 	r = report.new(12) | ||||
| 	r = tabulation.new(12) | ||||
| 	r:title(year .. " Monthly Expenses") | ||||
| 	series_map = account_series_map(accounts, r) | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| function account_series_map(accounts, report) | ||||
| function account_series_map(accounts, tabulation) | ||||
| 	map = {} | ||||
|  | ||||
| 	for i=1,100 do -- we're not messing with accounts more than 100 levels deep | ||||
| @@ -7,7 +7,7 @@ function account_series_map(accounts, report) | ||||
| 			if not map[id] then | ||||
| 				all_handled = false | ||||
| 				if not acct.parent then | ||||
| 					map[id] = report:series(acct.name) | ||||
| 					map[id] = tabulation:series(acct.name) | ||||
| 				elseif map[acct.parent.accountid] then | ||||
| 					map[id] = map[acct.parent.accountid]:series(acct.name) | ||||
| 				end | ||||
| @@ -26,7 +26,7 @@ function generate() | ||||
| 	account_type = account.Income | ||||
|  | ||||
| 	accounts = get_accounts() | ||||
| 	r = report.new(1) | ||||
| 	r = tabulation.new(1) | ||||
| 	r:title(year .. " Income") | ||||
| 	series_map = account_series_map(accounts, r) | ||||
|  | ||||
|   | ||||
							
								
								
									
										101
									
								
								reports_lua.go
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								reports_lua.go
									
									
									
									
									
								
							| @@ -4,14 +4,14 @@ import ( | ||||
| 	"github.com/yuin/gopher-lua" | ||||
| ) | ||||
|  | ||||
| const luaReportTypeName = "report" | ||||
| const luaTabulationTypeName = "tabulation" | ||||
| const luaSeriesTypeName = "series" | ||||
|  | ||||
| func luaRegisterReports(L *lua.LState) { | ||||
| 	mtr := L.NewTypeMetatable(luaReportTypeName) | ||||
| 	L.SetGlobal("report", mtr) | ||||
| 	L.SetField(mtr, "new", L.NewFunction(luaReportNew)) | ||||
| 	L.SetField(mtr, "__index", L.NewFunction(luaReport__index)) | ||||
| func luaRegisterTabulations(L *lua.LState) { | ||||
| 	mtr := L.NewTypeMetatable(luaTabulationTypeName) | ||||
| 	L.SetGlobal("tabulation", mtr) | ||||
| 	L.SetField(mtr, "new", L.NewFunction(luaTabulationNew)) | ||||
| 	L.SetField(mtr, "__index", L.NewFunction(luaTabulation__index)) | ||||
| 	L.SetField(mtr, "__metatable", lua.LString("protected")) | ||||
|  | ||||
| 	mts := L.NewTypeMetatable(luaSeriesTypeName) | ||||
| @@ -20,13 +20,13 @@ func luaRegisterReports(L *lua.LState) { | ||||
| 	L.SetField(mts, "__metatable", lua.LString("protected")) | ||||
| } | ||||
|  | ||||
| // Checks whether the first lua argument is a *LUserData with *Report and returns *Report | ||||
| func luaCheckReport(L *lua.LState, n int) *Report { | ||||
| // Checks whether the first lua argument is a *LUserData with *Tabulation and returns *Tabulation | ||||
| func luaCheckTabulation(L *lua.LState, n int) *Tabulation { | ||||
| 	ud := L.CheckUserData(n) | ||||
| 	if report, ok := ud.Value.(*Report); ok { | ||||
| 		return report | ||||
| 	if tabulation, ok := ud.Value.(*Tabulation); ok { | ||||
| 		return tabulation | ||||
| 	} | ||||
| 	L.ArgError(n, "report expected") | ||||
| 	L.ArgError(n, "tabulation expected") | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -40,114 +40,101 @@ func luaCheckSeries(L *lua.LState, n int) *Series { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func luaReportNew(L *lua.LState) int { | ||||
| func luaTabulationNew(L *lua.LState) int { | ||||
| 	numvalues := L.CheckInt(1) | ||||
| 	ud := L.NewUserData() | ||||
| 	ud.Value = &Report{ | ||||
| 	ud.Value = &Tabulation{ | ||||
| 		Labels: make([]string, numvalues), | ||||
| 		Series: make(map[string]*Series), | ||||
| 	} | ||||
| 	L.SetMetatable(ud, L.GetTypeMetatable(luaReportTypeName)) | ||||
| 	L.SetMetatable(ud, L.GetTypeMetatable(luaTabulationTypeName)) | ||||
| 	L.Push(ud) | ||||
| 	return 1 | ||||
| } | ||||
|  | ||||
| func luaReport__index(L *lua.LState) int { | ||||
| func luaTabulation__index(L *lua.LState) int { | ||||
| 	field := L.CheckString(2) | ||||
|  | ||||
| 	switch field { | ||||
| 	case "Label", "label": | ||||
| 		L.Push(L.NewFunction(luaReportLabel)) | ||||
| 		L.Push(L.NewFunction(luaTabulationLabel)) | ||||
| 	case "Series", "series": | ||||
| 		L.Push(L.NewFunction(luaReportSeries)) | ||||
| 		L.Push(L.NewFunction(luaTabulationSeries)) | ||||
| 	case "Title", "title": | ||||
| 		L.Push(L.NewFunction(luaReportTitle)) | ||||
| 		L.Push(L.NewFunction(luaTabulationTitle)) | ||||
| 	case "Subtitle", "subtitle": | ||||
| 		L.Push(L.NewFunction(luaReportSubtitle)) | ||||
| 	case "XAxisLabel", "xaxislabel": | ||||
| 		L.Push(L.NewFunction(luaReportXAxis)) | ||||
| 	case "YAxisLabel", "yaxislabel": | ||||
| 		L.Push(L.NewFunction(luaReportYAxis)) | ||||
| 		L.Push(L.NewFunction(luaTabulationSubtitle)) | ||||
| 	case "Units", "units": | ||||
| 		L.Push(L.NewFunction(luaTabulationUnits)) | ||||
| 	default: | ||||
| 		L.ArgError(2, "unexpected report attribute: "+field) | ||||
| 		L.ArgError(2, "unexpected tabulation attribute: "+field) | ||||
| 	} | ||||
|  | ||||
| 	return 1 | ||||
| } | ||||
|  | ||||
| func luaReportLabel(L *lua.LState) int { | ||||
| 	report := luaCheckReport(L, 1) | ||||
| func luaTabulationLabel(L *lua.LState) int { | ||||
| 	tabulation := luaCheckTabulation(L, 1) | ||||
| 	labelnumber := L.CheckInt(2) | ||||
| 	label := L.CheckString(3) | ||||
|  | ||||
| 	if labelnumber > cap(report.Labels) || labelnumber < 1 { | ||||
| 	if labelnumber > cap(tabulation.Labels) || labelnumber < 1 { | ||||
| 		L.ArgError(2, "Label index must be between 1 and the number of data points, inclusive") | ||||
| 	} | ||||
| 	report.Labels[labelnumber-1] = label | ||||
| 	tabulation.Labels[labelnumber-1] = label | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func luaReportSeries(L *lua.LState) int { | ||||
| 	report := luaCheckReport(L, 1) | ||||
| func luaTabulationSeries(L *lua.LState) int { | ||||
| 	tabulation := luaCheckTabulation(L, 1) | ||||
| 	name := L.CheckString(2) | ||||
| 	ud := L.NewUserData() | ||||
|  | ||||
| 	s, ok := report.Series[name] | ||||
| 	s, ok := tabulation.Series[name] | ||||
| 	if ok { | ||||
| 		ud.Value = s | ||||
| 	} else { | ||||
| 		report.Series[name] = &Series{ | ||||
| 		tabulation.Series[name] = &Series{ | ||||
| 			Series: make(map[string]*Series), | ||||
| 			Values: make([]float64, cap(report.Labels)), | ||||
| 			Values: make([]float64, cap(tabulation.Labels)), | ||||
| 		} | ||||
| 		ud.Value = report.Series[name] | ||||
| 		ud.Value = tabulation.Series[name] | ||||
| 	} | ||||
| 	L.SetMetatable(ud, L.GetTypeMetatable(luaSeriesTypeName)) | ||||
| 	L.Push(ud) | ||||
| 	return 1 | ||||
| } | ||||
|  | ||||
| func luaReportTitle(L *lua.LState) int { | ||||
| 	report := luaCheckReport(L, 1) | ||||
| func luaTabulationTitle(L *lua.LState) int { | ||||
| 	tabulation := luaCheckTabulation(L, 1) | ||||
|  | ||||
| 	if L.GetTop() == 2 { | ||||
| 		report.Title = L.CheckString(2) | ||||
| 		tabulation.Title = L.CheckString(2) | ||||
| 		return 0 | ||||
| 	} | ||||
| 	L.Push(lua.LString(report.Title)) | ||||
| 	L.Push(lua.LString(tabulation.Title)) | ||||
| 	return 1 | ||||
| } | ||||
|  | ||||
| func luaReportSubtitle(L *lua.LState) int { | ||||
| 	report := luaCheckReport(L, 1) | ||||
| func luaTabulationSubtitle(L *lua.LState) int { | ||||
| 	tabulation := luaCheckTabulation(L, 1) | ||||
|  | ||||
| 	if L.GetTop() == 2 { | ||||
| 		report.Subtitle = L.CheckString(2) | ||||
| 		tabulation.Subtitle = L.CheckString(2) | ||||
| 		return 0 | ||||
| 	} | ||||
| 	L.Push(lua.LString(report.Subtitle)) | ||||
| 	L.Push(lua.LString(tabulation.Subtitle)) | ||||
| 	return 1 | ||||
| } | ||||
|  | ||||
| func luaReportXAxis(L *lua.LState) int { | ||||
| 	report := luaCheckReport(L, 1) | ||||
| func luaTabulationUnits(L *lua.LState) int { | ||||
| 	tabulation := luaCheckTabulation(L, 1) | ||||
|  | ||||
| 	if L.GetTop() == 2 { | ||||
| 		report.XAxisLabel = L.CheckString(2) | ||||
| 		tabulation.Units = L.CheckString(2) | ||||
| 		return 0 | ||||
| 	} | ||||
| 	L.Push(lua.LString(report.XAxisLabel)) | ||||
| 	return 1 | ||||
| } | ||||
|  | ||||
| func luaReportYAxis(L *lua.LState) int { | ||||
| 	report := luaCheckReport(L, 1) | ||||
|  | ||||
| 	if L.GetTop() == 2 { | ||||
| 		report.YAxisLabel = L.CheckString(2) | ||||
| 		return 0 | ||||
| 	} | ||||
| 	L.Push(lua.LString(report.YAxisLabel)) | ||||
| 	L.Push(lua.LString(tabulation.Units)) | ||||
| 	return 1 | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user