diff --git a/fiberplane-templates/fiberplane.libsonnet b/fiberplane-templates/fiberplane.libsonnet index b184dfb7..4fb6add8 100644 --- a/fiberplane-templates/fiberplane.libsonnet +++ b/fiberplane-templates/fiberplane.libsonnet @@ -489,22 +489,92 @@ local notebook = { * used with the `frontmatter.*` helpers. * * @function notebook.Notebook#addFrontMatter - * @param {object} value - Map of frontMatter and frontMatterSchema to add to the notebook. + * @param {object | object[]} value - Map of frontMatter and frontMatterSchema to add to the notebook or an array of such objects * @returns {notebook.Notebook} * * @example notebook.addFrontMatter( * frontMatter.pagerdutyIncident(pagerduty_frontmatter) * ) */ - addFrontMatter(value):: self { - frontMatter+: value.frontMatter, - frontMatterSchema+: value.frontMatterSchema, - }, - + addFrontMatter(value):: + if std.isArray(value) then + std.foldl( + function(nb, v) + nb.addFrontMatter(v), + value, + self + ) + else self { + frontMatter+: value.frontMatter, + frontMatterSchema+: value.frontMatterSchema, + }, }, }; +local createFrontMatterValueWithSchema(key, value, schemaType, displayName, validatorType='string') = + // Validate the key and value types, and if they are valid return the front matter schema and value + if validate.string('front matter field ' + key + 'key', key) == key && + validate[validatorType]('front matter field ' + key + 'value', value) == value then + { + frontMatterSchema: [{ + key: key, + schema: { + type: schemaType, + displayName: displayName, + }, + }], + frontMatter: { + [key]: value, + }, + } else { + frontMatterSchema: [], + frontMatter: {}, + }; local frontMatter = { + /** + * Creates a number frontmatter value and schema. + * + * @function frontMatter.number + * @param {string} key - The key of the front matter entry + * @param {number} value - Number value of the front matter entry + * @param {string} [displayName="Number"] - The display name of the front matter entry + * @returns {object} + */ + number(key, value, displayName='Number'):: + createFrontMatterValueWithSchema(key, value, 'number', displayName, 'number'), + /** + * Creates a string frontmatter value and schema. + * + * @function frontMatter.string + * @param {string} key - The key of the front matter entry + * @param {string} value - String value of the front matter entry + * @param {string} [displayName="String"] - The display name of the front matter entry + * @returns {object} + */ + string(key, value, displayName='String'):: + createFrontMatterValueWithSchema(key, value, 'string', displayName), + /** + * Creates a datetime frontmatter value and schema. If incorrect datetime is provided, falls back to string. + * + * @function frontMatter.datetime + * @param {string} key - The key of the front matter entry + * @param {string} value - DateTime value of the front matter entry + * @param {string} [displayName="DateTime"] - The display name of the front matter entry + * @returns {object} + */ + dateTime(key, value, displayName='DateTime'):: + createFrontMatterValueWithSchema(key, value, 'date_time', displayName), + /** + * Creates a user frontmatter value and schema. + * + * @function frontMatter.user + * @param {string} key - The key of the front matter entry + * @param {string} value - UUID of the user + * @param {string} [displayName="User"] - The display name of the front matter entry + * @returns {object} + */ + user(key, value, displayName='User'):: + createFrontMatterValueWithSchema(key, value, 'user', displayName), /** * Creates a PagerDuty Incident frontmatter value and schema. * diff --git a/fiberplane-templates/src/expand/tests/frontmatter_tests.rs b/fiberplane-templates/src/expand/tests/frontmatter_tests.rs new file mode 100644 index 00000000..48183021 --- /dev/null +++ b/fiberplane-templates/src/expand/tests/frontmatter_tests.rs @@ -0,0 +1,213 @@ +use super::*; +use fiberplane_models::{ + front_matter_schemas::{ + FrontMatterDateTimeSchema, FrontMatterNumberSchema, FrontMatterSchema, + FrontMatterSchemaEntry, FrontMatterStringSchema, FrontMatterUserSchema, + }, + notebooks::front_matter::FrontMatterValue, +}; +use pretty_assertions::assert_eq; +use serde_json::Value; + +const ARGS: [(&str, Value); 0] = []; + +// Test manually adding a single value into the front matter +#[test] +fn expands_add_frontmatter() { + let template = r#" + local fp = import 'fiberplane.libsonnet'; + local fm = fp.frontMatter; + fp.notebook.new('Notebook') + .addFrontMatter({ + frontMatter: { + key: 42 + }, + frontMatterSchema: [{ + key: 'key', + schema: { + type: 'number', + displayName: 'Number' + }, + }] + }) + "#; + let output = expand_template(template, ARGS).unwrap(); + let expected_schema: FrontMatterSchema = vec![FrontMatterSchemaEntry::builder() + .key("key") + .schema( + FrontMatterNumberSchema::builder() + .display_name("Number") + .build(), + ) + .build()] + .into(); + assert_eq!(output.front_matter_schema, expected_schema); + let expected_front_matter: BTreeMap = + [("key".to_string(), json!(42).into())].into(); + assert_eq!(output.front_matter, expected_front_matter); +} + +// Test adding multiple number values into the front matter +#[test] +fn expands_add_frontmatter_number() { + let template = r#" + local fp = import 'fiberplane.libsonnet'; + local fm = fp.frontMatter; + fp.notebook.new('Notebook') + .addFrontMatter([ + fm.number('number', 42, 'Number of issues'), + fm.number('number-2', 13) + ]) + "#; + let output = expand_template(template, ARGS).unwrap(); + let expected_schema: FrontMatterSchema = vec![ + FrontMatterSchemaEntry::builder() + .key("number") + .schema( + FrontMatterNumberSchema::builder() + .display_name("Number of issues") + .build(), + ) + .build(), + FrontMatterSchemaEntry::builder() + .key("number-2") + .schema( + FrontMatterNumberSchema::builder() + .display_name("Number") + .build(), + ) + .build(), + ] + .into(); + assert_eq!(output.front_matter_schema, expected_schema); + let expected_front_matter: BTreeMap = [ + ("number".to_string(), json!(42).into()), + ("number-2".to_string(), json!(13).into()), + ] + .into(); + assert_eq!(output.front_matter, expected_front_matter); +} + +// Test adding string field into the front matter +#[test] +fn expands_add_frontmatter_string() { + let template = r#" + local fp = import 'fiberplane.libsonnet'; + local fm = fp.frontMatter; + fp.notebook.new('Notebook') + .addFrontMatter([ + fm.string('string', 'value', 'A field with string value'), + fm.string('string-2', 'value-2') + ]) + "#; + let output = expand_template(template, ARGS).unwrap(); + let expected_schema: FrontMatterSchema = vec![ + FrontMatterSchemaEntry::builder() + .key("string") + .schema( + FrontMatterStringSchema::builder() + .display_name("A field with string value") + .build(), + ) + .build(), + FrontMatterSchemaEntry::builder() + .key("string-2") + .schema( + FrontMatterStringSchema::builder() + .display_name("String") + .build(), + ) + .build(), + ] + .into(); + assert_eq!(output.front_matter_schema, expected_schema); + let expected_front_matter: BTreeMap = [ + ("string".to_string(), json!("value").into()), + ("string-2".to_string(), json!("value-2").into()), + ] + .into(); + assert_eq!(output.front_matter, expected_front_matter); +} + +// Test adding datetime field into the front matter +#[test] +fn expands_add_frontmatter_datetime() { + let template = r#" + local fp = import 'fiberplane.libsonnet'; + local fm = fp.frontMatter; + fp.notebook.new('Notebook') + .addFrontMatter( + fm.dateTime('datetime', '2021-01-01T00:00:00Z') + ) + "#; + let output = expand_template(template, ARGS).unwrap(); + let expected_schema: FrontMatterSchema = vec![FrontMatterSchemaEntry::builder() + .key("datetime") + .schema( + FrontMatterDateTimeSchema::builder() + .display_name("DateTime") + .build(), + ) + .build()] + .into(); + assert_eq!(output.front_matter_schema, expected_schema); + let expected_front_matter: BTreeMap = + [("datetime".to_string(), json!("2021-01-01T00:00:00Z").into())].into(); + assert_eq!(output.front_matter, expected_front_matter); +} + +// Test adding datetime field with incorrect value into the front matter +#[test] +fn expands_add_frontmatter_datetime_incorrect() { + let template = r#" + local fp = import 'fiberplane.libsonnet'; + local fm = fp.frontMatter; + fp.notebook.new('Notebook') + .addFrontMatter([ + fm.dateTime('correct', '2021-01-01T00:00:00Z'), + fm.dateTime('incorrect', 'not-a-date'), + ]) + "#; + let output = expand_template(template, ARGS).unwrap(); + if let Some(&FrontMatterValue::DateTime(value)) = output.front_matter.get("correct").as_ref() { + value + } else { + panic!( + "Expected a datetime value, got {}", + output.front_matter.get("incorrect").unwrap() + ); + }; + // this results in a string field, probably not the expected behavior + // let parsed_value = if let Some(&FrontMatterValue::DateTime(foo)) = output.front_matter.get("incorrect").as_ref() { + // foo + // } else { + // panic!("Expected a datetime value, got {}", output.front_matter.get("incorrect").unwrap()); + // }; +} + +// Test adding user value into the front matter +#[test] +fn expands_add_frontmatter_user() { + let template = r#" + local fp = import 'fiberplane.libsonnet'; + local fm = fp.frontMatter; + fp.notebook.new('Notebook') + .addFrontMatter([ + fm.user('user', 'base64-encoded-user'), + ]) + "#; + let output = expand_template(template, ARGS).unwrap(); + let expected_schema: FrontMatterSchema = vec![FrontMatterSchemaEntry::builder() + .key("user") + .schema( + FrontMatterUserSchema::builder() + .display_name("User") + .build(), + ) + .build()] + .into(); + assert_eq!(output.front_matter_schema, expected_schema); + let expected_front_matter: BTreeMap = + [("user".to_string(), json!("base64-encoded-user").into())].into(); + assert_eq!(output.front_matter, expected_front_matter); +} diff --git a/fiberplane-templates/src/expand/tests/mod.rs b/fiberplane-templates/src/expand/tests/mod.rs index 44e30f51..cc088ddc 100644 --- a/fiberplane-templates/src/expand/tests/mod.rs +++ b/fiberplane-templates/src/expand/tests/mod.rs @@ -1,3 +1,5 @@ +mod frontmatter_tests; + use super::*; use crate::types::{TemplateParameter, TemplateParameterType}; use crate::*;