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
66 changes: 65 additions & 1 deletion src/resource_clients/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,52 @@ export class TaskClient extends ResourceClient {
return this._update(newFields);
}

/**
* Publishes the task on its public landing page.
*
* The task's Actor must be public and the task must have its public display configuration
* (`publicConfig`) set up via {@apilink TaskClient.update}. Requires write permission to
* both the task and its Actor.
*
* Publishing an already published task is a no-op that returns the current state, so this
* call is safe to retry (unlike {@apilink TaskClient.unpublish}).
*
* @returns The task object.
* @see https://docs.apify.com/api/v2/actor-task-publish-post
*/
async publish(): Promise<Task> {
const response = await this.httpClient.call({
url: this._url('publish'),
method: 'POST',
params: this._params(),
});

return cast(parseDateFields(pluckData(response.data)));
}

/**
* Unpublishes the task from its public landing page.
*
* The public display configuration (`publicConfig`) is preserved, so the task can be
* published again without re-entering it. Requires write permission to both the task
* and its Actor.
*
* Unlike {@apilink TaskClient.publish}, this call is not idempotent: unpublishing a task
* that is not currently published throws an `ApifyApiError` (`cannot-unpublish-actor-task`).
*
* @returns The task object.
Comment thread
katzino marked this conversation as resolved.
* @see https://docs.apify.com/api/v2/actor-task-unpublish-post
*/
async unpublish(): Promise<Task> {
const response = await this.httpClient.call({
url: this._url('unpublish'),
method: 'POST',
params: this._params(),
});

return cast(parseDateFields(pluckData(response.data)));
}

/**
* Deletes the Task.
*
Expand Down Expand Up @@ -293,6 +339,24 @@ export interface Task {
options?: TaskOptions;
input?: Dictionary | Dictionary[];
actorStandby?: Partial<ActorStandby>;
publicConfig?: TaskPublicConfig | null;
}

/**
* Public-facing display configuration of a task's public landing page.
*
* The task is published when `publishedAt` is set and unpublished when it is `null`. The
* `publishedAt` field is read-only - use {@apilink TaskClient.publish} and
* {@apilink TaskClient.unpublish} to change the publication state.
*/
export interface TaskPublicConfig {
publishedAt: Date | null;
seoTitle?: string | null;
seoDescription?: string | null;
categorization?: string | null;
inputSchemaFields?: string[] | null;
datasetName?: string | null;
datasetView?: string | null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Important: You're missing highlightedFields?: string[] | null as far as I remember

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It's stale no? I don't think it's used anywhere and if we decide to use it, the I would add it to public facing API, wdyt?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, good catch I guess we kept duplicated field to inputSchemaFields. No idea how that happened, we should remove it then.

}

/**
Expand All @@ -317,7 +381,7 @@ export interface TaskOptions {
*/
export type TaskUpdateData = Partial<

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note: I commented on it at https://github.com/apify/apify-core/pull/29623#discussion_r3682214152 - TaskCreateData extends this type that means create method takes publicConfig but the endpoint in apify-core currently won't set it. Should be addressed there, not a blocker in this PR.

Pick<Task, 'name' | 'title' | 'description' | 'options' | 'input' | 'actorStandby'>
>;
> & { publicConfig?: Omit<TaskPublicConfig, 'publishedAt'> };

/**
* Options for filtering the last run of a Task.
Expand Down
2 changes: 2 additions & 0 deletions test/mock_server/routes/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const ROUTES: MockServerRoute[] = [
{ id: 'update-task', method: 'PUT', path: '/:taskId' },
{ id: 'delete-task', method: 'DELETE', path: '/:taskId' },
{ id: 'get-task', method: 'GET', path: '/:taskId' },
{ id: 'publish-task', method: 'POST', path: '/:taskId/publish' },
{ id: 'unpublish-task', method: 'POST', path: '/:taskId/unpublish' },
{ id: 'list-runs', method: 'GET', path: '/:taskId/runs' },
{ id: 'run-task', method: 'POST', path: '/:taskId/runs', type: 'responseJsonMock' },
{ id: 'list-webhooks', method: 'GET', path: '/:taskId/webhooks' },
Expand Down
24 changes: 24 additions & 0 deletions test/tasks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,30 @@ describe('Task methods', () => {
validateRequest({ query: {}, params: { taskId } });
});

test('publish() works', async () => {
const taskId = 'some-task-id';

const res = await client.task(taskId).publish();
expect(res.id).toEqual('publish-task');
validateRequest({ query: {}, params: { taskId } });

const browserRes = await page.evaluate((id) => client.task(id).publish(), taskId);
expect(browserRes).toEqual(res);
validateRequest({ query: {}, params: { taskId } });
});

test('unpublish() works', async () => {

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.

is this dependent on the previous test? Ideally, tests should work in isolation, if they are dependent, maybe just merge them to a single test case

const taskId = 'some-task-id';

const res = await client.task(taskId).unpublish();
expect(res.id).toEqual('unpublish-task');
validateRequest({ query: {}, params: { taskId } });

const browserRes = await page.evaluate((id) => client.task(id).unpublish(), taskId);
expect(browserRes).toEqual(res);
validateRequest({ query: {}, params: { taskId } });
});

test('get() works', async () => {
const taskId = 'some-id';

Expand Down
Loading