Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions components/apify/actions/get-dataset-items/get-dataset-items.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ export default {
"flatten",
],
},
maxResults: {
limit: {
propDefinition: [
apify,
"maxResults",
"limit",
],
},
},
Expand All @@ -60,20 +60,20 @@ export default {
let total;

do {
const items = await this.apify.listDatasetItems({
const { items } = await this.apify.listDatasetItems({
datasetId: this.datasetId,
params,
});
results.push(...items);
if (results.length >= this.maxResults) {
if (results.length >= this.limit) {
break;
}
total = items?.length;
params.offset += LIMIT;
} while (total);

if (results.length > this.maxResults) {
results.length = this.maxResults;
if (results.length > this.limit) {
results.length = this.limit;
}

if (results.length > 0) {
Expand Down
248 changes: 165 additions & 83 deletions components/apify/actions/run-actor/run-actor.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default {
actorSource: c.actorSource,
}),
],
reloadProps: true,
},
buildTag: {
propDefinition: [
Expand All @@ -57,13 +58,13 @@ export default {
},
timeout: {
type: "string",
label: "Timeout",
label: "Timeout (seconds)",
description: "Optional timeout for the run, in seconds. By default, the run uses a timeout specified in the default run configuration for the Actor.",
optional: true,
},
memory: {
type: "string",
label: "Memory",
label: "Memory (MB)",
description: "Memory limit for the run, in megabytes. The amount of memory can be set to a power of 2 with a minimum of 128. By default, the run uses a memory limit specified in the default run configuration for the Actor.",
optional: true,
},
Expand All @@ -81,7 +82,7 @@ export default {
},
webhook: {
type: "string",
label: "Webhook",
label: "Webhook URL",
description: "Specifies optional webhook associated with the Actor run, which can be used to receive a notification e.g. when the Actor finished or failed.",
optional: true,
reloadProps: true,
Expand All @@ -99,73 +100,83 @@ export default {
: "string[]";
},
async getSchema(actorId, buildId) {
const { data: { inputSchema } } = await this.apify.getBuild(actorId, buildId);
return JSON.parse(inputSchema);
const build = await this.apify.getBuild(actorId, buildId);
if (!build) {
throw new Error(`No build found for actor ${actorId}`);
}

// Case 1: schema is already an object
if (build.actorDefinition && build.actorDefinition.input) {
return build.actorDefinition.input;
}

// Case 2: schema is a string in inputSchema
if (build.inputSchema) {
try {
return typeof build.inputSchema === "string"
? JSON.parse(build.inputSchema)
: build.inputSchema;
} catch (err) {
throw new Error(
`Failed to parse inputSchema for actor ${actorId}: ${err.message}`,
);
}
}

// Case 3: no schema at all
throw new Error(
`No input schema found for actor ${actorId}. Has it been built successfully?`,
);
},
async prepareData(data) {
const newData = {};
const { properties } = await this.apify.getSchema(this.actorId, this.buildId);

const { properties } = await this.getSchema(this.actorId, this.buildId);
for (const [
key,
value,
] of Object.entries(data)) {
let editor;

if (properties[key]) {
editor = properties[key].editor;
} else {
console.warn(`Property "${key}" is not defined in the schema`);
editor = "hidden"; // This will prevent it from showing up
}

newData[key] = (Array.isArray(value))
let editor = properties[key]?.editor || "hidden";
newData[key] = Array.isArray(value)
? value.map((item) => this.setValue(editor, item))
: value;
}
return newData;
},
prepareOptions(value) {
let options = [];
if (value.enum && value.enumTitles) {
for (const [
index,
val,
] of value.enum.entries()) {
if (val) {
options.push({
value: val,
label: value.enumTitles[index],
});
}
}
return value.enum.map((val, i) => ({
value: val,
label: value.enumTitles[i],
}));
}
return options.length
? options
: undefined;
},
setValue(editor, item) {
switch (editor) {
case "requestListSources" : return {
url: item,
};
case "pseudoUrls" : return {
purl: item,
};
case "globs" : return {
glob: item,
};
default: return item;
case "requestListSources":
return {
url: item,
};
case "pseudoUrls":
return {
purl: item,
};
case "globs":
return {
glob: item,
};
default:
return item;
}
},
},
async additionalProps() {
const props = {};

try {
const schema = await this.getSchema(this.actorId, this.buildId);
const {
properties, required: requiredProps = [],
} = await this.getSchema(this.actorId, this.buildId);
} = schema;

for (const [
key,
Expand All @@ -179,30 +190,36 @@ export default {
description: value.description,
optional: !requiredProps.includes(key),
};

const options = this.prepareOptions(value);
if (options) props[key].options = options;

if (value.default) {
props[key].description += ` Default: \`${JSON.stringify(value.default)}\``;
if (props[key].type !== "object") { // default values don't work properly for object props
if (props[key].type !== "object") {
props[key].default = value.default;
}
}
}
} catch {
} catch (e) {
props.properties = {
type: "object",
label: "Properties",
description: "Properties to set for this actor",
description: e.message || "Schema not available, showing fallback.",
};
}
if (this.runAsynchronously) {

if (!this.runAsynchronously) {
props.outputRecordKey = {
type: "string",
label: "Output Record Key",
description: "Key of the record from run's default key-value store to be returned in the response. By default, it is OUTPUT.",
description:
"Key of the record from the run's default key-value store to return. Default is `OUTPUT`.",
optional: true,
default: "OUTPUT",
};
}

if (this.webhook) {
props.eventTypes = {
type: "string[]",
Expand All @@ -211,19 +228,14 @@ export default {
options: Object.values(WEBHOOK_EVENT_TYPES),
};
}

return props;
},
async run({ $ }) {
const {
getType,
getSchema,
prepareOptions,
setValue,
prepareData,
apify,
actorId,
buildTag,
properties,
runAsynchronously,
outputRecordKey,
timeout,
Expand All @@ -235,36 +247,106 @@ export default {
...data
} = this;

const fn = runAsynchronously
? apify.runActorAsynchronously
: apify.runActor;

const response = await fn({
// --- Validation step ---
const actorDetails = await apify.getActor({
actorId,
data: properties
? parseObject(properties)
: await prepareData(data),
params: {
outputRecordKey,
timeout,
memory,
maxItems,
maxTotalChargeUsd,
build: buildTag,
webhooks: webhook
? btoa(JSON.stringify([
{
eventTypes,
requestUrl: webhook,
},
]))
: undefined,
},
});
const summary = this.runAsynchronously
? `Successfully started Actor run with ID: ${response.data.id}`
: `Successfully ran Actor with ID: ${this.actorId}`;
$.export("$summary", `${summary}`);
return response;

if (!actorDetails) {
throw new Error(`Actor with ID "${actorId}" does not exist.`);
}

if (!actorDetails.stats?.totalBuilds || actorDetails.stats.totalBuilds === 0) {
throw new Error(
`Actor "${actorDetails.title || actorDetails.name}" has no builds. Please build it first before running.`,
);
}

if (buildTag) {
const taggedBuilds = actorDetails.taggedBuilds || {};
if (!taggedBuilds[buildTag]) {
throw new Error(
`Build with tag "${buildTag}" was not found for actor "${actorDetails.title || actorDetails.name}".`,
);
}
}

// Prepare input
const input = this.properties
? parseObject(this.properties)
: data;

// Build params safely
const params = {
...(buildTag && {
build: buildTag,
}),
...(timeout && {
timeoutSecs: Number(timeout),
}),
...(memory && {
memoryMbytes: Number(memory),
}),
...(maxItems && {
maxItems: Number(maxItems),
}),
...(maxTotalChargeUsd && {
maxTotalCostUsd: Number(maxTotalChargeUsd),
}),
...(webhook && {
webhooks: [
{
eventTypes,
requestUrl: webhook,
},
],
}),
};

let run;

if (runAsynchronously) {
// async run
run = await apify.runActorAsynchronously({
actorId,
data: input,
params,
});

$.export("$summary", `Successfully started Actor run with ID: ${run.id}`);
return run;
} else {
// sync run
run = await apify.runActor({
actorId,
data: input,
params,
});

// Fetch OUTPUT record manually
let output;
if (run.defaultKeyValueStoreId) {
const record = await apify
._client()
.keyValueStore(run.defaultKeyValueStoreId)
.getRecord(outputRecordKey);

output = record?.value;
}

$.export(
"$summary",
`The ran of an Actor with ID: ${actorId} has finished with status "${run.status}", retrieved ${
output
? "OUTPUT"
: "no output"
}`,
);

return {
run,
output,
};
}
},
};
Loading