summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKjetil Orbekk <kjetil.orbekk@gmail.com>2020-02-07 22:12:31 -0500
committerKjetil Orbekk <kjetil.orbekk@gmail.com>2020-02-07 22:12:31 -0500
commit16c0e847452629c2af9fa1dac7a9d83ae3846c62 (patch)
tree75f1896b5de502143caa9d88f67b3996a8b364fb
parent06dbcc08dd75cdcb790e3f41180114cbcddb8bc8 (diff)
Properly display running entries as a table
-rw-r--r--src/db.rs14
-rw-r--r--src/server.rs13
-rw-r--r--src/template.rs99
-rw-r--r--templates/profile.hbs22
4 files changed, 98 insertions, 50 deletions
diff --git a/src/db.rs b/src/db.rs
index 198ce03..5b165c9 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -241,16 +241,24 @@ pub fn get_raw_data_keys(conn: &PgConnection) -> Result<Vec<models::RawDataKey>,
Ok(rows)
}
-pub fn get_entries(conn: &PgConnection, username: &str) -> Result<Vec<models::Entry>, Error> {
+pub fn get_entries(
+ conn: &PgConnection,
+ username: &str,
+ entry_type: &str,
+) -> Result<Vec<models::Entry>, Error> {
use crate::schema::entries;
let r = entries::table
- .filter(entries::username.eq(username))
+ .filter(
+ entries::username
+ .eq(username)
+ .and(entries::entry_type.eq(entry_type)),
+ )
.get_results::<models::Entry>(conn)?;
Ok(r)
}
pub fn get_template(
- conn: &PgConnection,
+ _conn: &PgConnection,
entry_type: &str,
) -> Result<template::TemplateSpec, Error> {
match entry_type {
diff --git a/src/server.rs b/src/server.rs
index 3d264a9..62ca2d1 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -77,16 +77,9 @@ fn profile(conn: Db, username: String) -> Result<Template, Error> {
let mut context = default_context();
context.insert("title".to_string(), json!(username));
- let entries = db::get_entries(&*conn, &username)?;
- let headings = entries
- .first()
- .and_then(|e| e.payload.as_object())
- .map(|e| e.keys().collect::<Vec<_>>());
- context.insert("headings".to_string(), json!(headings));
- context.insert(
- "entries".to_string(),
- json!(entries.into_iter().map(|e| e.payload).collect::<Vec<_>>()),
- );
+ let entries = db::get_entries(&*conn, &username, "run")?;
+ let template = db::get_template(&*conn, "run")?;
+ context.insert("document".to_string(), template.apply(entries)?);
Ok(Template::render("profile", context))
}
diff --git a/src/template.rs b/src/template.rs
index d469394..2810600 100644
--- a/src/template.rs
+++ b/src/template.rs
@@ -1,8 +1,10 @@
use crate::error::Error;
+use crate::models;
use serde::Deserialize;
use serde::Serialize;
use serde_json::to_value;
use serde_json::Value as Json;
+use chrono::DateTime;
pub fn running_template() -> TemplateSpec {
TemplateSpec::Table(vec![
@@ -10,7 +12,7 @@ pub fn running_template() -> TemplateSpec {
display_name: "Date".to_string(),
field: FieldSpec::App(
Function::DisplayUnit(Unit::Timestamp, "date".to_string()),
- vec![FieldSpec::Field("start_time".to_string())],
+ vec![FieldSpec::Field("start_timestamp".to_string())],
),
},
Column {
@@ -92,28 +94,51 @@ mod function {
.map_err(|_| Error::TemplateError("convert to f64".to_string(), d.clone()))
}
- fn format_pace(d: &Json) -> Result<Json, Error> {
- let pace = really_f64(d)?;
- let seconds_per_mile = (pace * METERS_PER_MILE).round() as i64;
+ fn format_duration(d: &Json, _opts: &str) -> Result<Json, Error> {
+ let seconds = really_f64(d)? as i64;
let mut hours = "".to_string();
- if seconds_per_mile > 3600 {
- hours = format!("{}", seconds_per_mile / 3600);
+ if seconds > 3600 {
+ hours = format!("{}:", seconds / 3600);
}
Ok(json!(format!(
"{}{:02}:{:02}",
hours,
- seconds_per_mile / 60,
- seconds_per_mile % 60
+ (seconds % 3600) / 60,
+ seconds % 60
)))
}
+ fn format_pace(d: &Json) -> Result<Json, Error> {
+ let pace = really_f64(d)?;
+ format_duration(&json!(pace * METERS_PER_MILE), "")
+ }
+
fn format_distance(d: &Json, opts: &str) -> Result<Json, Error> {
Ok(json!(match opts {
"miles" => format!("{:.1}", really_f64(d)? / METERS_PER_MILE),
- _ => format!("{} m", really_f64(d)?)
+ _ => format!("{} m", really_f64(d)?),
}))
}
+ fn format_timestamp(d: &Json, opts: &str) -> Result<Json, Error> {
+ fn format_timestamp(t: DateTime<chrono::Utc>, opts: &str) -> Result<Json, Error> {
+ let s = match opts {
+ "date" => t.format("%Y/%m/%d"),
+ _ => t.format("%c"),
+ };
+ Ok(json!(format!("{}", s)))
+ }
+
+ let t = d.as_str();
+ match t {
+ None => Ok(json!(())),
+ Some(t) => {
+ format_timestamp(DateTime::parse_from_rfc3339(t)?.with_timezone(&chrono::Utc), opts)
+ }
+ }
+
+ }
+
fn display_unit(u: Unit, opts: &str, params: &Vec<FieldSpec>, d: &Json) -> Result<Json, Error> {
if params.len() != 1 {
Err(Error::TemplateError(
@@ -123,9 +148,9 @@ mod function {
}
let d = eval(&params[0], &d)?;
Ok(match u {
- Unit::Timestamp => json!(format!("as_timestamp({:?})", d)),
+ Unit::Timestamp => format_timestamp(&d, &opts)?,
Unit::Meters => format_distance(&d, &opts)?,
- Unit::Seconds => json!(format!("as_duration({:?})", d)),
+ Unit::Seconds => format_duration(&d, &opts)?,
Unit::Speed => json!(format!("as_speed({:?})", d)),
Unit::Pace => format_pace(&d)?,
})
@@ -173,7 +198,7 @@ mod function {
#[test]
fn eval_fn1() {
- let d = json!({"x": 0.260976});
+ let d = json!({"x": 2});
let app1 = |f| {
println!("{:?}", f);
@@ -184,9 +209,13 @@ mod function {
.unwrap()
};
assert_eq!(
- json!("07:00"),
+ json!("53:00"),
app1(Function::DisplayUnit(Unit::Pace, "".to_string()))
);
+ assert_eq!(
+ json!("00:02"),
+ app1(Function::DisplayUnit(Unit::Seconds, "".to_string()))
+ );
}
#[test]
@@ -198,10 +227,16 @@ mod function {
assert_eq!(
eval(
- &FieldSpec::App(Function::Div,
- vec!(FieldSpec::Field("time".to_string()),
- FieldSpec::Field("distance".to_string()))),
- &d).unwrap(),
+ &FieldSpec::App(
+ Function::Div,
+ vec!(
+ FieldSpec::Field("time".to_string()),
+ FieldSpec::Field("distance".to_string())
+ )
+ ),
+ &d
+ )
+ .unwrap(),
json!((time as f64) / distance)
)
}
@@ -218,24 +253,34 @@ mod table {
}
pub fn apply(columns: &Vec<Column>, d: &Json) -> Result<DisplayTable, Error> {
- let rows = d.as_array()
+ let rows = d
+ .as_array()
.ok_or_else(|| Error::TemplateError("expected array".to_string(), d.clone()))?;
Ok(DisplayTable {
headings: columns.iter().map(|c| c.display_name.clone()).collect(),
- rows: rows.iter().map(|d| {
- columns.iter().map(|c| {
- function::eval(&c.field, d)
- }).collect::<Result<Vec<_>, _>>()
- }).collect::<Result<Vec<_>, _>>()?,
+ rows: rows
+ .iter()
+ .map(|d| {
+ columns
+ .iter()
+ .map(|c| function::eval(&c.field, d))
+ .collect::<Result<Vec<_>, _>>()
+ })
+ .collect::<Result<Vec<_>, _>>()?,
})
}
}
-pub fn apply(t: &TemplateSpec, d: &Json) -> Result<Json, Error> {
- Ok(match t {
- &TemplateSpec::Table(ref c) => to_value(table::apply(c, d)?)?,
- })
+impl TemplateSpec {
+ pub fn apply(self: &TemplateSpec, entries: Vec<models::Entry>) -> Result<Json, Error> {
+ info!("Applying template\n {:#?}", self);
+ info!("Sample entry\n {:#?}", entries.first());
+ let d = Json::Array(entries.into_iter().map(|e| e.payload).collect());
+ Ok(match self {
+ &TemplateSpec::Table(ref c) => to_value(table::apply(c, &d)?)?,
+ })
+ }
}
#[cfg(test)]
diff --git a/templates/profile.hbs b/templates/profile.hbs
index 240c443..c7e75e9 100644
--- a/templates/profile.hbs
+++ b/templates/profile.hbs
@@ -1,22 +1,24 @@
-{{#*inline "page"}}
-{{#if user}}
+{{#*inline "page"~}}
+{{#if user~}}
<p>Profile for {{ user }}</p>
-{{/if}}
+{{/if~}}
+{{#with document ~}}
<table>
<thead>
- {{#each headings}}
+ {{#each headings ~}}
<th>{{ this }}</th>
- {{/each}}
+ {{/each ~}}
</thead>
<tbody>
- {{#each entries}}
+ {{#each rows ~}}
<tr>
- {{#each this}}
+ {{#each this ~}}
<td>{{ this }}</td>
- {{/each}}
+ {{/each ~}}
</tr>
- {{/each}}
+ {{/each ~}}
</tbody>
</table>
-{{/inline}}
+{{/with~}}
+{{/inline~}}
{{~> (parent)~}}