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) 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 Test.Tasty (TestTree, defaultMain, testGroup)
import Tests.WaspBuildTest (waspBuild) import Tests.WaspBuildTest (waspBuild)
import Tests.WaspCompileTest (waspCompile) import Tests.WaspCompileTest (waspCompile)
import Tests.WaspJobTest (waspJob)
import Tests.WaspMigrateTest (waspMigrate) import Tests.WaspMigrateTest (waspMigrate)
import Tests.WaspNewTest (waspNew) import Tests.WaspNewTest (waspNew)
@ -23,5 +24,6 @@ tests = do
[ waspNew, [ waspNew,
waspCompile, waspCompile,
waspMigrate, waspMigrate,
waspBuild waspBuild,
waspJob
] ]

View File

@ -8,6 +8,7 @@ module ShellCommands
combineShellCommands, combineShellCommands,
cdIntoCurrentProject, cdIntoCurrentProject,
appendToWaspFile, appendToWaspFile,
createFile,
setDbToPSQL, setDbToPSQL,
waspCliNew, waspCliNew,
waspCliCompile, waspCliCompile,
@ -57,6 +58,15 @@ appendToWaspFile content =
-- NOTE: Using `show` to preserve newlines in string. -- NOTE: Using `show` to preserve newlines in string.
return $ "printf " ++ show (content ++ "\n") ++ " >> main.wasp" 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 -- 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. -- we do not have a `db` field. Consider better alternatives.
setDbToPSQL :: ShellCommandBuilder ShellCommand 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/Main.css
waspBuild/.wasp/build/server/src/ext-src/MainPage.js waspBuild/.wasp/build/server/src/ext-src/MainPage.js
waspBuild/.wasp/build/server/src/ext-src/waspLogo.png 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/index.js
waspBuild/.wasp/build/server/src/routes/operations/index.js waspBuild/.wasp/build/server/src/routes/operations/index.js
waspBuild/.wasp/build/server/src/server.js waspBuild/.wasp/build/server/src/server.js

View File

@ -111,6 +111,13 @@
], ],
"0f05a89eb945d6d7326110e88776e402833b356202b06d0a8bf652e118d3fd2f" "0f05a89eb945d6d7326110e88776e402833b356202b06d0a8bf652e118d3fd2f"
], ],
[
[
"file",
"server/src/jobs/PassthroughJobFactory.js"
],
"404443274a33f1104e41d47174edfe5290bf5219fdf7e730bbdce1ad24a93fda"
],
[ [
[ [
"file", "file",
@ -137,7 +144,7 @@
"file", "file",
"server/src/utils.js" "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) 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/Main.css
waspCompile/.wasp/out/server/src/ext-src/MainPage.js waspCompile/.wasp/out/server/src/ext-src/MainPage.js
waspCompile/.wasp/out/server/src/ext-src/waspLogo.png 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/index.js
waspCompile/.wasp/out/server/src/routes/operations/index.js waspCompile/.wasp/out/server/src/routes/operations/index.js
waspCompile/.wasp/out/server/src/server.js waspCompile/.wasp/out/server/src/server.js

View File

@ -111,6 +111,13 @@
], ],
"0f05a89eb945d6d7326110e88776e402833b356202b06d0a8bf652e118d3fd2f" "0f05a89eb945d6d7326110e88776e402833b356202b06d0a8bf652e118d3fd2f"
], ],
[
[
"file",
"server/src/jobs/PassthroughJobFactory.js"
],
"404443274a33f1104e41d47174edfe5290bf5219fdf7e730bbdce1ad24a93fda"
],
[ [
[ [
"file", "file",
@ -137,7 +144,7 @@
"file", "file",
"server/src/utils.js" "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) 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/Main.css
waspMigrate/.wasp/out/server/src/ext-src/MainPage.js waspMigrate/.wasp/out/server/src/ext-src/MainPage.js
waspMigrate/.wasp/out/server/src/ext-src/waspLogo.png 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/index.js
waspMigrate/.wasp/out/server/src/routes/operations/index.js waspMigrate/.wasp/out/server/src/routes/operations/index.js
waspMigrate/.wasp/out/server/src/server.js waspMigrate/.wasp/out/server/src/server.js

View File

@ -111,6 +111,13 @@
], ],
"0f05a89eb945d6d7326110e88776e402833b356202b06d0a8bf652e118d3fd2f" "0f05a89eb945d6d7326110e88776e402833b356202b06d0a8bf652e118d3fd2f"
], ],
[
[
"file",
"server/src/jobs/PassthroughJobFactory.js"
],
"404443274a33f1104e41d47174edfe5290bf5219fdf7e730bbdce1ad24a93fda"
],
[ [
[ [
"file", "file",
@ -137,7 +144,7 @@
"file", "file",
"server/src/utils.js" "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) 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 let someResource = undefined
export const getSomeResource = () => someResource export const getSomeResource = () => someResource
@ -6,6 +8,12 @@ const setup = async () => {
await new Promise(resolve => setTimeout(resolve, 2000)) await new Promise(resolve => setTimeout(resolve, 2000))
someResource = 'This resource is now set up.' someResource = 'This resource is now set up.'
console.log('Custom server setup done!') 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 export default setup

View File

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

View File

@ -13,7 +13,18 @@ import Data.Maybe
fromMaybe, fromMaybe,
isJust, 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 Wasp.AppSpec (AppSpec)
import qualified Wasp.AppSpec as AS import qualified Wasp.AppSpec as AS
import qualified Wasp.AppSpec.App as AS.App 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 qualified Wasp.Generator.ServerGenerator.Common as C
import Wasp.Generator.ServerGenerator.ConfigG (genConfigFile) import Wasp.Generator.ServerGenerator.ConfigG (genConfigFile)
import qualified Wasp.Generator.ServerGenerator.ExternalCodeGenerator as ServerExternalCodeGenerator 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.OperationsG (genOperations)
import Wasp.Generator.ServerGenerator.OperationsRoutesG (genOperationsRoutes) import Wasp.Generator.ServerGenerator.OperationsRoutesG (genOperationsRoutes)
import qualified Wasp.SemanticVersion as SV import qualified Wasp.SemanticVersion as SV
@ -55,6 +67,8 @@ genServer spec =
<++> genSrcDir spec <++> genSrcDir spec
<++> genExternalCodeDir ServerExternalCodeGenerator.generatorStrategy (AS.externalCodeFiles spec) <++> genExternalCodeDir ServerExternalCodeGenerator.generatorStrategy (AS.externalCodeFiles spec)
<++> genDotEnv spec <++> genDotEnv spec
<++> genJobs spec
<++> genJobFactories
genDotEnv :: AppSpec -> Generator [FileDraft] genDotEnv :: AppSpec -> Generator [FileDraft]
genDotEnv spec = return $ 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.Common
Wasp.Generator.ServerGenerator.ConfigG Wasp.Generator.ServerGenerator.ConfigG
Wasp.Generator.ServerGenerator.ExternalCodeGenerator Wasp.Generator.ServerGenerator.ExternalCodeGenerator
Wasp.Generator.ServerGenerator.JobGenerator
Wasp.Generator.ServerGenerator.OperationsG Wasp.Generator.ServerGenerator.OperationsG
Wasp.Generator.ServerGenerator.OperationsRoutesG Wasp.Generator.ServerGenerator.OperationsRoutesG
Wasp.Generator.ServerGenerator.Setup Wasp.Generator.ServerGenerator.Setup
@ -384,6 +385,7 @@ test-suite e2e-test
ShellCommands ShellCommands
Tests.WaspBuildTest Tests.WaspBuildTest
Tests.WaspCompileTest Tests.WaspCompileTest
Tests.WaspJobTest
Tests.WaspMigrateTest Tests.WaspMigrateTest
Tests.WaspNewTest Tests.WaspNewTest