Skip to content

Commit

Permalink
fix: supply next_run for all scheduled functions calls (#4163)
Browse files Browse the repository at this point in the history
* chore: add reproduction test

co-authored-by: Alexander Karagulamos <alex-contractor@netlify.com>

* fix: supply next_run for all calls

co-authored-by: Alexander Karagulamos <alex-contractor@netlify.com>

* fix: linting

Co-authored-by: Netlify Team Account 1 <netlify-team-account-1@users.noreply.github.com>
Co-authored-by: Alexander Karagulamos <alex-contractor@netlify.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
4 people committed Feb 1, 2022
1 parent 434cdb7 commit 1981515
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 12 deletions.
10 changes: 0 additions & 10 deletions src/commands/functions/functions-invoke.js
Expand Up @@ -3,7 +3,6 @@ const fs = require('fs')
const path = require('path')
const process = require('process')

const CronParser = require('cron-parser')
const inquirer = require('inquirer')
const fetch = require('node-fetch')

Expand Down Expand Up @@ -131,13 +130,6 @@ const getFunctionToTrigger = function (options, argumentName) {
return argumentName
}

const getNextRun = function (schedule) {
const cron = CronParser.parseExpression(schedule, {
tz: 'Etc/UTC',
})
return cron.next().toDate()
}

/**
* The functions:invoke command
* @param {string} nameArgument
Expand All @@ -164,10 +156,8 @@ const functionsInvoke = async (nameArgument, options, command) => {
let body = {}

if (functionObj.schedule) {
body.next_run = getNextRun(functionObj.schedule)
headers = {
'user-agent': CLOCKWORK_USERAGENT,
'X-NF-Event': 'schedule',
}
} else if (eventTriggeredFunctions.has(functionToTrigger)) {
/** handle event triggered fns */
Expand Down
17 changes: 17 additions & 0 deletions src/lib/functions/netlify-function.js
@@ -1,11 +1,20 @@
// @ts-check
const CronParser = require('cron-parser')

const { error: errorExit } = require('../../utils/command-helpers')

const BACKGROUND_SUFFIX = '-background'

// Returns a new set with all elements of `setA` that don't exist in `setB`.
const difference = (setA, setB) => new Set([...setA].filter((item) => !setB.has(item)))

const getNextRun = function (schedule) {
const cron = CronParser.parseExpression(schedule, {
tz: 'Etc/UTC',
})
return cron.next().toDate()
}

class NetlifyFunction {
constructor({
config,
Expand Down Expand Up @@ -51,6 +60,14 @@ class NetlifyFunction {
return Boolean(this.schedule)
}

async getNextRun() {
if (!(await this.isScheduled())) {
return null
}

return getNextRun(this.schedule)
}

// The `build` method transforms source files into invocable functions. Its
// return value is an object with:
//
Expand Down
25 changes: 23 additions & 2 deletions src/lib/functions/server.js
@@ -1,7 +1,14 @@
// @ts-check
const jwtDecode = require('jwt-decode')

const { NETLIFYDEVERR, NETLIFYDEVLOG, error: errorExit, getInternalFunctionsDir, log } = require('../../utils')
const {
CLOCKWORK_USERAGENT,
NETLIFYDEVERR,
NETLIFYDEVLOG,
error: errorExit,
getInternalFunctionsDir,
log,
} = require('../../utils')

const { handleBackgroundFunction, handleBackgroundFunctionResult } = require('./background')
const { createFormSubmissionHandler } = require('./form-submissions-handler')
Expand Down Expand Up @@ -107,7 +114,21 @@ const createHandler = function ({ functionsRegistry }) {

handleBackgroundFunctionResult(functionName, error)
} else if (await func.isScheduled()) {
const { error, result } = await func.invoke(event, clientContext)
const { error, result } = await func.invoke(
{
...event,
body: JSON.stringify({
next_run: await func.getNextRun(),
}),
isBase64Encoded: false,
headers: {
...event.headers,
'user-agent': CLOCKWORK_USERAGENT,
'X-NF-Event': 'schedule',
},
},
clientContext,
)

handleScheduledFunction({
error,
Expand Down
42 changes: 42 additions & 0 deletions tests/command.functions.test.js
Expand Up @@ -643,6 +643,48 @@ test('should serve helpful tips and tricks', async (t) => {
})
})

test('should emulate next_run for scheduled functions', async (t) => {
await withSiteBuilder('site-with-isc-ping-function', async (builder) => {
await builder
.withNetlifyToml({
config: { functions: { directory: 'functions' } },
})
// mocking until https://github.com/netlify/functions/pull/226 landed
.withContentFile({
path: 'node_modules/@netlify/functions/package.json',
content: `{}`,
})
.withContentFile({
path: 'node_modules/@netlify/functions/index.js',
content: `
module.exports.schedule = (schedule, handler) => handler
`,
})
.withContentFile({
path: 'functions/hello-world.js',
content: `
const { schedule } = require('@netlify/functions')
module.exports.handler = schedule("@daily", (event) => {
const { next_run } = JSON.parse(event.body)
return {
statusCode: !!next_run ? 200 : 400,
}
})
`.trim(),
})
.buildAsync()

await withDevServer({ cwd: builder.directory }, async (server) => {
const response = await got(`http://localhost:${server.port}/.netlify/functions/hello-world`, {
throwHttpErrors: false,
retry: null,
})

t.is(response.statusCode, 200)
})
})
})

test('should detect netlify-toml defined scheduled functions', async (t) => {
await withSiteBuilder('site-with-netlify-toml-ping-function', async (builder) => {
await builder
Expand Down

1 comment on commit 1981515

@github-actions
Copy link

Choose a reason for hiding this comment

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

📊 Benchmark results

Package size: 360 MB

Please sign in to comment.