mirror of
				https://github.com/aclindsa/moneygo.git
				synced 2025-10-31 01:43: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(Security{}, "securities").SetKeys(true, "SecurityId") | ||||||
| 	dbmap.AddTableWithName(Transaction{}, "transactions").SetKeys(true, "TransactionId") | 	dbmap.AddTableWithName(Transaction{}, "transactions").SetKeys(true, "TransactionId") | ||||||
| 	dbmap.AddTableWithName(Split{}, "splits").SetKeys(true, "SplitId") | 	dbmap.AddTableWithName(Split{}, "splits").SetKeys(true, "SplitId") | ||||||
|  | 	dbmap.AddTableWithName(Report{}, "reports").SetKeys(true, "ReportId") | ||||||
|  |  | ||||||
| 	err = dbmap.CreateTablesIfNotExists() | 	err = dbmap.CreateTablesIfNotExists() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
| @@ -4,55 +4,116 @@ var ErrorActions = require('./ErrorActions'); | |||||||
|  |  | ||||||
| var models = require('../models.js'); | var models = require('../models.js'); | ||||||
| var Report = models.Report; | var Report = models.Report; | ||||||
|  | var Tabulation = models.Tabulation; | ||||||
| var Error = models.Error; | var Error = models.Error; | ||||||
|  |  | ||||||
| function fetchReport(reportName) { | function fetchReports() { | ||||||
| 	return { | 	return { | ||||||
| 		type: ReportConstants.FETCH_REPORT, | 		type: ReportConstants.FETCH_REPORTS | ||||||
| 		reportName: reportName |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| function reportFetched(report) { | function reportsFetched(reports) { | ||||||
| 	return { | 	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 | 		report: report | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| function selectReport(report, seriesTraversal) { | function updateReport() { | ||||||
| 	return { | 	return { | ||||||
| 		type: ReportConstants.SELECT_REPORT, | 		type: ReportConstants.UPDATE_REPORT | ||||||
| 		report: report, |  | ||||||
| 		seriesTraversal: seriesTraversal |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| 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 { | 	return { | ||||||
| 		type: ReportConstants.REPORT_SELECTED, | 		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 | 		seriesTraversal: seriesTraversal | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| function fetch(report) { | function fetchAll() { | ||||||
| 	return function (dispatch) { | 	return function (dispatch) { | ||||||
| 		dispatch(fetchReport(report)); | 		dispatch(fetchReports()); | ||||||
|  |  | ||||||
| 		$.ajax({ | 		$.ajax({ | ||||||
| 			type: "GET", | 			type: "GET", | ||||||
| 			dataType: "json", | 			dataType: "json", | ||||||
| 			url: "report/"+report+"/", | 			url: "report/", | ||||||
| 			success: function(data, status, jqXHR) { | 			success: function(data, status, jqXHR) { | ||||||
| 				var e = new Error(); | 				var e = new Error(); | ||||||
| 				e.fromJSON(data); | 				e.fromJSON(data); | ||||||
| 				if (e.isError()) { | 				if (e.isError()) { | ||||||
| 					dispatch(ErrorActions.serverError(e)); | 					dispatch(ErrorActions.serverError(e)); | ||||||
| 				} else { | 				} else { | ||||||
| 					var r = new Report(); | 					dispatch(reportsFetched(data.reports.map(function(json) { | ||||||
| 					r.fromJSON(data); | 						var r = new Report(); | ||||||
| 					dispatch(reportFetched(r)); | 						r.fromJSON(json); | ||||||
|  | 						return r; | ||||||
|  | 					}))); | ||||||
| 				} | 				} | ||||||
| 			}, | 			}, | ||||||
| 			error: function(jqXHR, status, error) { | 			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) { | 	return function (dispatch) { | ||||||
| 		if (!seriesTraversal) | 		if (!seriesTraversal) | ||||||
| 			seriesTraversal = []; | 			seriesTraversal = []; | ||||||
| 		dispatch(selectReport(report, seriesTraversal)); |  | ||||||
|  |  | ||||||
| 		// Descend the tree to the right series to flatten | 		// Descend the tree to the right series to flatten | ||||||
| 		var series = report; | 		var series = tabulation; | ||||||
| 		for (var i=0; i < seriesTraversal.length; i++) { | 		for (var i=0; i < seriesTraversal.length; i++) { | ||||||
| 			if (!series.Series.hasOwnProperty(seriesTraversal[i])) { | 			if (!series.Series.hasOwnProperty(seriesTraversal[i])) { | ||||||
| 				dispatch(ErrorActions.clientError("Invalid series")); | 				dispatch(ErrorActions.clientError("Invalid series")); | ||||||
| @@ -87,23 +251,27 @@ function select(report, seriesTraversal) { | |||||||
|  |  | ||||||
| 		// Add back in any values from the current level | 		// Add back in any values from the current level | ||||||
| 		if (series.hasOwnProperty('Values')) | 		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; | 		flattenedTabulation.ReportId = tabulation.ReportId; | ||||||
| 		flattenedReport.Title = report.Title; | 		flattenedTabulation.Title = tabulation.Title; | ||||||
| 		flattenedReport.Subtitle = report.Subtitle; | 		flattenedTabulation.Subtitle = tabulation.Subtitle; | ||||||
| 		flattenedReport.XAxisLabel = report.XAxisLabel; | 		flattenedTabulation.Units = tabulation.Units; | ||||||
| 		flattenedReport.YAxisLabel = report.YAxisLabel; | 		flattenedTabulation.Labels = tabulation.Labels.slice(); | ||||||
| 		flattenedReport.Labels = report.Labels.slice(); | 		flattenedTabulation.FlattenedSeries = flattenedSeries; | ||||||
| 		flattenedReport.FlattenedSeries = flattenedSeries; |  | ||||||
|  |  | ||||||
| 		dispatch(reportSelected(flattenedReport, seriesTraversal)); | 		dispatch(seriesSelected(flattenedTabulation, seriesTraversal)); | ||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
| 	fetch: fetch, | 	fetchAll: fetchAll, | ||||||
| 	select: select | 	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 Panel = ReactBootstrap.Panel; | ||||||
|  |  | ||||||
| var StackedBarChart = require('../components/StackedBarChart'); | var StackedBarChart = require('../components/StackedBarChart'); | ||||||
|  | var PieChart = require('../components/PieChart'); | ||||||
|  |  | ||||||
| var models = require('../models') | var models = require('../models') | ||||||
| var Report = models.Report; | var Report = models.Report; | ||||||
|  | var Tabulation = models.Tabulation; | ||||||
|  |  | ||||||
| class ReportsTab extends React.Component { | class ReportsTab extends React.Component { | ||||||
| 	constructor() { | 	constructor() { | ||||||
| 		super(); | 		super(); | ||||||
|  | 		this.state = { | ||||||
|  | 			initialized: false | ||||||
|  | 		} | ||||||
| 		this.onSelectSeries = this.handleSelectSeries.bind(this); | 		this.onSelectSeries = this.handleSelectSeries.bind(this); | ||||||
| 	} | 	} | ||||||
| 	componentWillMount() { | 	componentWillMount() { | ||||||
| 		this.props.onFetchReport("monthly_expenses"); | 		this.props.onFetchAllReports(); | ||||||
| 	} | 	} | ||||||
| 	componentWillReceiveProps(nextProps) { | 	componentWillReceiveProps(nextProps) { | ||||||
| 		if (nextProps.reports['monthly_expenses'] && !nextProps.selectedReport.report) { | 		var selected = nextProps.reports.selected; | ||||||
| 			this.props.onSelectReport(nextProps.reports['monthly_expenses'], []); | 		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) { | 	handleSelectSeries(series) { | ||||||
| 		if (series == Report.topLevelAccountName()) | 		if (series == Tabulation.topLevelSeriesName()) | ||||||
| 			return; | 			return; | ||||||
| 		var seriesTraversal = this.props.selectedReport.seriesTraversal.slice(); | 		var seriesTraversal = this.props.selectedTabulation.seriesTraversal.slice(); | ||||||
| 		seriesTraversal.push(series); | 		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() { | 	render() { | ||||||
| 		var report = []; | 		var selectedTabulation = this.props.reports.selectedTabulation; | ||||||
| 		if (this.props.selectedReport.report) { | 		if (!selectedTabulation) { | ||||||
| 			var titleTracks = []; | 			return ( | ||||||
| 			var seriesTraversal = []; | 				<div></div> | ||||||
|  |  | ||||||
| 			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 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 ( | 		return ( | ||||||
| 			<div> | 			<Panel header={titleTracks}> | ||||||
| 				{report} | 				<PieChart | ||||||
| 			</div> | 					report={this.props.selectedTabulation.tabulation} | ||||||
|  | 					onSelectSeries={this.onSelectSeries} | ||||||
|  | 					seriesTraversal={this.props.selectedTabulation.seriesTraversal} /> | ||||||
|  | 			</Panel> | ||||||
| 		); | 		); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,8 +1,17 @@ | |||||||
| var keyMirror = require('keymirror'); | var keyMirror = require('keymirror'); | ||||||
|  |  | ||||||
| module.exports = keyMirror({ | module.exports = keyMirror({ | ||||||
| 	FETCH_REPORT: null, | 	FETCH_REPORTS: null, | ||||||
| 	REPORT_FETCHED: null, | 	REPORTS_FETCHED: null, | ||||||
| 	SELECT_REPORT: null, | 	CREATE_REPORT: null, | ||||||
| 	REPORT_SELECTED: 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) { | function mapStateToProps(state) { | ||||||
| 	return { | 	return { | ||||||
| 		reports: state.reports, | 		reports: state.reports | ||||||
| 		selectedReport: state.selectedReport |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| function mapDispatchToProps(dispatch) { | function mapDispatchToProps(dispatch) { | ||||||
| 	return { | 	return { | ||||||
| 		onFetchReport: function(reportname) {dispatch(ReportActions.fetch(reportname))}, | 		onFetchAllReports: function() {dispatch(ReportActions.fetchAll())}, | ||||||
| 		onSelectReport: function(report, seriesTraversal) {dispatch(ReportActions.select(report, seriesTraversal))} | 		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() { | 	constructor() { | ||||||
| 		this.ReportId = ""; | 		this.ReportId = ""; | ||||||
| 		this.Title = ""; | 		this.Title = ""; | ||||||
| 		this.Subtitle = ""; | 		this.Subtitle = ""; | ||||||
| 		this.XAxisLabel = ""; | 		this.Units = ""; | ||||||
| 		this.YAxisLabel = ""; |  | ||||||
| 		this.Labels = []; | 		this.Labels = []; | ||||||
| 		this.Series = {}; | 		this.Series = {}; | ||||||
| 		this.FlattenedSeries = {}; | 		this.FlattenedSeries = {}; | ||||||
| 	} | 	} | ||||||
| 	static topLevelAccountName() { | 	static topLevelSeriesName() { | ||||||
| 		return "(top level)" | 		return "(top level)" | ||||||
| 	} | 	} | ||||||
| 	toJSON() { | 	toJSON() { | ||||||
| @@ -513,8 +512,7 @@ class Report { | |||||||
| 		json_obj.ReportId = this.ReportId; | 		json_obj.ReportId = this.ReportId; | ||||||
| 		json_obj.Title = this.Title; | 		json_obj.Title = this.Title; | ||||||
| 		json_obj.Subtitle = this.Subtitle; | 		json_obj.Subtitle = this.Subtitle; | ||||||
| 		json_obj.XAxisLabel = this.XAxisLabel; | 		json_obj.Units = this.Units; | ||||||
| 		json_obj.YAxisLabel = this.YAxisLabel; |  | ||||||
| 		json_obj.Labels = this.Labels; | 		json_obj.Labels = this.Labels; | ||||||
| 		json_obj.Series = {}; | 		json_obj.Series = {}; | ||||||
| 		for (var series in this.Series) { | 		for (var series in this.Series) { | ||||||
| @@ -532,10 +530,8 @@ class Report { | |||||||
| 			this.Title = json_obj.Title; | 			this.Title = json_obj.Title; | ||||||
| 		if (json_obj.hasOwnProperty("Subtitle")) | 		if (json_obj.hasOwnProperty("Subtitle")) | ||||||
| 			this.Subtitle = json_obj.Subtitle; | 			this.Subtitle = json_obj.Subtitle; | ||||||
| 		if (json_obj.hasOwnProperty("XAxisLabel")) | 		if (json_obj.hasOwnProperty("Units")) | ||||||
| 			this.XAxisLabel = json_obj.XAxisLabel; | 			this.Units = json_obj.Units; | ||||||
| 		if (json_obj.hasOwnProperty("YAxisLabel")) |  | ||||||
| 			this.YAxisLabel = json_obj.YAxisLabel; |  | ||||||
| 		if (json_obj.hasOwnProperty("Labels")) | 		if (json_obj.hasOwnProperty("Labels")) | ||||||
| 			this.Labels = json_obj.Labels; | 			this.Labels = json_obj.Labels; | ||||||
| 		if (json_obj.hasOwnProperty("Series")) { | 		if (json_obj.hasOwnProperty("Series")) { | ||||||
| @@ -582,7 +578,7 @@ module.exports = { | |||||||
| 	Account: Account, | 	Account: Account, | ||||||
| 	Split: Split, | 	Split: Split, | ||||||
| 	Transaction: Transaction, | 	Transaction: Transaction, | ||||||
| 	Report: Report, | 	Tabulation: Tabulation, | ||||||
| 	OFXDownload: OFXDownload, | 	OFXDownload: OFXDownload, | ||||||
| 	Error: Error, | 	Error: Error, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,7 +8,6 @@ var SecurityTemplateReducer = require('./SecurityTemplateReducer'); | |||||||
| var SelectedAccountReducer = require('./SelectedAccountReducer'); | var SelectedAccountReducer = require('./SelectedAccountReducer'); | ||||||
| var SelectedSecurityReducer = require('./SelectedSecurityReducer'); | var SelectedSecurityReducer = require('./SelectedSecurityReducer'); | ||||||
| var ReportReducer = require('./ReportReducer'); | var ReportReducer = require('./ReportReducer'); | ||||||
| var SelectedReportReducer = require('./SelectedReportReducer'); |  | ||||||
| var TransactionReducer = require('./TransactionReducer'); | var TransactionReducer = require('./TransactionReducer'); | ||||||
| var TransactionPageReducer = require('./TransactionPageReducer'); | var TransactionPageReducer = require('./TransactionPageReducer'); | ||||||
| var ImportReducer = require('./ImportReducer'); | var ImportReducer = require('./ImportReducer'); | ||||||
| @@ -23,7 +22,6 @@ module.exports = Redux.combineReducers({ | |||||||
| 	selectedAccount: SelectedAccountReducer, | 	selectedAccount: SelectedAccountReducer, | ||||||
| 	selectedSecurity: SelectedSecurityReducer, | 	selectedSecurity: SelectedSecurityReducer, | ||||||
| 	reports: ReportReducer, | 	reports: ReportReducer, | ||||||
| 	selectedReport: SelectedReportReducer, |  | ||||||
| 	transactions: TransactionReducer, | 	transactions: TransactionReducer, | ||||||
| 	transactionPage: TransactionPageReducer, | 	transactionPage: TransactionPageReducer, | ||||||
| 	imports: ImportReducer, | 	imports: ImportReducer, | ||||||
|   | |||||||
| @@ -3,15 +3,78 @@ var assign = require('object-assign'); | |||||||
| var ReportConstants = require('../constants/ReportConstants'); | var ReportConstants = require('../constants/ReportConstants'); | ||||||
| var UserConstants = require('../constants/UserConstants'); | 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) { | 	switch (action.type) { | ||||||
| 		case ReportConstants.REPORT_FETCHED: | 		case ReportConstants.REPORTS_FETCHED: | ||||||
| 			var report = action.report; | 			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, { | 			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 | 				[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: | 		case UserConstants.USER_LOGGEDOUT: | ||||||
| 			return {}; | 			return initialState; | ||||||
| 		default: | 		default: | ||||||
| 			return state; | 			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" | 	"context" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"fmt" | ||||||
| 	"github.com/yuin/gopher-lua" | 	"github.com/yuin/gopher-lua" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"os" | 	"regexp" | ||||||
| 	"path" | 	"strings" | ||||||
| 	"time" | 	"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 and value to store user in lua's Context | ||||||
| type key int | type key int | ||||||
|  |  | ||||||
| @@ -24,19 +31,11 @@ const ( | |||||||
|  |  | ||||||
| const luaTimeoutSeconds time.Duration = 30 // maximum time a lua request can run for | 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 { | type Report struct { | ||||||
| 	ReportId   string | 	ReportId int64 | ||||||
| 	Title      string | 	UserId   int64 | ||||||
| 	Subtitle   string | 	Name     string | ||||||
| 	XAxisLabel string | 	Lua      string | ||||||
| 	YAxisLabel string |  | ||||||
| 	Labels     []string |  | ||||||
| 	Series     map[string]*Series |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (r *Report) Write(w http.ResponseWriter) error { | func (r *Report) Write(w http.ResponseWriter) error { | ||||||
| @@ -44,7 +43,90 @@ func (r *Report) Write(w http.ResponseWriter) error { | |||||||
| 	return enc.Encode(r) | 	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 | 	// Create a new LState without opening the default libs for security | ||||||
| 	L := lua.NewState(lua.Options{SkipOpenLibs: true}) | 	L := lua.NewState(lua.Options{SkipOpenLibs: true}) | ||||||
| 	defer L.Close() | 	defer L.Close() | ||||||
| @@ -78,9 +160,9 @@ func runReport(user *User, reportpath string) (*Report, error) { | |||||||
| 	luaRegisterSecurities(L) | 	luaRegisterSecurities(L) | ||||||
| 	luaRegisterBalances(L) | 	luaRegisterBalances(L) | ||||||
| 	luaRegisterDates(L) | 	luaRegisterDates(L) | ||||||
| 	luaRegisterReports(L) | 	luaRegisterTabulations(L) | ||||||
|  |  | ||||||
| 	err := L.DoFile(reportpath) | 	err := L.DoString(report.Lua) | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -96,13 +178,36 @@ func runReport(user *User, reportpath string) (*Report, error) { | |||||||
|  |  | ||||||
| 	value := L.Get(-1) | 	value := L.Get(-1) | ||||||
| 	if ud, ok := value.(*lua.LUserData); ok { | 	if ud, ok := value.(*lua.LUserData); ok { | ||||||
| 		if report, ok := ud.Value.(*Report); ok { | 		if tabulation, ok := ud.Value.(*Tabulation); ok { | ||||||
| 			return report, nil | 			return tabulation, nil | ||||||
| 		} else { | 		} 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 { | 	} 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 | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if r.Method == "GET" { | 	if r.Method == "POST" { | ||||||
| 		var reportname string | 		report_json := r.PostFormValue("report") | ||||||
| 		n, err := GetURLPieces(r.URL.Path, "/report/%s", &reportname) | 		if report_json == "" { | ||||||
| 		if err != nil || n != 1 { |  | ||||||
| 			WriteError(w, 3 /*Invalid Request*/) | 			WriteError(w, 3 /*Invalid Request*/) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		reportpath := path.Join(baseDir, "reports", reportname+".lua") | 		var report Report | ||||||
| 		report_stat, err := os.Stat(reportpath) | 		err := report.Read(report_json) | ||||||
| 		if err != nil || !report_stat.Mode().IsRegular() { | 		if err != nil { | ||||||
| 			WriteError(w, 3 /*Invalid Request*/) | 			WriteError(w, 3 /*Invalid Request*/) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  | 		report.ReportId = -1 | ||||||
|  | 		report.UserId = user.UserId | ||||||
|  |  | ||||||
| 		report, err := runReport(user, reportpath) | 		err = InsertReport(&report) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			WriteError(w, 999 /*Internal Error*/) | 			WriteError(w, 999 /*Internal Error*/) | ||||||
| 			log.Print(err) | 			log.Print(err) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		report.ReportId = reportname |  | ||||||
|  |  | ||||||
|  | 		w.WriteHeader(201 /*Created*/) | ||||||
| 		err = report.Write(w) | 		err = report.Write(w) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			WriteError(w, 999 /*Internal Error*/) | 			WriteError(w, 999 /*Internal Error*/) | ||||||
| 			log.Print(err) | 			log.Print(err) | ||||||
| 			return | 			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 = {} | 	map = {} | ||||||
|  |  | ||||||
| 	for i=1,100 do -- we're not messing with accounts more than 100 levels deep | 	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 | 			if not map[id] then | ||||||
| 				all_handled = false | 				all_handled = false | ||||||
| 				if not acct.parent then | 				if not acct.parent then | ||||||
| 					map[id] = report:series(acct.name) | 					map[id] = tabulation:series(acct.name) | ||||||
| 				elseif map[acct.parent.accountid] then | 				elseif map[acct.parent.accountid] then | ||||||
| 					map[id] = map[acct.parent.accountid]:series(acct.name) | 					map[id] = map[acct.parent.accountid]:series(acct.name) | ||||||
| 				end | 				end | ||||||
| @@ -26,7 +26,7 @@ function generate() | |||||||
| 	account_type = account.Expense | 	account_type = account.Expense | ||||||
|  |  | ||||||
| 	accounts = get_accounts() | 	accounts = get_accounts() | ||||||
| 	r = report.new(12) | 	r = tabulation.new(12) | ||||||
| 	r:title(year .. " Monthly Expenses") | 	r:title(year .. " Monthly Expenses") | ||||||
| 	series_map = account_series_map(accounts, r) | 	series_map = account_series_map(accounts, r) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| function account_series_map(accounts, report) | function account_series_map(accounts, tabulation) | ||||||
| 	map = {} | 	map = {} | ||||||
|  |  | ||||||
| 	for i=1,100 do -- we're not messing with accounts more than 100 levels deep | 	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 | 			if not map[id] then | ||||||
| 				all_handled = false | 				all_handled = false | ||||||
| 				if not acct.parent then | 				if not acct.parent then | ||||||
| 					map[id] = report:series(acct.name) | 					map[id] = tabulation:series(acct.name) | ||||||
| 				elseif map[acct.parent.accountid] then | 				elseif map[acct.parent.accountid] then | ||||||
| 					map[id] = map[acct.parent.accountid]:series(acct.name) | 					map[id] = map[acct.parent.accountid]:series(acct.name) | ||||||
| 				end | 				end | ||||||
| @@ -26,7 +26,7 @@ function generate() | |||||||
| 	account_type = account.Income | 	account_type = account.Income | ||||||
|  |  | ||||||
| 	accounts = get_accounts() | 	accounts = get_accounts() | ||||||
| 	r = report.new(1) | 	r = tabulation.new(1) | ||||||
| 	r:title(year .. " Income") | 	r:title(year .. " Income") | ||||||
| 	series_map = account_series_map(accounts, r) | 	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" | 	"github.com/yuin/gopher-lua" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const luaReportTypeName = "report" | const luaTabulationTypeName = "tabulation" | ||||||
| const luaSeriesTypeName = "series" | const luaSeriesTypeName = "series" | ||||||
|  |  | ||||||
| func luaRegisterReports(L *lua.LState) { | func luaRegisterTabulations(L *lua.LState) { | ||||||
| 	mtr := L.NewTypeMetatable(luaReportTypeName) | 	mtr := L.NewTypeMetatable(luaTabulationTypeName) | ||||||
| 	L.SetGlobal("report", mtr) | 	L.SetGlobal("tabulation", mtr) | ||||||
| 	L.SetField(mtr, "new", L.NewFunction(luaReportNew)) | 	L.SetField(mtr, "new", L.NewFunction(luaTabulationNew)) | ||||||
| 	L.SetField(mtr, "__index", L.NewFunction(luaReport__index)) | 	L.SetField(mtr, "__index", L.NewFunction(luaTabulation__index)) | ||||||
| 	L.SetField(mtr, "__metatable", lua.LString("protected")) | 	L.SetField(mtr, "__metatable", lua.LString("protected")) | ||||||
|  |  | ||||||
| 	mts := L.NewTypeMetatable(luaSeriesTypeName) | 	mts := L.NewTypeMetatable(luaSeriesTypeName) | ||||||
| @@ -20,13 +20,13 @@ func luaRegisterReports(L *lua.LState) { | |||||||
| 	L.SetField(mts, "__metatable", lua.LString("protected")) | 	L.SetField(mts, "__metatable", lua.LString("protected")) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Checks whether the first lua argument is a *LUserData with *Report and returns *Report | // Checks whether the first lua argument is a *LUserData with *Tabulation and returns *Tabulation | ||||||
| func luaCheckReport(L *lua.LState, n int) *Report { | func luaCheckTabulation(L *lua.LState, n int) *Tabulation { | ||||||
| 	ud := L.CheckUserData(n) | 	ud := L.CheckUserData(n) | ||||||
| 	if report, ok := ud.Value.(*Report); ok { | 	if tabulation, ok := ud.Value.(*Tabulation); ok { | ||||||
| 		return report | 		return tabulation | ||||||
| 	} | 	} | ||||||
| 	L.ArgError(n, "report expected") | 	L.ArgError(n, "tabulation expected") | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -40,114 +40,101 @@ func luaCheckSeries(L *lua.LState, n int) *Series { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func luaReportNew(L *lua.LState) int { | func luaTabulationNew(L *lua.LState) int { | ||||||
| 	numvalues := L.CheckInt(1) | 	numvalues := L.CheckInt(1) | ||||||
| 	ud := L.NewUserData() | 	ud := L.NewUserData() | ||||||
| 	ud.Value = &Report{ | 	ud.Value = &Tabulation{ | ||||||
| 		Labels: make([]string, numvalues), | 		Labels: make([]string, numvalues), | ||||||
| 		Series: make(map[string]*Series), | 		Series: make(map[string]*Series), | ||||||
| 	} | 	} | ||||||
| 	L.SetMetatable(ud, L.GetTypeMetatable(luaReportTypeName)) | 	L.SetMetatable(ud, L.GetTypeMetatable(luaTabulationTypeName)) | ||||||
| 	L.Push(ud) | 	L.Push(ud) | ||||||
| 	return 1 | 	return 1 | ||||||
| } | } | ||||||
|  |  | ||||||
| func luaReport__index(L *lua.LState) int { | func luaTabulation__index(L *lua.LState) int { | ||||||
| 	field := L.CheckString(2) | 	field := L.CheckString(2) | ||||||
|  |  | ||||||
| 	switch field { | 	switch field { | ||||||
| 	case "Label", "label": | 	case "Label", "label": | ||||||
| 		L.Push(L.NewFunction(luaReportLabel)) | 		L.Push(L.NewFunction(luaTabulationLabel)) | ||||||
| 	case "Series", "series": | 	case "Series", "series": | ||||||
| 		L.Push(L.NewFunction(luaReportSeries)) | 		L.Push(L.NewFunction(luaTabulationSeries)) | ||||||
| 	case "Title", "title": | 	case "Title", "title": | ||||||
| 		L.Push(L.NewFunction(luaReportTitle)) | 		L.Push(L.NewFunction(luaTabulationTitle)) | ||||||
| 	case "Subtitle", "subtitle": | 	case "Subtitle", "subtitle": | ||||||
| 		L.Push(L.NewFunction(luaReportSubtitle)) | 		L.Push(L.NewFunction(luaTabulationSubtitle)) | ||||||
| 	case "XAxisLabel", "xaxislabel": | 	case "Units", "units": | ||||||
| 		L.Push(L.NewFunction(luaReportXAxis)) | 		L.Push(L.NewFunction(luaTabulationUnits)) | ||||||
| 	case "YAxisLabel", "yaxislabel": |  | ||||||
| 		L.Push(L.NewFunction(luaReportYAxis)) |  | ||||||
| 	default: | 	default: | ||||||
| 		L.ArgError(2, "unexpected report attribute: "+field) | 		L.ArgError(2, "unexpected tabulation attribute: "+field) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return 1 | 	return 1 | ||||||
| } | } | ||||||
|  |  | ||||||
| func luaReportLabel(L *lua.LState) int { | func luaTabulationLabel(L *lua.LState) int { | ||||||
| 	report := luaCheckReport(L, 1) | 	tabulation := luaCheckTabulation(L, 1) | ||||||
| 	labelnumber := L.CheckInt(2) | 	labelnumber := L.CheckInt(2) | ||||||
| 	label := L.CheckString(3) | 	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") | 		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 | 	return 0 | ||||||
| } | } | ||||||
|  |  | ||||||
| func luaReportSeries(L *lua.LState) int { | func luaTabulationSeries(L *lua.LState) int { | ||||||
| 	report := luaCheckReport(L, 1) | 	tabulation := luaCheckTabulation(L, 1) | ||||||
| 	name := L.CheckString(2) | 	name := L.CheckString(2) | ||||||
| 	ud := L.NewUserData() | 	ud := L.NewUserData() | ||||||
|  |  | ||||||
| 	s, ok := report.Series[name] | 	s, ok := tabulation.Series[name] | ||||||
| 	if ok { | 	if ok { | ||||||
| 		ud.Value = s | 		ud.Value = s | ||||||
| 	} else { | 	} else { | ||||||
| 		report.Series[name] = &Series{ | 		tabulation.Series[name] = &Series{ | ||||||
| 			Series: make(map[string]*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.SetMetatable(ud, L.GetTypeMetatable(luaSeriesTypeName)) | ||||||
| 	L.Push(ud) | 	L.Push(ud) | ||||||
| 	return 1 | 	return 1 | ||||||
| } | } | ||||||
|  |  | ||||||
| func luaReportTitle(L *lua.LState) int { | func luaTabulationTitle(L *lua.LState) int { | ||||||
| 	report := luaCheckReport(L, 1) | 	tabulation := luaCheckTabulation(L, 1) | ||||||
|  |  | ||||||
| 	if L.GetTop() == 2 { | 	if L.GetTop() == 2 { | ||||||
| 		report.Title = L.CheckString(2) | 		tabulation.Title = L.CheckString(2) | ||||||
| 		return 0 | 		return 0 | ||||||
| 	} | 	} | ||||||
| 	L.Push(lua.LString(report.Title)) | 	L.Push(lua.LString(tabulation.Title)) | ||||||
| 	return 1 | 	return 1 | ||||||
| } | } | ||||||
|  |  | ||||||
| func luaReportSubtitle(L *lua.LState) int { | func luaTabulationSubtitle(L *lua.LState) int { | ||||||
| 	report := luaCheckReport(L, 1) | 	tabulation := luaCheckTabulation(L, 1) | ||||||
|  |  | ||||||
| 	if L.GetTop() == 2 { | 	if L.GetTop() == 2 { | ||||||
| 		report.Subtitle = L.CheckString(2) | 		tabulation.Subtitle = L.CheckString(2) | ||||||
| 		return 0 | 		return 0 | ||||||
| 	} | 	} | ||||||
| 	L.Push(lua.LString(report.Subtitle)) | 	L.Push(lua.LString(tabulation.Subtitle)) | ||||||
| 	return 1 | 	return 1 | ||||||
| } | } | ||||||
|  |  | ||||||
| func luaReportXAxis(L *lua.LState) int { | func luaTabulationUnits(L *lua.LState) int { | ||||||
| 	report := luaCheckReport(L, 1) | 	tabulation := luaCheckTabulation(L, 1) | ||||||
|  |  | ||||||
| 	if L.GetTop() == 2 { | 	if L.GetTop() == 2 { | ||||||
| 		report.XAxisLabel = L.CheckString(2) | 		tabulation.Units = L.CheckString(2) | ||||||
| 		return 0 | 		return 0 | ||||||
| 	} | 	} | ||||||
| 	L.Push(lua.LString(report.XAxisLabel)) | 	L.Push(lua.LString(tabulation.Units)) | ||||||
| 	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)) |  | ||||||
| 	return 1 | 	return 1 | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user