Update nodes/WizPixelFlow/WizPixelFlow.node.ts
feat: add job_type field, align jobs.php URL, expand terminal statuses v0.1.1
This commit is contained in:
@@ -32,6 +32,8 @@ const OUTPUT_FORMATS = [
|
|||||||
{ name: 'Twitter Square 1080x1080', value: 'twitter_square_1080x1080' },
|
{ name: 'Twitter Square 1080x1080', value: 'twitter_square_1080x1080' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const TERMINAL_STATUSES = ['done', 'failed', 'complete', 'error'];
|
||||||
|
|
||||||
export class WizPixelFlow implements INodeType {
|
export class WizPixelFlow implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'WizPixel Flow',
|
displayName: 'WizPixel Flow',
|
||||||
@@ -58,16 +60,91 @@ export class WizPixelFlow implements INodeType {
|
|||||||
],
|
],
|
||||||
default: 'render',
|
default: 'render',
|
||||||
},
|
},
|
||||||
{ displayName: 'Template ID', name: 'templateId', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['render'] } }, description: 'The ID of the Flow template to render' },
|
{
|
||||||
{ displayName: 'Output Formats', name: 'outputFormats', type: 'multiOptions', options: OUTPUT_FORMATS, default: ['leaderboard_728x90', 'medium_rectangle_300x250'], required: true, displayOptions: { show: { operation: ['render'] } } },
|
displayName: 'Template ID',
|
||||||
{ displayName: 'Locales', name: 'locales', type: 'string', default: 'en', displayOptions: { show: { operation: ['render'] } }, description: 'Comma-separated locale codes, e.g. en,it,fr' },
|
name: 'templateId',
|
||||||
{ displayName: 'Variables (JSON)', name: 'variables', type: 'json', default: '{}', displayOptions: { show: { operation: ['render'] } }, description: 'Key/value pairs for {{variable}} substitution' },
|
type: 'string',
|
||||||
{ displayName: 'Include Video (MP4)', name: 'includeVideo', type: 'boolean', default: false, displayOptions: { show: { operation: ['render'] } } },
|
default: '',
|
||||||
{ displayName: 'Wait for Completion', name: 'waitForCompletion', type: 'boolean', default: true, displayOptions: { show: { operation: ['render'] } }, description: 'Poll until done and return ZIP download URL' },
|
required: true,
|
||||||
{ displayName: 'Poll Interval (seconds)', name: 'pollInterval', type: 'number', default: 5, displayOptions: { show: { operation: ['render'], waitForCompletion: [true] } } },
|
displayOptions: { show: { operation: ['render'] } },
|
||||||
{ displayName: 'Max Wait (seconds)', name: 'maxWait', type: 'number', default: 300, displayOptions: { show: { operation: ['render'], waitForCompletion: [true] } } },
|
description: 'UID of the Flow template to render',
|
||||||
{ displayName: 'Job ID', name: 'jobId', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['getJob'] } } },
|
},
|
||||||
{ displayName: 'Limit', name: 'limit', type: 'number', default: 50, typeOptions: { minValue: 1, maxValue: 200 }, displayOptions: { show: { operation: ['listTemplates'] } } },
|
{
|
||||||
|
displayName: 'Job Type',
|
||||||
|
name: 'jobType',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{ name: 'Banner (HTML5)', value: 'banner' },
|
||||||
|
{ name: 'Video (MP4)', value: 'video' },
|
||||||
|
],
|
||||||
|
default: 'banner',
|
||||||
|
required: true,
|
||||||
|
displayOptions: { show: { operation: ['render'] } },
|
||||||
|
description: 'Render HTML5 banners or MP4 videos (different credit pools)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Output Formats',
|
||||||
|
name: 'outputFormats',
|
||||||
|
type: 'multiOptions',
|
||||||
|
options: OUTPUT_FORMATS,
|
||||||
|
default: ['leaderboard_728x90', 'medium_rectangle_300x250'],
|
||||||
|
required: true,
|
||||||
|
displayOptions: { show: { operation: ['render'] } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Locales',
|
||||||
|
name: 'locales',
|
||||||
|
type: 'string',
|
||||||
|
default: 'en',
|
||||||
|
displayOptions: { show: { operation: ['render'] } },
|
||||||
|
description: 'Comma-separated locale codes, e.g. en,it,fr',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Variables (JSON)',
|
||||||
|
name: 'variables',
|
||||||
|
type: 'json',
|
||||||
|
default: '{}',
|
||||||
|
displayOptions: { show: { operation: ['render'] } },
|
||||||
|
description: 'Key/value pairs for {{variable}} substitution',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Wait for Completion',
|
||||||
|
name: 'waitForCompletion',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
displayOptions: { show: { operation: ['render'] } },
|
||||||
|
description: 'Poll until done and return ZIP download URL',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Poll Interval (seconds)',
|
||||||
|
name: 'pollInterval',
|
||||||
|
type: 'number',
|
||||||
|
default: 5,
|
||||||
|
displayOptions: { show: { operation: ['render'], waitForCompletion: [true] } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Max Wait (seconds)',
|
||||||
|
name: 'maxWait',
|
||||||
|
type: 'number',
|
||||||
|
default: 300,
|
||||||
|
displayOptions: { show: { operation: ['render'], waitForCompletion: [true] } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Job ID',
|
||||||
|
name: 'jobId',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: { show: { operation: ['getJob'] } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
type: 'number',
|
||||||
|
default: 50,
|
||||||
|
typeOptions: { minValue: 1, maxValue: 200 },
|
||||||
|
displayOptions: { show: { operation: ['listTemplates'] } },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -76,6 +153,7 @@ export class WizPixelFlow implements INodeType {
|
|||||||
const baseUrl = (credentials.baseUrl as string).replace(/\/$/, '');
|
const baseUrl = (credentials.baseUrl as string).replace(/\/$/, '');
|
||||||
const apiKey = credentials.apiKey as string;
|
const apiKey = credentials.apiKey as string;
|
||||||
const headers = { 'X-WPF-API-Key': apiKey, 'Content-Type': 'application/json' };
|
const headers = { 'X-WPF-API-Key': apiKey, 'Content-Type': 'application/json' };
|
||||||
|
|
||||||
const items = this.getInputData();
|
const items = this.getInputData();
|
||||||
const returnData: INodeExecutionData[] = [];
|
const returnData: INodeExecutionData[] = [];
|
||||||
|
|
||||||
@@ -84,46 +162,98 @@ export class WizPixelFlow implements INodeType {
|
|||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
const operation = this.getNodeParameter('operation', i) as string;
|
const operation = this.getNodeParameter('operation', i) as string;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (operation === 'render') {
|
if (operation === 'render') {
|
||||||
const templateId = this.getNodeParameter('templateId', i) as string;
|
const templateId = this.getNodeParameter('templateId', i) as string;
|
||||||
|
const jobType = this.getNodeParameter('jobType', i) as string;
|
||||||
const outputFormats = this.getNodeParameter('outputFormats', i) as string[];
|
const outputFormats = this.getNodeParameter('outputFormats', i) as string[];
|
||||||
const localesRaw = this.getNodeParameter('locales', i) as string;
|
const localesRaw = this.getNodeParameter('locales', i) as string;
|
||||||
const variablesRaw = this.getNodeParameter('variables', i) as string;
|
const variablesRaw = this.getNodeParameter('variables', i) as string;
|
||||||
const includeVideo = this.getNodeParameter('includeVideo', i) as boolean;
|
|
||||||
const waitForCompletion = this.getNodeParameter('waitForCompletion', i) as boolean;
|
const waitForCompletion = this.getNodeParameter('waitForCompletion', i) as boolean;
|
||||||
const pollInterval = this.getNodeParameter('pollInterval', i) as number;
|
const pollInterval = this.getNodeParameter('pollInterval', i) as number;
|
||||||
const maxWait = this.getNodeParameter('maxWait', i) as number;
|
const maxWait = this.getNodeParameter('maxWait', i) as number;
|
||||||
|
|
||||||
const locales = localesRaw.split(',').map((l) => l.trim()).filter(Boolean);
|
const locales = localesRaw.split(',').map((l) => l.trim()).filter(Boolean);
|
||||||
const variables = typeof variablesRaw === 'string' ? JSON.parse(variablesRaw) : variablesRaw;
|
const variables = typeof variablesRaw === 'string' ? JSON.parse(variablesRaw) : variablesRaw;
|
||||||
const body = { template_id: templateId, formats: outputFormats, locales, variables, include_video: includeVideo };
|
|
||||||
const submitted = parse(await this.helpers.httpRequest({ method: 'POST', url: `${baseUrl}/render`, headers, body: JSON.stringify(body) }));
|
const body = {
|
||||||
|
template_id: templateId,
|
||||||
|
job_type: jobType,
|
||||||
|
formats: outputFormats,
|
||||||
|
locales,
|
||||||
|
variables,
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitted = parse(
|
||||||
|
await this.helpers.httpRequest({
|
||||||
|
method: 'POST',
|
||||||
|
url: `${baseUrl}/render`,
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!waitForCompletion) {
|
||||||
|
returnData.push({ json: submitted });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const jobId = submitted.job_id as string;
|
const jobId = submitted.job_id as string;
|
||||||
if (!waitForCompletion) { returnData.push({ json: submitted }); continue; }
|
|
||||||
const deadline = Date.now() + maxWait * 1000;
|
const deadline = Date.now() + maxWait * 1000;
|
||||||
let jobData: IDataObject = {};
|
let jobData: IDataObject = {};
|
||||||
|
|
||||||
while (Date.now() < deadline) {
|
while (Date.now() < deadline) {
|
||||||
await new Promise((r) => setTimeout(r, pollInterval * 1000));
|
await new Promise((r) => setTimeout(r, pollInterval * 1000));
|
||||||
jobData = parse(await this.helpers.httpRequest({ method: 'GET', url: `${baseUrl}/jobs/${jobId}`, headers }));
|
jobData = parse(
|
||||||
|
await this.helpers.httpRequest({
|
||||||
|
method: 'GET',
|
||||||
|
url: `${baseUrl}/jobs.php?id=${jobId}`,
|
||||||
|
headers,
|
||||||
|
}),
|
||||||
|
);
|
||||||
const job = jobData.job as IDataObject | undefined;
|
const job = jobData.job as IDataObject | undefined;
|
||||||
if (job?.status === 'done' || job?.status === 'failed') break;
|
if (job?.status && TERMINAL_STATUSES.includes(job.status as string)) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
returnData.push({ json: jobData });
|
returnData.push({ json: jobData });
|
||||||
|
|
||||||
} else if (operation === 'getJob') {
|
} else if (operation === 'getJob') {
|
||||||
const jobId = this.getNodeParameter('jobId', i) as string;
|
const jobId = this.getNodeParameter('jobId', i) as string;
|
||||||
returnData.push({ json: parse(await this.helpers.httpRequest({ method: 'GET', url: `${baseUrl}/jobs/${jobId}`, headers })) });
|
returnData.push({
|
||||||
|
json: parse(
|
||||||
|
await this.helpers.httpRequest({
|
||||||
|
method: 'GET',
|
||||||
|
url: `${baseUrl}/jobs.php?id=${jobId}`,
|
||||||
|
headers,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
} else if (operation === 'listTemplates') {
|
} else if (operation === 'listTemplates') {
|
||||||
const limit = this.getNodeParameter('limit', i) as number;
|
const limit = this.getNodeParameter('limit', i) as number;
|
||||||
returnData.push({ json: parse(await this.helpers.httpRequest({ method: 'GET', url: `${baseUrl}/templates?limit=${limit}`, headers })) });
|
returnData.push({
|
||||||
|
json: parse(
|
||||||
|
await this.helpers.httpRequest({
|
||||||
|
method: 'GET',
|
||||||
|
url: `${baseUrl}/templates?limit=${limit}`,
|
||||||
|
headers,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnData.push({ json: { error: (error as Error).message } as IDataObject, pairedItem: i });
|
returnData.push({
|
||||||
|
json: { error: (error as Error).message } as IDataObject,
|
||||||
|
pairedItem: i,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new NodeOperationError(this.getNode(), error as Error, { itemIndex: i });
|
throw new NodeOperationError(this.getNode(), error as Error, { itemIndex: i });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [returnData];
|
return [returnData];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user