Skip to content

Commit aa55ea6

Browse files
authored
Add features to task queue functions (#1423)
* augment task context interface & pass in headers * update changelog * update docstrings & handle header edge cases
1 parent 3e1b5ca commit aa55ea6

File tree

4 files changed

+135
-11
lines changed

4 files changed

+135
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Add features to task queue functions. (#1423)

spec/common/providers/tasks.spec.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,10 @@ describe("onEnqueueHandler", () => {
7575
function mockEnqueueRequest(
7676
data: unknown,
7777
contentType = "application/json",
78-
context: { authorization?: string } = { authorization: "Bearer abc" }
78+
context: { authorization?: string } = { authorization: "Bearer abc" },
79+
headers: Record<string, string> = {}
7980
): ReturnType<typeof mockRequest> {
80-
return mockRequest(data, contentType, context);
81+
return mockRequest(data, contentType, context, headers);
8182
}
8283

8384
before(() => {
@@ -194,6 +195,50 @@ describe("onEnqueueHandler", () => {
194195
});
195196
});
196197

198+
it("should populate context with values from header", () => {
199+
const headers = {
200+
"x-cloudtasks-queuename": "x",
201+
"x-cloudtasks-taskname": "x",
202+
"x-cloudtasks-taskretrycount": "1",
203+
"x-cloudtasks-taskexecutioncount": "1",
204+
"x-cloudtasks-tasketa": "timestamp",
205+
"x-cloudtasks-taskpreviousresponse": "400",
206+
"x-cloudtasks-taskretryreason": "something broke",
207+
};
208+
const expectedContext = {
209+
queueName: "x",
210+
id: "x",
211+
retryCount: 1,
212+
executionCount: 1,
213+
scheduledTime: "timestamp",
214+
previousResponse: 400,
215+
retryReason: "something broke",
216+
};
217+
218+
const projectId = getApp().options.projectId;
219+
const idToken = generateIdToken(projectId);
220+
return runTaskTest({
221+
httpRequest: mockEnqueueRequest(
222+
{},
223+
"application/json",
224+
{ authorization: "Bearer " + idToken },
225+
headers
226+
),
227+
expectedData: {},
228+
taskFunction: (data, context) => {
229+
checkAuthContext(context, projectId, mocks.user_id);
230+
expect(context).to.include(expectedContext);
231+
return null;
232+
},
233+
taskFunction2: (request) => {
234+
checkAuthContext(request, projectId, mocks.user_id);
235+
expect(request).to.include(expectedContext);
236+
return null;
237+
},
238+
expectedStatus: 204,
239+
});
240+
});
241+
197242
it("should handle auth", async () => {
198243
const projectId = getApp().options.projectId;
199244
const idToken = generateIdToken(projectId);

spec/v1/providers/tasks.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@ describe("#onDispatch", () => {
161161
uid: "abc",
162162
token: "token" as any,
163163
},
164+
queueName: "fn",
165+
id: "task0",
166+
retryCount: 0,
167+
executionCount: 0,
168+
scheduledTime: "timestamp",
164169
};
165170
let done = false;
166171
const cf = taskQueue().onDispatch((d, c) => {

src/common/providers/tasks.ts

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,22 +88,72 @@ export interface TaskContext {
8888
* The result of decoding and verifying an ODIC token.
8989
*/
9090
auth?: AuthData;
91+
92+
/**
93+
* The name of the queue.
94+
* Populated via the `X-CloudTasks-QueueName` header.
95+
*/
96+
queueName: string;
97+
98+
/**
99+
* The "short" name of the task, or, if no name was specified at creation, a unique
100+
* system-generated id.
101+
* This is the my-task-id value in the complete task name, ie, task_name =
102+
* projects/my-project-id/locations/my-location/queues/my-queue-id/tasks/my-task-id.
103+
* Populated via the `X-CloudTasks-TaskName` header.
104+
*/
105+
id: string;
106+
107+
/**
108+
* The number of times this task has been retried.
109+
* For the first attempt, this value is 0. This number includes attempts where the task failed
110+
* due to 5XX error codes and never reached the execution phase.
111+
* Populated via the `X-CloudTasks-TaskRetryCount` header.
112+
*/
113+
retryCount: number;
114+
115+
/**
116+
* The total number of times that the task has received a response from the handler.
117+
* Since Cloud Tasks deletes the task once a successful response has been received, all
118+
* previous handler responses were failures. This number does not include failures due to 5XX
119+
* error codes.
120+
* Populated via the `X-CloudTasks-TaskExecutionCount` header.
121+
*/
122+
executionCount: number;
123+
124+
/**
125+
* The schedule time of the task, as an RFC 3339 string in UTC time zone.
126+
* Populated via the `X-CloudTasks-TaskETA` header, which uses seconds since January 1 1970.
127+
*/
128+
scheduledTime: string;
129+
130+
/**
131+
* The HTTP response code from the previous retry.
132+
* Populated via the `X-CloudTasks-TaskPreviousResponse` header
133+
*/
134+
previousResponse?: number;
135+
136+
/**
137+
* The reason for retrying the task.
138+
* Populated via the `X-CloudTasks-TaskRetryReason` header.
139+
*/
140+
retryReason?: string;
141+
142+
/**
143+
* Raw request headers.
144+
*/
145+
headers?: Record<string, string>;
91146
}
92147

93148
/**
94-
* The request used to call a Task Queue function.
149+
* The request used to call a task queue function.
95150
*/
96-
export interface Request<T = any> {
151+
export type Request<T = any> = TaskContext & {
97152
/**
98153
* The parameters used by a client when calling this function.
99154
*/
100155
data: T;
101-
102-
/**
103-
* The result of decoding and verifying an ODIC token.
104-
*/
105-
auth?: AuthData;
106-
}
156+
};
107157

108158
type v1TaskHandler = (data: any, context: TaskContext) => void | Promise<void>;
109159
type v2TaskHandler<Req> = (request: Request<Req>) => void | Promise<void>;
@@ -119,7 +169,30 @@ export function onDispatchHandler<Req = any>(
119169
throw new https.HttpsError("invalid-argument", "Bad Request");
120170
}
121171

122-
const context: TaskContext = {};
172+
const headers: Record<string, string> = {};
173+
for (const [key, value] of Object.entries(req.headers)) {
174+
if (!Array.isArray(value)) {
175+
headers[key] = value;
176+
}
177+
}
178+
179+
const context: TaskContext = {
180+
queueName: req.header("X-CloudTasks-QueueName"),
181+
id: req.header("X-CloudTasks-TaskName"),
182+
retryCount: req.header("X-CloudTasks-TaskRetryCount")
183+
? Number(req.header("X-CloudTasks-TaskRetryCount"))
184+
: undefined,
185+
executionCount: req.header("X-CloudTasks-TaskExecutionCount")
186+
? Number(req.header("X-CloudTasks-TaskExecutionCount"))
187+
: undefined,
188+
scheduledTime: req.header("X-CloudTasks-TaskETA"),
189+
previousResponse: req.header("X-CloudTasks-TaskPreviousResponse")
190+
? Number(req.header("X-CloudTasks-TaskPreviousResponse"))
191+
: undefined,
192+
retryReason: req.header("X-CloudTasks-TaskRetryReason"),
193+
headers,
194+
};
195+
123196
if (!process.env.FUNCTIONS_EMULATOR) {
124197
const authHeader = req.header("Authorization") || "";
125198
const token = authHeader.match(/^Bearer (.*)$/)?.[1];

0 commit comments

Comments
 (0)