src/
Describes the src/
directory for applications in Optimizely Connect Platform (OCP).
The src/
directory contains source code for your app and is where you will spend most of your time developing your app. You can organize your code however you prefer, but some folders have a specific purpose in Optimizely Connect Platform (OCP):
src/channel/
– Contains theChannel.ts
file for channel apps.src/lifecycle/
– Contains theLifecycle.ts
file, which contains callback methods OCP calls at various points in the app's lifecycle.src/liquid-extensions/
– Contains the code you can use as functions for liquid templates.src/functions/
– Contains the code for functions.src/jobs/
– Contains the code for jobs.src/schema/
– Contains YAML files with declarative Optimizely Data Platform (ODP) schema definitions.
You can add any other directory and reference files from there in your code. A common practice is to create a src/lib/
directory for your own libraries and utilities.
channel/
folder
channel/
folderChannel apps allow you to create a new channel that you can use within campaigns in ODP.
Channel app resources:
Below is an example of the channel
section in the app.yml
file:
channel:
type: sms
targeting:
- identifier: phone #<-- field for reachability/consent
options:
prepare: false
events:
send: true
delivery: true
delivery_unknown: true
hard_bounce: true
soft_bounce: true
spam_report: false
active_actions:
- engage
- disengage
channel/
functions
channel/
functionsFunction | Called | Parameters | Return |
---|---|---|---|
ready | is channel ready | none | boolean |
target | n/a | contentSettings | ChannelTargetResult |
validate | content, on campaign save or campaign run | content, options | ChannelValidateOptions |
publish | to publish content to provider | contentKey, content, options | ChannelContentResult |
prepare | before campaign run | contentKey, tracking, options | ChannelPrepareResult |
preview | on preview | content, batch | ChannelPreviewResult |
deliver | deliver batch | contentKey, tracking, options, batch, previousResult | ChannelDeliverResult |
Example Channel.ts
file
Channel.ts
fileimport * as App from '@zaius/app-sdk';
import {Batcher, CampaignContent, CampaignDelivery, CampaignTracking, ChannelContentResult, ChannelDeliverOptions, ChannelDeliverResult, ChannelPreviewResult, ChannelPublishOptions, ChannelTargetResult, ChannelValidateOptions, KVHash, logger, storage} from '@zaius/app-sdk';
import {z} from '@zaius/node-sdk';
import * as URL from 'url';
/**
* Defines the interface of a channel. The typical channel flow in a campaign run is:
* 1. Check if the channel is `ready` to use
* 2. Dynamically determine the `target` (may be provided statically in the `app.yml` file instead)
* 3. `publish` the content template (in the future, this will instead happen when the campaign is modified)
* 4. `prepare` for the run (if the method is implemented)
* 5. `deliver` the content to batches of recipients with substitutions
*
* Outside of the campaign run flow, a channel must also be able to `preview` a given piece of content for a batch
* of recipients.
*/
export class Channel extends App.Channel {
/**
* Checks if the channel is ready to use. This ensures that any required credentials and/or other configuration
* exist and are valid. Utilize reasonable caching to prevent excessive requests to external resources.
* @async
* @returns true if the channel is ready to use
*/
public async ready(): Promise<boolean> {
return true;
}
/**
* Dynamically determines campaign targeting requirements. It should also perform any necessary validations on the
* input data. If targeting is always known ahead of time, specify this statically using `channel.targeting`
* in `app.yml`. If targeting is based on selections made in the content settings form, you must implement this method
* and set the value in `app.yml` to `dynamic`.
* @async
* @param contentSettings data from the content settings form
* @returns result of the operation
*/
public async target(contentSettings: ContentSettingsFormData): Promise<ChannelTargetResult> {
const result = new ChannelTargetResult();
result.addTargeting({identifier: contentSettings.target.identifier});
return result;
}
/**
* Validates the given content. This ensures the content is suitable for use in the current mode, as
* specified in the options (see {@link ChannelValidateOptions.mode}). If specific fields are missing or invalid,
* provide appropriate error messages using {@link ChannelContentResult.addError}. Provide any errors that are
* not linked to a specific field using {@link ChannelContentResult.addToast}. If no errors of
* either type are returned, the validation is considered successful, and the operation is allowed to proceed.
* @async
* @param content the content with translated tempaltes
* @param options additional options
* @returns result of the operation
*/
public async validate(
content: CampaignContent, options: ChannelValidateOptions
): Promise<ChannelContentResult> {
const result = new ChannelContentResult();
this.validateSettings(content.settings as ContentSettingsFormData, result);
await this.validateTemplate(content.template as ContentTemplateFormData, result);
return result;
}
/**
* Publishes the given content. This is the place to perform any necessary transformations between the given template
* format and the external system's format. It is assumed that {@link Channel.validate} has already been called,
* but additional errors may still be detected in this phase and returned in the same way as during validation.
* <p>
* If you must store the content in an external system, this is also the time to do that. If the content must instead
* be known in `prepare` or `deliver`, place it in the document store for future use.
* <p>
* You may call this method multiple times with the same content key. But for a given content key, it is always
* called with the same content and options. As such, when successful, treat it as an idempotent
* operation at the content key level. So if you have already processed and successfully stored the given key, there
* is no need to process and store it again.
* @async
* @param contentKey unique key for the content
* @param content the content with translated templates
* @param options additional options
* @returns result of the operation
*/
public async publish(
contentKey: string, content: CampaignContent, options: ChannelPublishOptions
): Promise<ChannelContentResult> {
const result = new ChannelContentResult();
return result;
}
/**
* Prepares for a campaign run. You can use this to set up an external entity for use in `deliver` (or perform any
* other processing that should only be performed once per run). If this step is unnecessary, do not implement
* the method.
* <p>
* If implemented, this method is called exactly once per content key involved in a campaign run. If any one of
* these fails, the campaign run fails.
* @async
* @param contentKey unique key of the content
* @param tracking campaign tracking parameters
* @param options additional options
* @returns result of the operation
*/
// public async prepare?(
// contentKey: string, tracking: CampaignTracking, options: ChannelPrepareOptions
// ): Promise<ChannelPrepareResult>;
/**
* Delivers a batch of messages. You can call this method many times for the same content key, tracking parameters,
* and options, each with a unique batch of recipients and substitutions. A batch either succeeds
* or fails as a whole. There is no partial delivery handling.
* <p>
* If a batch fails, you can retry it (as controlled by the return value). When that happens, the subsequent call(s)
* for that batch will be given the previous result for reference to enable proper recovery logic.
* <p>
* Once a batch succeeds, it is never given again.
* @async
* @param contentKey unique key of the content
* @param tracking campaign tracking parameters
* @param options additional options
* @param batch of recipients and substitutions
* @param previousResult previous result of the operation, if this is a retry
* @returns result of the operation
*/
public async deliver(
contentKey: string,
tracking: CampaignTracking,
options: ChannelDeliverOptions,
batch: CampaignDelivery[],
previousResult?: ChannelDeliverResult
): Promise<ChannelDeliverResult> {
// Because OCP reports soft_bounces, retries are not supported.
// Even if everything was not delivered, the delivery call was completed.
return {success: true};
}
/**
* Renders a batch of messages into HTML previews. Each preview must be a full HTML page containing a user-friendly
* representation of the message as it would be delivered.
* @async
* @param content the content with translated templates
* @param batch of recipients and substitutions
* @returns result of the operation
*/
public async preview(content: CampaignContent, batch: CampaignDelivery[]): Promise<ChannelPreviewResult> {
const result = new ChannelPreviewResult();
return result;
}
}
lifecycle/
folder
lifecycle/
folderDefine functions that you call throughout the lifecycle of an app in the lifecycle/
folder.
Lifecycle resources:
Note
There is no
lifecycle
section in theapp.yml
file. OCP reads thelifecycle/Lifecycle.ts
file directly and executes methods from it.
lifecycle/
functions
lifecycle/
functionsFunction | Called | Parameters | Return |
---|---|---|---|
onSettingsForm | on settings form action | page, action, formData | App.Response() |
onUpgrade | on version upgrade | fromVersion | {success: } |
onFinalizeUpgrade | after onUpgrade | fromVersion | {success: } |
onInstall | on user install of app | none | {success: } |
onUninstall | on user uninstall of app | none | {success: } |
Example Lifecycle.ts
file
Lifecycle.ts
fileimport * as App from '@zaius/app-sdk';
import {logger, storage} from '@zaius/app-sdk';
export class Lifecycle extends App.Lifecycle {
public async onInstall(): Promise<App.LifecycleResult> {
try {
logger.info(`Performing Install`);
// TODO: any operation you need to perform during installation
return {success: true};
} catch (error) {
logger.error('Error during installation:', error);
return {success: false, retryable: true};
}
}
public async onSetingsForm(page: string, action: string, formData: App.ValueHash): Promise<App.Response> {
try {
// TODO: any logic you need to perform when a setup form section is submitted
// When you are finished, save the form data to the settings store
storage.settings.put(page, formData);
return new App.Response(200);
} catch (e) {
return new App.Response(500);
}
}
public async onUpgrade(fromVersion: string): Promise<App.LifecycleResult> {
// TODO: any logic required when upgrading from a previous version of the app
// Note: `fromVersion` may not be the most recent version or could be a beta version
return {success: true};
}
public async onFinalizeUpgrade(fromVersion: string): Promise<App.LifecycleResult> {
// TODO: any logic required when finalizing an upgrade from a previous version
// At this point, new webhook URLs have been created for any new functions in this version
return {success: true};
}
public async onUninstall(): Promise<App.LifecycleResult> {
// TODO: any logic required to properly uninstall the app
return {success: true};
}
}
liquid-extensions/
folder
liquid-extensions/
folderDefine the code for Liquid extensions in the liquid-extensions/
folder. Each liquid extension is a separate file in the liquid-extensions/
folder that contains a class extending the LiquidExtension
class. Both file and class names must match the value of the entry_point
property from the app.yaml
file.
Liquid extension resources:
functions/
folder
functions/
folderFunctions are small, executable blocks of code typically triggered from a real-time event. An app can have any number of functions stored in the src/functions
directory.
Functions resources:
Below is an example of the functions
section in the app.yml
file:
functions:
get_esp_lists: #<-- name used for form sources and reference within the app
entry_point: GetEspLists #<-- File and Class name
description: Pulls the lists from the ESP to select which ones you want imported
functions/
functions
functions/
functionsFunction | Called | Parameters | Return |
---|---|---|---|
perform | on request | Request | App.Response() |
Example GetEspLists.ts
file
GetEspLists.ts
fileimport * as App from '@zaius/app-sdk';
import {logger} from '@zaius/app-sdk';
import {z} from '@zaius/node-sdk';
/**
* Returns the lists within ODP
*/
export class GetEspLists extends App.Function {
public async perform(): Promise<App.Response> {
try {
return new App.Response(200, await z.lists.getLists().data);
} catch (e) {
logger.error(e);
return new App.Response(500, `An unexpected error occurred: ${e}`);
}
}
}
}
jobs/
folder
jobs/
folderJobs are asynchronous functions with an undefined completion time. Jobs are typically used for historical imports of data or recurring imports of data from APIs that do not support real-time functions.
Jobs resources:
Below is an example of the jobs
section in the app.yml
file:
jobs:
historical_import: #<-- used to reference within app
entry_point: Import #<-- File/Class name
description: Performs a one-time historical import when triggered
jobs/
functions
jobs/
functionsFunction | Called | Parameters | Return |
---|---|---|---|
prepare | before perform | params, status | status |
perform | after perform, until status.complete == true | status | status |
Note
status
is an interface that contains the information needed to complete a unit of work. Often, this includes the last page called from an API, number of records imported, and so on.
Example Import.ts
file
Import.ts
fileimport * as App from '@zaius/app-sdk';
import {logger, ValueHash} from '@zaius/app-sdk';
interface ImportJobStatus extends App.JobStatus {
state: {
// TODO: define your job state here
};
}
/**
* Performs an import
*/
export class Import extends App.Job {
/**
* Prepares to run a job. Prepare is called at the start of a job
* and again only if the job was interrupted and is being resumed.
* Use this function to read secrets and establish connections to simplify the job loop (perform).
* @param params a hash if params were supplied to the job run, otherwise an empty hash
* @param status if job was interrupted and should continue from the last known state
*/
public async prepare(params: ValueHash, status?: ImportJobStatus): Promise<ImportJobStatus> {
logger.info('Preparing Import Job with params:', params, 'and status', status);
// TODO: Prepare for a job run
return {state: {}, complete: false};
}
/**
* Performs a unit of work. Jobs should perform a small unit of work and then return the current state.
* Perform is called in a loop where the previously returned state will be given to the next iteration.
* Iteration will continue until the returned state.complete is set to true or the job is interrupted.
* @param status last known job state and status
* @returns The current JobStatus/state that you can use to perform the next iteration or resume a job if interrupted.
*/
public async perform(status: ImportJobStatus): Promise<ImportJobStatus> {
// TODO: perform a small unit of work, then return your state
// When you are finished, set complete to true
status.complete = true;
// Return your status. If complete is false, perform will be called again imediately with the status you returned
return status;
}
}
schema/
folder
schema/
folderThe src/schema
folder contains YAML files with declarative, static schema definitions for an app. OCP reads these files when users install or upgrade an app and makes sure the ODP account where the app is installed contains all declared objects and fields.
For more information about schema, see Schema for objects and fields.
Updated about 1 year ago