Skip to content

Commit

Permalink
Add v2 Schedule Triggers (#1177)
Browse files Browse the repository at this point in the history
Adds schedule triggers to the v2 namespace.

- Replaces pub/sub with an http underlying function
- Updated syntax to match with the other v2 triggers

This change does not add the `invoker` property to the container contract.

```
export const sch = v2.scheduler.onSchedule("* * * * *", () => {});

export const sch = v2.scheduler.onSchedule(
  {
    schedule: "* * * * *",
    timeZone: "utc",
  },
  () => {}
);
```
  • Loading branch information
colerogers committed Aug 25, 2022
1 parent c0e0361 commit eed7d59
Show file tree
Hide file tree
Showing 11 changed files with 982 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
- Fixes a bug that disallowed setting customClaims and/or sessionClaims in blocking functions (#1199).
- Add v2 Schedule Triggers (#1177).
28 changes: 28 additions & 0 deletions integration_test/functions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,32 @@ async function callScheduleTrigger(
return;
}

async function callV2ScheduleTrigger(
functionName: string,
region: string,
accessToken: string
) {
const response = await fetch(
`https://cloudscheduler.googleapis.com/v1/projects/${firebaseConfig.projectId}/locations/us-central1/jobs/firebase-schedule-${functionName}-${region}:run`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
}
);
if (!response.ok) {
throw new Error(`Failed request with status ${response.status}!`);
}
const data = await response.text();
functions.logger.log(
`Successfully scheduled v2 function ${functionName}`,
data
);
return;
}

async function updateRemoteConfig(
testId: string,
accessToken: string
Expand Down Expand Up @@ -171,6 +197,8 @@ function v2Tests(testId: string, accessToken: string): Array<Promise<void>> {
return [
// Invoke a callable HTTPS trigger.
callV2HttpsTrigger('v2-callabletests', { foo: 'bar', testId }, accessToken),
// Invoke a scheduled trigger.
callV2ScheduleTrigger('v2-schedule', 'us-central1', accessToken),
];
}

Expand Down
1 change: 1 addition & 0 deletions integration_test/functions/src/v2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ import { REGION } from '../region';
setGlobalOptions({ region: REGION });

export * from './https-tests';
export * from './scheduled-tests';
23 changes: 23 additions & 0 deletions integration_test/functions/src/v2/scheduled-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as admin from 'firebase-admin';
import { onSchedule } from 'firebase-functions/v2/scheduler';
import { REGION } from '../region';
import { success, TestSuite } from '../testing';

export const schedule: any = onSchedule(
{
schedule: 'every 10 hours',
region: REGION,
},
async (event) => {
const db = admin.database();
const snap = await db
.ref('testRuns')
.orderByChild('timestamp')
.limitToLast(1)
.once('value');
const testId = Object.keys(snap.val())[0];
return new TestSuite('scheduler scheduleOnRun')
.it('should trigger when the scheduler fires', () => success())
.run(testId, null);
}
);
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@
"./v2/alerts/crashlytics": "./lib/v2/providers/alerts/crashlytics.js",
"./v2/eventarc": "./lib/v2/providers/eventarc.js",
"./v2/identity": "./lib/v2/providers/identity.js",
"./v2/database": "./lib/v2/providers/database.js"
"./v2/database": "./lib/v2/providers/database.js",
"./v2/scheduler": "./lib/v2/providers/scheduler.js"
},
"typesVersions": {
"*": {
Expand Down Expand Up @@ -153,6 +154,9 @@
],
"v2/tasks": [
"lib/v2/providers/tasks"
],
"v2/scheduler": [
"lib/v2/providers/scheduler"
]
}
},
Expand Down
140 changes: 140 additions & 0 deletions spec/v2/providers/scheduler.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// The MIT License (MIT)
//
// Copyright (c) 2022 Firebase
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import { expect } from 'chai';
import * as schedule from '../../../src/v2/providers/scheduler';

describe('schedule', () => {
describe('getOpts', () => {
it('should handle a schedule', () => {
expect(schedule.getOpts('* * * * *')).to.deep.eq({
schedule: '* * * * *',
opts: {},
});
});

it('should handle full options', () => {
const options: schedule.ScheduleOptions = {
schedule: '* * * * *',
timeZone: 'utc',
retryCount: 3,
maxRetrySeconds: 1,
minBackoffSeconds: 2,
maxBackoffSeconds: 3,
maxDoublings: 4,
memory: '128MiB',
region: 'us-central1',
};

expect(schedule.getOpts(options)).to.deep.eq({
schedule: '* * * * *',
timeZone: 'utc',
retryCount: 3,
maxRetrySeconds: 1,
minBackoffSeconds: 2,
maxBackoffSeconds: 3,
maxDoublings: 4,
opts: {
...options,
memory: '128MiB',
region: 'us-central1',
},
});
});
});

describe('onSchedule', () => {
it('should create a schedule function given a schedule', () => {
const schfn = schedule.onSchedule('* * * * *', (event) => console.log(1));

expect(schfn.__endpoint).to.deep.eq({
platform: 'gcfv2',
labels: {},
scheduleTrigger: {
schedule: '* * * * *',
retryConfig: {},
},
});
expect(schfn.__requiredAPIs).to.deep.eq([
{
api: 'cloudscheduler.googleapis.com',
reason: 'Needed for scheduled functions.',
},
]);
});

it('should create a schedule function given options', () => {
const schfn = schedule.onSchedule(
{
schedule: '* * * * *',
timeZone: 'utc',
retryCount: 3,
maxRetrySeconds: 10,
minBackoffSeconds: 11,
maxBackoffSeconds: 12,
maxDoublings: 2,
region: 'us-central1',
labels: { key: 'val' },
},
(event) => {}
);

expect(schfn.__endpoint).to.deep.eq({
platform: 'gcfv2',
labels: { key: 'val' },
region: ['us-central1'],
scheduleTrigger: {
schedule: '* * * * *',
timeZone: 'utc',
retryConfig: {
retryCount: 3,
maxRetrySeconds: 10,
minBackoffSeconds: 11,
maxBackoffSeconds: 12,
maxDoublings: 2,
},
},
});
expect(schfn.__requiredAPIs).to.deep.eq([
{
api: 'cloudscheduler.googleapis.com',
reason: 'Needed for scheduled functions.',
},
]);
});

it('should have a .run method', () => {
const testObj = {
foo: 'bar',
};
const schfn = schedule.onSchedule('* * * * *', (event) => {
testObj.foo = 'newBar';
});

schfn.run('input' as any);

expect(testObj).to.deep.eq({
foo: 'newBar',
});
});
});
});

0 comments on commit eed7d59

Please sign in to comment.