Skip to content
Merged
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
2 changes: 1 addition & 1 deletion components/apify/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ The Apify API unleashes the power to automate web scraping, process data, and or

**Real Estate Listings Aggregator**

- Create a workflow where Apify actors periodically fetch the latest real estate listings from multiple websites. Process and normalize the data with Pipedream's code steps, then automatically update a Google Sheet that serves as a central repository for all listings, keeping potential buyers informed with the latest options.
- Create a workflow where Apify Actors periodically fetch the latest real estate listings from multiple websites. Process and normalize the data with Pipedream's code steps, then automatically update a Google Sheet that serves as a central repository for all listings, keeping potential buyers informed with the latest options.
43 changes: 31 additions & 12 deletions components/apify/actions/run-actor/run-actor.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default {
actorSource: {
type: "string",
label: "Search Actors from",
description: "Where to search for Actors. Valid options are Store and Recently used Actors.",
description: "Where to search for Actors. Choose **Apify Store Actors** to browse the public [Apify Store](https://apify.com/store), or **Recently used Actors** to pick from Actors you've run before.",
options: [
{
label: "Apify Store Actors",
Expand Down Expand Up @@ -61,10 +61,11 @@ export default {
reloadProps: true,
optional: true,
},
runAsynchronously: {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: About breaking change, can we just keep the field, but just make it hidden somehow?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hiding the old field would only preserve behavior by making the new "Wait for Finish" toggle silently inert for existing steps, so I think that clean rename is the better trade.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, it that case we need to do breaking change, can you check what this means, maybe we would need to bump version?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, we resolve the breaking change via version bump, 0.0.7 -> 0.0.8 (which is already covered)

existing deployed steps stay pinned to their old version, so they keep working fine

// Renamed from `runAsynchronously` for clarity.
waitForFinish: {
type: "boolean",
label: "Run Asynchronously",
description: "Set to `true` to run the Actor asynchronously",
label: "Wait for Finish",
description: "If `true` (default), the step waits for the Actor run to finish and returns its output. If `false`, the step starts the run and returns immediately with the run details, without waiting.",
reloadProps: true,
default: true,
},
Expand Down Expand Up @@ -162,7 +163,7 @@ export default {
async getBuildOrThrow(actorId, buildTag) {
const build = await this.apify.getBuild(actorId, buildTag);
if (!build) {
throw new Error(`No build found for actor ${actorId}`);
throw new Error(`No build found for Actor ${actorId}`);
}
return build;
},
Expand All @@ -180,14 +181,14 @@ export default {
: build.inputSchema;
} catch (err) {
throw new Error(
`Failed to parse inputSchema for actor ${actorId}: ${err.message}`,
`Failed to parse inputSchema for Actor ${actorId}: ${err.message}`,
);
}
}

// Case 3: no schema at all (e.g. apify/hello-world)
const noSchemaError = new Error(
`No input schema found for actor ${actorId}. Has it been built successfully?`,
`No input schema found for Actor ${actorId}. Has it been built successfully?`,
);
noSchemaError.noInputSchema = true;
throw noSchemaError;
Expand Down Expand Up @@ -273,6 +274,24 @@ export default {
},
async additionalProps() {
const props = {};

// Show a hint if user set actorId to sentinel value "".
// Displayed only when user has no recently-used Actors.
if (this.actorId === "") {
return {
actorHint: {
type: "alert",
alertType: "info",
content: "No Actor selected. Set **Search Actors from** to **Apify Store Actors** above, then choose an Actor to run.",
},
};
}

// Not picked yet (undefined): show nothing rather than erroring.
if (!this.actorId) {
return props;
}

let memoryLimits = getMemoryLimits();
try {
const build = await this.getBuildOrThrow(this.actorId, this.buildTag);
Expand Down Expand Up @@ -345,7 +364,7 @@ export default {
// Actor memory dropdown, filtered by per-actor limits.
props.memory = buildMemoryProp(memoryLimits);

if (!this.runAsynchronously) {
if (this.waitForFinish) {
props.outputRecordKey = {
type: "string",
label: "Output Record Key",
Expand All @@ -372,7 +391,7 @@ export default {
apify,
actorId,
buildTag,
runAsynchronously,
waitForFinish,
outputRecordKey,
timeout,
memory,
Expand All @@ -395,7 +414,7 @@ export default {

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.`,
`Actor "${actorDetails.title || actorDetails.name}" has no builds yet and can't be run until it's built. Open the Actor in Apify Console and build it (Source → Code → Build), or trigger a build via the Apify CLI/API, then run this step again.`,
);
}

Expand Down Expand Up @@ -460,8 +479,8 @@ export default {

let run;

if (runAsynchronously) {
// async run
if (!waitForFinish) {
// async run — start and return immediately without waiting
run = await apify.runActorAsynchronously({
actorId,
data: input,
Expand Down
11 changes: 10 additions & 1 deletion components/apify/apify.app.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default {
actorId: {
type: "string",
label: "Actor",
description: "Select the Actor, enter the Actor ID, or use a tilde-separated combination of the owner's username and the Actor name.",
description: "Select an Actor from the dropdown, enter an Actor ID, or use a tilde-separated owner/name identifier (e.g. `apify~web-scraper`). If the list is empty, switch \"Search Actors from\" to **Apify Store Actors**.",
async options({
page, actorSource,
}) {
Expand Down Expand Up @@ -168,6 +168,15 @@ export default {
limit: LIMIT,
});

if (page === 0 && items.length === 0 && actorSource !== "store") {
return [
{
label: "No recent Actors, switch to \"Apify Store Actors\" above",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important: getActorOptions is shared — new-finished-actor-run-instant.mjs:42 calls it too, so this hint row appears in the trigger's Actor dropdown as well, but only run-actor.mjs guards on actorId === "". Picking it there yields getCondition() -> { actorId: "" } and a webhook on an empty Actor ID; the sentinel needs handling where it's consumed, or it shouldn't live in the shared method.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled it in base.mjs activate(), which now throws a ConfigurationError before creating the webhook if the condition target is empty. Thanks

value: "",
},
];
}

return items.map((actor) => ({
label: this.formatActorOrTaskLabel(actor),
value: actor.id,
Expand Down
9 changes: 9 additions & 0 deletions components/apify/sources/common/base.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createHash } from "crypto";
import {
WEBHOOK_EVENT_TYPE_GROUPS, WEBHOOK_EVENT_TYPES,
} from "@apify/consts";
import { ConfigurationError } from "@pipedream/platform";
import apify from "../../apify.app.mjs";

export default {
Expand Down Expand Up @@ -40,6 +41,14 @@ export default {
},
hooks: {
async activate() {
// Block empty or unset condition targets.
if (Object.values(this.getCondition()).some((value) => !value)) {
Comment thread
daveomri marked this conversation as resolved.
throw new ConfigurationError(
this.getEmptyConditionMessage?.()
?? "No target selected. Select an Actor or Task before deploying.",
);
}

// Whitelist terminal states only; fall back to all when none are valid.
const selected = (this.eventTypes ?? [])
.filter((type) => WEBHOOK_EVENT_TYPE_GROUPS.ACTOR_RUN_TERMINAL.includes(type));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default {
actorSource: {
type: "string",
label: "Search Actors from",
description: "Where to search for Actors. Valid options are Store and Recently used Actors.",
description: "Where to search for Actors. Choose **Apify Store Actors** to browse the public [Apify Store](https://apify.com/store), or **Recently used Actors** to pick from Actors you've run before.",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important: this component's content changed but version is still 0.0.8 (same as develop), while the apify_oauth mirror was bumped to 0.0.4 — the description tweak won't ship for the non-OAuth source. Needs a bump too.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, will do in next commit

options: [
{
label: "Apify Store Actors",
Expand Down Expand Up @@ -56,6 +56,9 @@ export default {
actorId: this.actorId,
};
},
getEmptyConditionMessage() {
return "No Actor selected. If the list was empty, set \"Search Actors from\" to \"Apify Store Actors\" and pick an Actor before deploying.";
},
getSummary(body) {
return `A new Actor run ${body.eventData.actorRunId} has finished`;
},
Expand Down