Skip to content

Commit

Permalink
#74 Allow type conversion for timestamp and numeric types. (#75)
Browse files Browse the repository at this point in the history
* Allow type conversion for timestamp and numeric types.

* Allow merge field values to be objects, to support Address types.

* Added to changelog
  • Loading branch information
barticus authored Oct 4, 2023
1 parent 2ac7ef0 commit a9d2887
Show file tree
Hide file tree
Showing 6 changed files with 595 additions and 284 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Added linter config and updated packages. (PR #70)
- Node.JS 18 runtime for functions (PR #73)
- GitHub Actions Support to assist with verification and publishing (PR #77)
- Allow type conversion for timestamp and numeric types in merge fields. Additionally, support complex values in merge fields by facilitating path-style merge field names (PR #75).

## Version 0.5.3

Expand Down
517 changes: 256 additions & 261 deletions README.md

Large diffs are not rendered by default.

85 changes: 63 additions & 22 deletions extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -234,34 +234,41 @@ params:
1) `memberTags` - The Firestore document fields(s) to retrieve data from and classify as subscriber tags in Mailchimp. Acceptable data types include:
- Array\<String\> - The extension will lookup the values in the provided fields and update the subscriber's member tags with the respective data values. The format of each string can be any valid [JMES Path query](https://jmespath.org/). e.g. ["primaryTags", "additionalTags", "tags.primary"]
- `Array<String>` - The extension will lookup the values in the provided fields and update the subscriber's member tags with the respective data values. The format of each string can be any valid [JMES Path query](https://jmespath.org/). e.g. ["primaryTags", "additionalTags", "tags.primary"]
- Array\<Object\> - An extended object configuration is supported with the following fields:
- `documentPath` - (required) The path to the field in the document containing tag/s, as a string. The format can be any valid [JMES Path query](https://jmespath.org/). e.g. "primaryTags", "tags.primary".
- `Array<Object>` - An extended object configuration is supported with the following fields:
- `documentPath` - (required) The path to the field in the document containing tag/s, as a string. The format can be any valid [JMES Path query](https://jmespath.org/). e.g. "primaryTags", "tags.primary".
2) `subscriberEmail` - The Firestore document field capturing the user email as is recognized by Mailchimp
Configuration Example:
```json
{
"memberTags": ["domainKnowledge", "jobTitle"],
"subscriberEmail": "emailAddress"
}
}
```
Or via equivalent extended syntax:
```json
{
"memberTags": [{ "documentPath": "domainKnowledge" }, { "documentPath": "jobTitle" }],
"subscriberEmail": "emailAddress"
}
```
Based on the sample configuration, if the following Firestore document is provided:
```json
{
"firstName": "..",
"lastName": "..",
Expand All @@ -272,13 +279,15 @@ params:
"domainKnowledge": "..", // The config property 'memberTags' maps to this document field
"activity": []
}
```
Any data associated with the mapped fields (i.e. `domainKnowledge` and `jobTitle`) will be considered Member Tags and the Mailchimp user's profile will be updated accordingly.
For complex documents such as:
```json
{
"emailAddress": "..", // The config property 'subscriberEmail' maps to this document field
"meta": {
Expand All @@ -291,15 +300,18 @@ params:
}]
}
}
```
A configuration of the following will allow for the tag values of "red", "team1" to be sent to Mailchimp:
```json
{
"memberTags": [{ "documentPath": "meta.tags[*].value" }],
"subscriberEmail": "emailAddress"
}
```
NOTE: To disable this cloud function listener, provide an empty JSON config `{}`.
Expand All @@ -326,24 +338,33 @@ params:
1) `mergeFields` - JSON mapping representing the Firestore document fields to associate with Mailchimp Merge Fields. The key format can be any valid [JMES Path query](https://jmespath.org/) as a string. The value must be the name of a Mailchimp Merge Field as a string, or an object with the following properties:
- `mailchimpFieldName` - (required) The name of the Mailchimp Merge Field to map to, e.g. "FNAME".
- `mailchimpFieldName` - (required) The name of the Mailchimp Merge Field to map to, e.g. "FNAME". Paths are allowed, e.g. "ADDRESS.addr1" will map to an "ADDRESS" object.
- `when` - (optional) When to send the value of the field to Mailchimp. Options are "always" (which will send the value of this field on _any_ change to the document, not just this field) or "changed". Default is "changed".
- `typeConversion` - (optional) Whether to apply a type conversion to the value found at documentPath. Valid options:
- `none`: no conversion is applied.
- `timestampToDate`: Converts from a [Firebase Timestamp](https://firebase.google.com/docs/reference/android/com/google/firebase/Timestamp) to YYYY-MM-DD format (UTC).
- `stringToNumber`: Converts to a number.
- `when` - (optional) When to send the value of the field to Mailchimp. Options are "always" (which will send the value of this field on _any_ change to the document, not just this field) or "changed". Default is "changed".
2) `statusField` - An optional configuration setting for syncing the users mailchimp status. Properties are:
- `documentPath` - (required) The path to the field in the document containing the users status, as a string. The format can be any valid [JMES Path query](https://jmespath.org/). e.g. "status", "meta.status".
- `documentPath` - (required) The path to the field in the document containing the users status, as a string. The format can be any valid [JMES Path query](https://jmespath.org/). e.g. "status", "meta.status".
- `statusFormat` - (optional) Indicates the format that the status field is. The options are:
- `"string"` - The default, this will sync the value from the status field as is, with no modification.
- `"boolean"` - This will check if the value is truthy (e.g. true, 1, "subscribed"), and if so will resolve the status to "subscribed", otherwise it will resolve to "unsubscribed".
- `statusFormat` - (optional) Indicates the format that the status field is. The options are:
- `"string"` - The default, this will sync the value from the status field as is, with no modification.
- `"boolean"` - This will check if the value is truthy (e.g. true, 1, "subscribed"), and if so will resolve the status to "subscribed", otherwise it will resolve to "unsubscribed".
3) `subscriberEmail` - The Firestore document field capturing the user email as is recognized by Mailchimp
Configuration Example:
```
```json
{
"mergeFields": {
"firstName": "FNAME",
Expand All @@ -352,11 +373,13 @@ params:
},
"subscriberEmail": "emailAddress"
}
```
Or via equivalent extended syntax:
```json
{
"mergeFields": {
"firstName": { "mailchimpFieldName": "FNAME" },
Expand All @@ -365,12 +388,14 @@ params:
},
"subscriberEmail": "emailAddress"
}
```
Based on the sample configuration, if the following Firestore document is provided:
```
```json
{
"firstName": "..", // The config property FNAME maps to this document field
"lastName": "..", // The config property LNAME maps to this document field
Expand All @@ -380,14 +405,16 @@ params:
"domainKnowledge": "..",
"activity": []
}
```
Any data associated with the mapped fields (i.e. firstName, lastName, phoneNumber) will be considered Merge Fields and the Mailchimp user's profile will be updated accordingly.
If there is a requirement to always send the firstName and lastName values, the `"when": "always"` configuration option can be set on those fields, like so:
```
```json
{
"mergeFields": {
"firstName": { "mailchimpFieldName": "FNAME", "when": "always" },
Expand All @@ -396,20 +423,23 @@ params:
},
"subscriberEmail": "emailAddress"
}
```
This can be handy if Firebase needs to remain the source or truth or if the extension has been installed after data is already in the collection and there is a data migration period.
If the users status is also captured in the Firestore document, the status can be updated in Mailchimp by using the following configuration:
```
```json
{
"statusField": {
"documentPath": "meta.status",
"statusFormat": "string",
},
"subscriberEmail": "emailAddress"
}
```
This can be as well, or instead of, the `mergeFields` configuration property being set.
Expand Down Expand Up @@ -438,36 +468,42 @@ params:
1) `memberEvents` - The Firestore document fields(s) to retrieve data from and classify as member events in Mailchimp. Acceptable data types include:
- Array\<String\> - The extension will lookup the values (mailchimp event names) in the provided fields and post those events to Mailchimp on the subscriber's activity feed. The format can be any valid [JMES Path query](https://jmespath.org/). e.g. ["events", "meta.events"]
- `Array<String>` - The extension will lookup the values (mailchimp event names) in the provided fields and post those events to Mailchimp on the subscriber's activity feed. The format can be any valid [JMES Path query](https://jmespath.org/). e.g. ["events", "meta.events"]
- Array\<Object\> - An extended object configuration is supported with the following fields:
- `documentPath` - (required) The path to the field in the document containing events. The format can be any valid [JMES Path query](https://jmespath.org/). e.g. "events", "meta.events".
- `Array<Object>` - An extended object configuration is supported with the following fields:
- `documentPath` - (required) The path to the field in the document containing events. The format can be any valid [JMES Path query](https://jmespath.org/). e.g. "events", "meta.events".
2) `subscriberEmail` - The Firestore document field capturing the user email as is recognized by Mailchimp
Configuration Example:
```
```json
{
"memberEvents": [
"activity"
],
"subscriberEmail": "emailAddress"
}
```
Or via equivalent extended syntax:
```
```json
{
"memberEvents": [{ "documentPath": "activity" }],
"subscriberEmail": "emailAddress"
}
```
Based on the sample configuration, if the following Firestore document is provided:
```
```json
{
"firstName": "..",
"lastName": "..",
Expand All @@ -478,13 +514,15 @@ params:
"emailAddress": "..", // The config property "subscriberEmail" maps to this document field
"activity": ["send_welcome_email"] // The config property "memberTags" maps to this document field
}
```
Any data associated with the mapped fields (i.e. `activity`) will be considered events and the Mailchimp user's profile will be updated accordingly.
For complex documents such as:
```
```json
{
"emailAddress": "..", // The config property 'subscriberEmail' maps to this document field
"meta": {
Expand All @@ -497,15 +535,18 @@ params:
}]
}
}
```
A configuration of the following will allow for the events of "Registered", "Invited Friend" to be sent to Mailchimp:
```
```json
{
"memberEvents": [{ "documentPath": "meta.events[*].title" }],
"subscriberEmail": "emailAddress"
}
```
NOTE: To disable this cloud function listener, provide an empty JSON config `{}`.
Expand Down
28 changes: 27 additions & 1 deletion functions/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const crypto = require("crypto");
const assert = require("assert");
const _ = require("lodash");
const { auth, firestore, logger } = require("firebase-functions");
const admin = require("firebase-admin");
Expand Down Expand Up @@ -138,6 +139,18 @@ async function wait(attempt) {
return new Promise((resolve) => setTimeout(resolve, time));
}

/**
* Converts a Firestore Timestamp type to YYYY-MM-DD format
* @param {import('firebase-admin').firestore.Timestamp} timestamp
* @returns {string} The date in string format.
*/
function convertTimestampToMailchimpDate(timestamp) {
assert(timestamp instanceof admin.firestore.Timestamp, `Value ${timestamp} is not a Timestamp`);
const timestampDate = timestamp.toDate();
const padNumber = (number) => _.padStart(number, 2, "0");
return `${timestampDate.getUTCFullYear()}-${padNumber(timestampDate.getUTCMonth() + 1)}-${padNumber(timestampDate.getUTCDate())}`;
}

/**
* Attempts the provided function
* @template T
Expand Down Expand Up @@ -363,7 +376,20 @@ exports.mergeFieldsHandler = firestore.document(config.mailchimpMergeFieldWatchP

// if delta exists or the field should always be sent, then update accumulator collection
if (prevMergeFieldValue !== newMergeFieldValue || (_.isObject(mergeFieldConfig) && mergeFieldConfig.when && mergeFieldConfig.when === "always")) {
acc[mergeFieldName] = newMergeFieldValue;
const conversionToApply = _.isObject(mergeFieldConfig) && mergeFieldConfig.typeConversion ? mergeFieldConfig.typeConversion : "none";
let finalValue = newMergeFieldValue;
switch (conversionToApply) {
case "timestampToDate":
finalValue = convertTimestampToMailchimpDate(newMergeFieldValue);
break;
case "stringToNumber":
finalValue = Number(newMergeFieldValue);
assert(!isNaN(finalValue), `${newMergeFieldValue} could not be converted to a number.`);
break;
default:
break;
}
_.set(acc, mergeFieldName, finalValue);
}
return acc;
}, {});
Expand Down
Loading

0 comments on commit a9d2887

Please sign in to comment.