Skip to content

Commit

Permalink
#1025: Add support to runOnce an automation via execute command
Browse files Browse the repository at this point in the history
  • Loading branch information
JoernBerkefeld committed Jul 14, 2023
1 parent c107210 commit 1af9597
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 43 deletions.
45 changes: 42 additions & 3 deletions docs/dist/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ Provides default functionality that can be overwritten by child metadata type cl
<dt><a href="#Automation.">Automation.(metadataMap, key)</a> ⇒ <code>Promise.&lt;object&gt;</code></dt>
<dd><p>helper for <a href="#Automation.execute">execute</a></p>
</dd>
<dt><a href="#Automation.">Automation.(metadata)</a> ⇒ <code>Promise.&lt;object&gt;</code></dt>
<dt><a href="#Automation.">Automation.(metadataEntry)</a> ⇒ <code>Promise.&lt;object&gt;</code></dt>
<dd><p>helper for <a href="#Automation.execute">execute</a></p>
</dd>
<dt><a href="#Automation.">Automation.(metadata)</a> ⇒ <code>Promise.&lt;object&gt;</code></dt>
Expand Down Expand Up @@ -3314,6 +3314,7 @@ Provides default functionality that can be overwritten by child metadata type cl
* [.retrieveSOAP(retrieveDir, [requestParams], [singleRetrieve], [additionalFields])](#MetadataType.retrieveSOAP) ⇒ <code>Promise.&lt;TYPE.MetadataTypeMapObj&gt;</code>
* [.retrieveREST(retrieveDir, uri, [templateVariables], [singleRetrieve])](#MetadataType.retrieveREST) ⇒ <code>Promise.&lt;{metadata: (TYPE.MetadataTypeMap\|TYPE.MetadataTypeItem), type: string}&gt;</code>
* [.executeREST(uri, key)](#MetadataType.executeREST) ⇒ <code>Promise.&lt;string&gt;</code>
* [.executeSOAP([metadataEntry])](#MetadataType.executeSOAP) ⇒ <code>Promise.&lt;object&gt;</code>
* [.runDocumentOnRetrieve([singleRetrieve], metadataMap)](#MetadataType.runDocumentOnRetrieve) ⇒ <code>Promise.&lt;void&gt;</code>
* [.parseResponseBody(body, [singleRetrieve])](#MetadataType.parseResponseBody) ⇒ <code>TYPE.MetadataTypeMap</code>
* [.deleteFieldByDefinition(metadataEntry, fieldPath, definitionProperty, origin)](#MetadataType.deleteFieldByDefinition) ⇒ <code>void</code>
Expand Down Expand Up @@ -3802,6 +3803,18 @@ Used to execute a query/automation etc.
| uri | <code>string</code> | REST endpoint where the POST request should be sent |
| key | <code>string</code> | item key |

<a name="MetadataType.executeSOAP"></a>

### MetadataType.executeSOAP([metadataEntry]) ⇒ <code>Promise.&lt;object&gt;</code>
Used to execute a query/automation etc.

**Kind**: static method of [<code>MetadataType</code>](#MetadataType)
**Returns**: <code>Promise.&lt;object&gt;</code> - api response

| Param | Type | Description |
| --- | --- | --- |
| [metadataEntry] | <code>TYPE.MetadataTypeItem</code> | single metadata entry |

<a name="MetadataType.runDocumentOnRetrieve"></a>

### MetadataType.runDocumentOnRetrieve([singleRetrieve], metadataMap) ⇒ <code>Promise.&lt;void&gt;</code>
Expand Down Expand Up @@ -5998,6 +6011,7 @@ CLI entry for SFMC DevTools
* [.getSsjs(code)](#Util.getSsjs) ⇒ <code>string</code>
* [.stringLike(testString, search)](#Util.stringLike) ⇒ <code>boolean</code>
* [.fieldsLike(metadata, [filters])](#Util.fieldsLike) ⇒ <code>boolean</code>
* [.capitalizeFirstLetter(str)](#Util.capitalizeFirstLetter) ⇒ <code>string</code>

<a name="Util.skipInteraction"></a>

Expand Down Expand Up @@ -6380,6 +6394,18 @@ returns true if no LIKE filter is defined or if all filters match
| metadata | <code>TYPE.MetadataTypeItem</code> | a single metadata item |
| [filters] | <code>object</code> | only used in recursive calls |

<a name="Util.capitalizeFirstLetter"></a>

### Util.capitalizeFirstLetter(str) ⇒ <code>string</code>
helper used by SOAP methods to ensure the type always uses an upper-cased first letter

**Kind**: static method of [<code>Util</code>](#Util)
**Returns**: <code>string</code> - str with first letter capitalized

| Param | Type |
| --- | --- |
| str | <code>string</code> |

<a name="MetadataTypeDefinitions"></a>

## MetadataTypeDefinitions
Expand Down Expand Up @@ -7916,6 +7942,7 @@ Util that contains logger and simple util methods
* [.getSsjs(code)](#Util.getSsjs) ⇒ <code>string</code>
* [.stringLike(testString, search)](#Util.stringLike) ⇒ <code>boolean</code>
* [.fieldsLike(metadata, [filters])](#Util.fieldsLike) ⇒ <code>boolean</code>
* [.capitalizeFirstLetter(str)](#Util.capitalizeFirstLetter) ⇒ <code>string</code>

<a name="Util.skipInteraction"></a>

Expand Down Expand Up @@ -8298,6 +8325,18 @@ returns true if no LIKE filter is defined or if all filters match
| metadata | <code>TYPE.MetadataTypeItem</code> | a single metadata item |
| [filters] | <code>object</code> | only used in recursive calls |

<a name="Util.capitalizeFirstLetter"></a>

### Util.capitalizeFirstLetter(str) ⇒ <code>string</code>
helper used by SOAP methods to ensure the type always uses an upper-cased first letter

**Kind**: static method of [<code>Util</code>](#Util)
**Returns**: <code>string</code> - str with first letter capitalized

| Param | Type |
| --- | --- |
| str | <code>string</code> |

<a name="csvToArray"></a>

## csvToArray(csv) ⇒ <code>Array.&lt;string&gt;</code>
Expand Down Expand Up @@ -8381,15 +8420,15 @@ helper for [execute](#Automation.execute)

<a name="Automation."></a>

## Automation.(metadata) ⇒ <code>Promise.&lt;object&gt;</code>
## Automation.(metadataEntry) ⇒ <code>Promise.&lt;object&gt;</code>
helper for [execute](#Automation.execute)

**Kind**: global function
**Returns**: <code>Promise.&lt;object&gt;</code> - Returns the result of the API call

| Param | Type | Description |
| --- | --- | --- |
| metadata | <code>TYPE.AutomationItem</code> | metadata json |
| metadataEntry | <code>TYPE.AutomationItem</code> | metadata object |

<a name="Automation."></a>

Expand Down
38 changes: 28 additions & 10 deletions lib/metadataTypes/Automation.js
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,8 @@ class Automation extends MetadataType {
static async execute(keyArr) {
const metadataMap = {};
for (const key of keyArr) {
if (key) {
if (Util.OPTIONS.schedule || Util.OPTIONS.execute === 'schedule') {
// schedule
const results = await this.retrieve(undefined, undefined, undefined, key);
if (Object.keys(results.metadata).length) {
for (const key of Object.keys(results.metadata)) {
Expand All @@ -476,20 +477,37 @@ class Automation extends MetadataType {
}
}
}
} else {
// runOnce
const objectId = await this.#getObjectIdForSingleRetrieve(key);
metadataMap[key] = {};
metadataMap[key][this.definition.idField] = objectId;
metadataMap[key][this.definition.keyField] = key;
}
}

if (!Object.keys(metadataMap).length) {
Util.logger.error(`No ${this.definition.type} to execute`);
return false;
}
Util.logger.info(
`Starting automations according to schedule: ${Object.keys(metadataMap).length}`
`Starting automations ${
Util.OPTIONS.schedule || Util.OPTIONS.execute === 'schedule'
? 'according to schedule'
: 'to run once (use --schedule or --execute=schedule to schedule instead)'
}: ${Object.keys(metadataMap).length}`
);
const promiseResults = [];

for (const key of Object.keys(metadataMap)) {
if (metadataMap[key].status === 'Scheduled') {
if (
(Util.OPTIONS.schedule || Util.OPTIONS.execute === 'schedule') &&
metadataMap[key].status === 'Scheduled'
) {
// schedule
Util.logger.info(
` - skipping ${this.definition.type} ${metadataMap[key].name}: already scheduled.`
);
} else {
// schedule + runOnce
promiseResults.push(this.#executeItem(metadataMap, key));
}
}
Expand Down Expand Up @@ -520,11 +538,11 @@ class Automation extends MetadataType {
/**
* helper for {@link Automation.execute}
*
* @param {TYPE.AutomationItem} metadata metadata json
* @param {TYPE.AutomationItem} metadataEntry metadata object
* @returns {Promise.<object>} Returns the result of the API call
*/
static async #runOnce(metadata) {
return metadata;
static async #runOnce(metadataEntry) {
return super.executeSOAP(metadataEntry);
}
/**
* a function to start query execution via API
Expand Down Expand Up @@ -1441,7 +1459,7 @@ class Automation extends MetadataType {
* @param {string} key customer key
* @returns {Promise.<string>} objectId or enpty string
*/
static async _getObjectIdForSingleRetrieve(key) {
static async #getObjectIdForSingleRetrieve(key) {
const response = await this.client.soap.retrieve('Program', ['ObjectID'], {
filter: {
leftOperand: 'CustomerKey',
Expand All @@ -1460,7 +1478,7 @@ class Automation extends MetadataType {
*/
static async deleteByKey(customerKey) {
// the delete endpoint returns a general exception if the automation does not exist; handle it gracefully instead by adding a retrieve first
const objectId = customerKey ? await this._getObjectIdForSingleRetrieve(customerKey) : null;
const objectId = customerKey ? await this.#getObjectIdForSingleRetrieve(customerKey) : null;
if (!objectId) {
Util.logger.error(` - automation not found`);
return false;
Expand Down
59 changes: 48 additions & 11 deletions lib/metadataTypes/MetadataType.js
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,7 @@ class MetadataType {
this.removeNotCreateableFields(metadataEntry);
try {
const response = await this.client.soap.create(
soapType.charAt(0).toUpperCase() + soapType.slice(1),
Util.capitalizeFirstLetter(soapType),
metadataEntry,
null
);
Expand Down Expand Up @@ -956,7 +956,7 @@ class MetadataType {
this.removeNotUpdateableFields(metadataEntry);
try {
const response = await this.client.soap.update(
soapType.charAt(0).toUpperCase() + soapType.slice(1),
Util.capitalizeFirstLetter(soapType),
metadataEntry,
null
);
Expand Down Expand Up @@ -997,9 +997,14 @@ class MetadataType {
* @returns {string} error message
*/
static getSOAPErrorMsg(ex) {
return ex?.json?.Results?.length
? `${ex.json.Results[0].StatusMessage} (Code ${ex.json.Results[0].ErrorCode})`
: ex.message;
if (ex?.json?.Results?.length) {
if (ex?.json?.Results[0].StatusMessage) {
return `${ex.json.Results[0].StatusMessage} (Code ${ex.json.Results[0].ErrorCode})`;
} else if (ex?.json?.Results[0].Result.StatusMessage) {
return `${ex.json.Results[0].Result.StatusMessage} (Code ${ex.json.Results[0].Result.ErrorCode})`;
}
}
return ex.message;
}
/**
* Retrieves SOAP via generic fuel-soap wrapper based metadata of metadata type into local filesystem. executes callback with retrieved metadata
Expand All @@ -1016,7 +1021,11 @@ class MetadataType {
const soapType = this.definition.soapType || this.definition.type;
let response;
try {
response = await this.client.soap.retrieveBulk(soapType, fields, requestParams);
response = await this.client.soap.retrieveBulk(
Util.capitalizeFirstLetter(soapType),
fields,
requestParams
);
} catch (ex) {
this._handleSOAPErrors(ex, 'retrieving');
return {};
Expand Down Expand Up @@ -1100,6 +1109,38 @@ class MetadataType {
}
}

/**
* Used to execute a query/automation etc.
*
* @param {TYPE.MetadataTypeItem} [metadataEntry] single metadata entry
* @returns {Promise.<object>} api response
*/
static async executeSOAP(metadataEntry) {
const soapType = this.definition.soapType || this.definition.type;
try {
const response = await this.client.soap.perform(
Util.capitalizeFirstLetter(soapType),
'start',
{
ObjectID: metadataEntry[this.definition.idField],
}
);
if (response?.OverallStatus === 'OK') {
Util.logger.info(
` - executed ${this.definition.type}: ${
metadataEntry[this.definition.keyField]
}`
);
} else {
throw new Error(response?.OverallStatus);
}
return response;
} catch (ex) {
this._handleSOAPErrors(ex, 'executing', metadataEntry);
return null;
}
}

/**
* helper for {@link MetadataType.retrieveREST} and {@link MetadataType.retrieveSOAP}
*
Expand Down Expand Up @@ -1912,11 +1953,7 @@ class MetadataType {
metadata[overrideKeyField || this.definition.keyField] = customerKey;
const soapType = this.definition.soapType || this.definition.type;
try {
await this.client.soap.delete(
soapType.charAt(0).toUpperCase() + soapType.slice(1),
metadata,
null
);
await this.client.soap.delete(Util.capitalizeFirstLetter(soapType), metadata, null);
if (!handleOutside) {
Util.logger.info(` - deleted ${this.definition.type}: ${customerKey}`);
}
Expand Down
9 changes: 9 additions & 0 deletions lib/util/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,15 @@ const Util = {
return false;
});
},
/**
* helper used by SOAP methods to ensure the type always uses an upper-cased first letter
*
* @param {string} str

Check warning on line 840 in lib/util/util.js

View workflow job for this annotation

GitHub Actions / Test and report

Missing JSDoc @param "str" description

Check warning on line 840 in lib/util/util.js

View workflow job for this annotation

GitHub Actions / lint & test

Missing JSDoc @param "str" description
* @returns {string} str with first letter capitalized
*/
capitalizeFirstLetter(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
},
};

Util.startLogger(false, true);
Expand Down
10 changes: 10 additions & 0 deletions test/resourceFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,16 @@ exports.handleSOAPRequest = async (config) => {

break;
}
case 'Perform': {
responseXML = await this.loadSOAPRecords(
config.headers.SOAPAction.toLocaleLowerCase(),
fullObj.Envelope.Body.PerformRequestMsg.Definitions.Definition['@_xsi:type'],
jObj.Envelope.Header.fueloauth,
fullObj.Envelope.Body.PerformRequestMsg.Definitions.Definition.ObjectID
);

break;
}
default: {
throw new Error(
`The SOAP Action ${config.headers.SOAPAction} is not supported by test handler`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<soap:Header>
<wsa:Action>DeleteResponse</wsa:Action>
<wsa:MessageID>urn:uuid:cc45bb83-15a3-4a82-ba48-47ac4fb13000</wsa:MessageID>
<wsa:RelatesTo>urn:uuid:29986848-a0c3-4f3b-bf5a-91151f784449</wsa:RelatesTo>
<wsa:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To>
<wsse:Security>
<wsu:Timestamp wsu:Id="Timestamp-4e8dad58-7a68-4653-8406-a172d1960c8a">
<wsu:Created>2023-06-02T13:41:30Z</wsu:Created>
<wsu:Expires>2023-06-02T13:46:30Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
</soap:Header>
<soap:Body>
<PerformResponseMsg xmlns="http://exacttarget.com/wsdl/partnerAPI">
<Results>
<Result>
<StatusCode>OK</StatusCode>
<StatusMessage>Performed Activity</StatusMessage>
<Object xsi:type="Automation">
<PartnerKey xsi:nil="true" />
<ObjectID>08afb0e2-b00a-4c88-ad2e-1f7f8788c560</ObjectID>
<CustomerKey>testExisting_automation</CustomerKey>
<IsPlatformObject>false</IsPlatformObject>
<Name />
<Description />
<IsActive>true</IsActive>
</Object>
</Result>
</Results>
<OverallStatus>OK</OverallStatus>
<OverallStatusMessage />
<RequestID>898702fc-a432-4176-8130-5d6dd5ad9ca6</RequestID>
</PerformResponseMsg>
</soap:Body>
</soap:Envelope>
Loading

0 comments on commit 1af9597

Please sign in to comment.