Adds a simple pass-through Jobs implementation. (#553)

This commit is contained in:
Shayne Czyzewski 2022-04-13 12:30:29 -04:00 committed by GitHub
parent 801ba69e54
commit adcb15a41c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 306 additions and 5 deletions

View File

@ -0,0 +1,26 @@
import { sleep } from '../utils.js'
/**
* "Immutable-ish" passthrough job wrapper, mainly to be used for testing.
*/
class PassthroughJob {
constructor(values) {
this.perform = () => { }
this.delayMs = 0
Object.assign(this, values)
}
delay(ms) {
return new PassthroughJob({ ...this, delayMs: ms })
}
performAsync(args) {
return {
result: sleep(this.delayMs).then(() => this.perform(args))
}
}
}
export function jobFactory(fn) {
return new PassthroughJob({ perform: fn })
}

View File

@ -0,0 +1,5 @@
{{={= =}=}}
import { jobFactory } from './{= jobFactoryName =}.js'
{=& jobPerformFnImportStatement =}
export const {= jobName =} = jobFactory({= jobPerformFnName =})

View File

@ -43,3 +43,5 @@ export const prismaErrorToHttpError = (e) => {
return new HttpError(500)
}
}
export const sleep = ms => new Promise(r => setTimeout(r, ms))

View File

@ -3,6 +3,7 @@ import System.Info (os)
import Test.Tasty (TestTree, defaultMain, testGroup)
import Tests.WaspBuildTest (waspBuild)
import Tests.WaspCompileTest (waspCompile)
import Tests.WaspJobTest (waspJob)
import Tests.WaspMigrateTest (waspMigrate)
import Tests.WaspNewTest (waspNew)
@ -23,5 +24,6 @@ tests = do
[ waspNew,
waspCompile,
waspMigrate,
waspBuild
waspBuild,
waspJob
]

View File

@ -8,6 +8,7 @@ module ShellCommands
combineShellCommands,
cdIntoCurrentProject,
appendToWaspFile,
createFile,
setDbToPSQL,
waspCliNew,
waspCliCompile,
@ -57,6 +58,15 @@ appendToWaspFile content =
-- NOTE: Using `show` to preserve newlines in string.
return $ "printf " ++ show (content ++ "\n") ++ " >> main.wasp"
-- NOTE: Pretty fragile. Can't handle spaces in args, *nix only, etc.
createFile :: String -> FilePath -> String -> ShellCommandBuilder ShellCommand
createFile content relDirFp filename = return $ combineShellCommands [createParentDir, writeContentsToFile]
where
createParentDir = "mkdir -p ./" ++ relDirFp
destinationFile = "./" ++ relDirFp ++ "/" ++ filename
contents = show (content ++ "\n")
writeContentsToFile = unwords ["printf", contents, ">", destinationFile]
-- NOTE: This is fragile and will likely break in future. Assumes `app` decl is first line and by default
-- we do not have a `db` field. Consider better alternatives.
setDbToPSQL :: ShellCommandBuilder ShellCommand

View File

@ -0,0 +1,31 @@
module Tests.WaspJobTest (waspJob) where
import GoldenTest (GoldenTest, makeGoldenTest)
import ShellCommands
( appendToWaspFile,
cdIntoCurrentProject,
createFile,
waspCliCompile,
waspCliNew,
)
waspJob :: GoldenTest
waspJob = do
let entityDecl =
" job MySpecialJob { \n\
\ perform: import { foo } from \"@ext/jobs/bar.js\" \n\
\ } \n"
let jobFile =
" export const foo = async (args) => { \n\
\ return 1 \n\
\ } \n"
makeGoldenTest "waspJob" $
sequence
[ waspCliNew,
cdIntoCurrentProject,
appendToWaspFile entityDecl,
createFile jobFile "./ext/jobs" "bar.js",
waspCliCompile
]

View File

@ -15,6 +15,7 @@ waspBuild/.wasp/build/server/src/dbClient.js
waspBuild/.wasp/build/server/src/ext-src/Main.css
waspBuild/.wasp/build/server/src/ext-src/MainPage.js
waspBuild/.wasp/build/server/src/ext-src/waspLogo.png
waspBuild/.wasp/build/server/src/jobs/PassthroughJobFactory.js
waspBuild/.wasp/build/server/src/routes/index.js
waspBuild/.wasp/build/server/src/routes/operations/index.js
waspBuild/.wasp/build/server/src/server.js

View File

@ -111,6 +111,13 @@
],
"0f05a89eb945d6d7326110e88776e402833b356202b06d0a8bf652e118d3fd2f"
],
[
[
"file",
"server/src/jobs/PassthroughJobFactory.js"
],
"404443274a33f1104e41d47174edfe5290bf5219fdf7e730bbdce1ad24a93fda"
],
[
[
"file",
@ -137,7 +144,7 @@
"file",
"server/src/utils.js"
],
"b75212f40dffee19d17e7dfeb9d3daf936c680abc2467d990398e6c4d8d01d0a"
"68a5794f55e24b303d81456a1181a3a2cd70773f6ebc4e7a63dac064834aa8e9"
],
[
[

View File

@ -0,0 +1,26 @@
import { sleep } from '../utils.js'
/**
* "Immutable-ish" passthrough job wrapper, mainly to be used for testing.
*/
class PassthroughJob {
constructor(values) {
this.perform = () => { }
this.delayMs = 0
Object.assign(this, values)
}
delay(ms) {
return new PassthroughJob({ ...this, delayMs: ms })
}
performAsync(args) {
return {
result: sleep(this.delayMs).then(() => this.perform(args))
}
}
}
export function jobFactory(fn) {
return new PassthroughJob({ perform: fn })
}

View File

@ -43,3 +43,5 @@ export const prismaErrorToHttpError = (e) => {
return new HttpError(500)
}
}
export const sleep = ms => new Promise(r => setTimeout(r, ms))

View File

@ -15,6 +15,7 @@ waspCompile/.wasp/out/server/src/dbClient.js
waspCompile/.wasp/out/server/src/ext-src/Main.css
waspCompile/.wasp/out/server/src/ext-src/MainPage.js
waspCompile/.wasp/out/server/src/ext-src/waspLogo.png
waspCompile/.wasp/out/server/src/jobs/PassthroughJobFactory.js
waspCompile/.wasp/out/server/src/routes/index.js
waspCompile/.wasp/out/server/src/routes/operations/index.js
waspCompile/.wasp/out/server/src/server.js

View File

@ -111,6 +111,13 @@
],
"0f05a89eb945d6d7326110e88776e402833b356202b06d0a8bf652e118d3fd2f"
],
[
[
"file",
"server/src/jobs/PassthroughJobFactory.js"
],
"404443274a33f1104e41d47174edfe5290bf5219fdf7e730bbdce1ad24a93fda"
],
[
[
"file",
@ -137,7 +144,7 @@
"file",
"server/src/utils.js"
],
"b75212f40dffee19d17e7dfeb9d3daf936c680abc2467d990398e6c4d8d01d0a"
"68a5794f55e24b303d81456a1181a3a2cd70773f6ebc4e7a63dac064834aa8e9"
],
[
[

View File

@ -0,0 +1,26 @@
import { sleep } from '../utils.js'
/**
* "Immutable-ish" passthrough job wrapper, mainly to be used for testing.
*/
class PassthroughJob {
constructor(values) {
this.perform = () => { }
this.delayMs = 0
Object.assign(this, values)
}
delay(ms) {
return new PassthroughJob({ ...this, delayMs: ms })
}
performAsync(args) {
return {
result: sleep(this.delayMs).then(() => this.perform(args))
}
}
}
export function jobFactory(fn) {
return new PassthroughJob({ perform: fn })
}

View File

@ -43,3 +43,5 @@ export const prismaErrorToHttpError = (e) => {
return new HttpError(500)
}
}
export const sleep = ms => new Promise(r => setTimeout(r, ms))

View File

@ -20,6 +20,7 @@ waspMigrate/.wasp/out/server/src/dbClient.js
waspMigrate/.wasp/out/server/src/ext-src/Main.css
waspMigrate/.wasp/out/server/src/ext-src/MainPage.js
waspMigrate/.wasp/out/server/src/ext-src/waspLogo.png
waspMigrate/.wasp/out/server/src/jobs/PassthroughJobFactory.js
waspMigrate/.wasp/out/server/src/routes/index.js
waspMigrate/.wasp/out/server/src/routes/operations/index.js
waspMigrate/.wasp/out/server/src/server.js

View File

@ -111,6 +111,13 @@
],
"0f05a89eb945d6d7326110e88776e402833b356202b06d0a8bf652e118d3fd2f"
],
[
[
"file",
"server/src/jobs/PassthroughJobFactory.js"
],
"404443274a33f1104e41d47174edfe5290bf5219fdf7e730bbdce1ad24a93fda"
],
[
[
"file",
@ -137,7 +144,7 @@
"file",
"server/src/utils.js"
],
"b75212f40dffee19d17e7dfeb9d3daf936c680abc2467d990398e6c4d8d01d0a"
"68a5794f55e24b303d81456a1181a3a2cd70773f6ebc4e7a63dac064834aa8e9"
],
[
[

View File

@ -0,0 +1,26 @@
import { sleep } from '../utils.js'
/**
* "Immutable-ish" passthrough job wrapper, mainly to be used for testing.
*/
class PassthroughJob {
constructor(values) {
this.perform = () => { }
this.delayMs = 0
Object.assign(this, values)
}
delay(ms) {
return new PassthroughJob({ ...this, delayMs: ms })
}
performAsync(args) {
return {
result: sleep(this.delayMs).then(() => this.perform(args))
}
}
}
export function jobFactory(fn) {
return new PassthroughJob({ perform: fn })
}

View File

@ -43,3 +43,5 @@ export const prismaErrorToHttpError = (e) => {
return new HttpError(500)
}
}
export const sleep = ms => new Promise(r => setTimeout(r, ms))

View File

@ -0,0 +1,7 @@
import { sleep } from '@wasp/utils.js'
export const foo = async (args) => {
console.log("Inside Job bar's callback foo: ", args)
await sleep(4000)
return "I am the Job's result!"
}

View File

@ -1,3 +1,5 @@
import { mySpecialJob } from '@wasp/jobs/mySpecialJob.js'
let someResource = undefined
export const getSomeResource = () => someResource
@ -6,6 +8,12 @@ const setup = async () => {
await new Promise(resolve => setTimeout(resolve, 2000))
someResource = 'This resource is now set up.'
console.log('Custom server setup done!')
console.log('Kicking off Job...')
// Or: const runningJob = mySpecialJob.delay(1000).performAsync({ something: "here" })
const runningJob = mySpecialJob.performAsync({ something: "here" })
console.log('Waiting for Job result...')
runningJob.result.then(res => { console.log(res) }).finally(() => { console.log("Job done!") })
}
export default setup

View File

@ -107,3 +107,7 @@ action toggleAllTasks {
fn: import { toggleAllTasks } from "@ext/actions.js",
entities: [Task]
}
job mySpecialJob {
perform: import { foo } from "@ext/jobs/bar.js"
}

View File

@ -13,7 +13,18 @@ import Data.Maybe
fromMaybe,
isJust,
)
import StrongPath (Dir, File', Path, Path', Posix, Rel, reldir, reldirP, relfile, (</>))
import StrongPath
( Dir,
File',
Path,
Path',
Posix,
Rel,
reldir,
reldirP,
relfile,
(</>),
)
import Wasp.AppSpec (AppSpec)
import qualified Wasp.AppSpec as AS
import qualified Wasp.AppSpec.App as AS.App
@ -38,6 +49,7 @@ import Wasp.Generator.ServerGenerator.Common
import qualified Wasp.Generator.ServerGenerator.Common as C
import Wasp.Generator.ServerGenerator.ConfigG (genConfigFile)
import qualified Wasp.Generator.ServerGenerator.ExternalCodeGenerator as ServerExternalCodeGenerator
import Wasp.Generator.ServerGenerator.JobGenerator (genJobFactories, genJobs)
import Wasp.Generator.ServerGenerator.OperationsG (genOperations)
import Wasp.Generator.ServerGenerator.OperationsRoutesG (genOperationsRoutes)
import qualified Wasp.SemanticVersion as SV
@ -55,6 +67,8 @@ genServer spec =
<++> genSrcDir spec
<++> genExternalCodeDir ServerExternalCodeGenerator.generatorStrategy (AS.externalCodeFiles spec)
<++> genDotEnv spec
<++> genJobs spec
<++> genJobFactories
genDotEnv :: AppSpec -> Generator [FileDraft]
genDotEnv spec = return $

View File

@ -0,0 +1,82 @@
module Wasp.Generator.ServerGenerator.JobGenerator
( genJobs,
genJobFactories,
)
where
import Data.Aeson (object, (.=))
import Data.Maybe
( fromJust,
)
import qualified GHC.Enum as Enum
import StrongPath
( Dir,
File',
Path,
Path',
Posix,
Rel,
parseRelFile,
reldir,
reldirP,
relfile,
(</>),
)
import Wasp.AppSpec (AppSpec, getJobs)
import Wasp.AppSpec.Job (Job (perform))
import Wasp.Generator.ExternalCodeGenerator.Common (GeneratedExternalCodeDir)
import Wasp.Generator.FileDraft (FileDraft)
import Wasp.Generator.JsImport (getJsImportDetailsForExtFnImport)
import Wasp.Generator.Monad (Generator)
import Wasp.Generator.ServerGenerator.Common
( ServerSrcDir,
)
import qualified Wasp.Generator.ServerGenerator.Common as C
-- | TODO: Make this not hardcoded!
relPosixPathFromJobFileToExtSrcDir :: Path Posix (Rel (Dir ServerSrcDir)) (Dir GeneratedExternalCodeDir)
relPosixPathFromJobFileToExtSrcDir = [reldirP|../ext-src|]
data JobFactory = PassthroughJobFactory
deriving (Show, Eq, Ord, Enum, Enum.Bounded)
jobFactories :: [JobFactory]
jobFactories = enumFrom minBound :: [JobFactory]
-- TODO: In future we will detect what type of JobFactory
-- to use based on what the Job is using.
jobFactoryForJob :: Job -> JobFactory
jobFactoryForJob _ = PassthroughJobFactory
jobFactoryFilePath :: JobFactory -> Path' (Rel d) File'
jobFactoryFilePath PassthroughJobFactory = [relfile|src/jobs/PassthroughJobFactory.js|]
genJobs :: AppSpec -> Generator [FileDraft]
genJobs spec = return $ genJob <$> getJobs spec
where
tmplFile = C.asTmplFile [relfile|src/jobs/_job.js|]
dstFileFromJobName jobName = C.asServerFile $ [reldir|src/jobs/|] </> fromJust (parseRelFile $ jobName ++ ".js")
genJob :: (String, Job) -> FileDraft
genJob (jobName, job) =
let (jobPerformFnName, jobPerformFnImportStatement) = getJsImportDetailsForExtFnImport relPosixPathFromJobFileToExtSrcDir $ perform job
in C.mkTmplFdWithDstAndData
tmplFile
(dstFileFromJobName jobName)
( Just $
object
[ "jobName" .= jobName,
"jobPerformFnName" .= jobPerformFnName,
"jobPerformFnImportStatement" .= jobPerformFnImportStatement,
"jobFactoryName" .= show (jobFactoryForJob job)
]
)
genJobFactories :: Generator [FileDraft]
genJobFactories = return $ genJobFactory <$> jobFactories
where
genJobFactory :: JobFactory -> FileDraft
genJobFactory jobFactory =
let jobFactoryFp = jobFactoryFilePath jobFactory
sourceTemplateFp = C.asTmplFile jobFactoryFp
destinationServerFp = C.asServerFile jobFactoryFp
in C.mkTmplFdWithDstAndData sourceTemplateFp destinationServerFp Nothing

View File

@ -208,6 +208,7 @@ library
Wasp.Generator.ServerGenerator.Common
Wasp.Generator.ServerGenerator.ConfigG
Wasp.Generator.ServerGenerator.ExternalCodeGenerator
Wasp.Generator.ServerGenerator.JobGenerator
Wasp.Generator.ServerGenerator.OperationsG
Wasp.Generator.ServerGenerator.OperationsRoutesG
Wasp.Generator.ServerGenerator.Setup
@ -384,6 +385,7 @@ test-suite e2e-test
ShellCommands
Tests.WaspBuildTest
Tests.WaspCompileTest
Tests.WaspJobTest
Tests.WaspMigrateTest
Tests.WaspNewTest