@wmfs/tymly-schedule-plugin 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.circleci/config.yml +111 -0
- package/.releaserc.json +38 -0
- package/.version +1 -0
- package/CHANGELOG.md +41 -0
- package/LICENSE +21 -0
- package/README.md +21 -0
- package/lib/blueprints/schedule-blueprint/blueprint.json +9 -0
- package/lib/blueprints/schedule-blueprint/models/task.json +56 -0
- package/lib/components/services/schedule/doc/index.js +5 -0
- package/lib/components/services/schedule/index.js +225 -0
- package/lib/components/services/schedule/utils/datetime-to-rule.js +14 -0
- package/lib/components/services/schedule/utils/rule-to-datetime.js +11 -0
- package/lib/index.js +2 -0
- package/package.json +61 -0
- package/plugin.json +13 -0
- package/renovate.json +26 -0
- package/test/fixtures/cats-blueprint/blueprint.json +14 -0
- package/test/fixtures/cats-blueprint/functions/random-number.js +7 -0
- package/test/fixtures/cats-blueprint/models/cat-stats.json +10 -0
- package/test/fixtures/cats-blueprint/state-machine-schedules/two-second-cat-updates.json +7 -0
- package/test/fixtures/cats-blueprint/state-machines/send-cat-update.json +35 -0
- package/test/fixtures/cats-blueprint/state-machines/set-tiredness-level.json +29 -0
- package/test/rule-to-datetime-tests.js +103 -0
- package/test/schedule-tests.js +198 -0
- package/version_plugin +14 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
jobs:
|
|
3
|
+
unit test:
|
|
4
|
+
docker:
|
|
5
|
+
- image: cimg/node:lts
|
|
6
|
+
auth:
|
|
7
|
+
username: $DOCKERHUB_USERNAME
|
|
8
|
+
password: $DOCKERHUB_PASSWORD
|
|
9
|
+
environment:
|
|
10
|
+
TZ: "Europe/London"
|
|
11
|
+
PG_CONNECTION_STRING: postgres://postgres:postgres@localhost:5432/circle_test
|
|
12
|
+
TIMEOUT: 15000
|
|
13
|
+
- image: cimg/postgres:13.4-postgis
|
|
14
|
+
auth:
|
|
15
|
+
username: $DOCKERHUB_USERNAME
|
|
16
|
+
password: $DOCKERHUB_PASSWORD
|
|
17
|
+
environment:
|
|
18
|
+
POSTGRES_USER: postgres
|
|
19
|
+
POSTGRES_HOST_AUTH_METHOD: trust
|
|
20
|
+
POSTGRES_DB: circle_test
|
|
21
|
+
TZ: "Europe/London"
|
|
22
|
+
working_directory: ~/repo
|
|
23
|
+
steps:
|
|
24
|
+
- checkout
|
|
25
|
+
- restore_cache:
|
|
26
|
+
keys:
|
|
27
|
+
- v1-deps-{{ checksum "package.json" }}
|
|
28
|
+
- v1-deps-
|
|
29
|
+
- run:
|
|
30
|
+
name: install
|
|
31
|
+
command: |
|
|
32
|
+
npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN
|
|
33
|
+
npm install
|
|
34
|
+
- save_cache:
|
|
35
|
+
key: v1-deps-{{ checksum "package.json" }}
|
|
36
|
+
paths:
|
|
37
|
+
- node_modules
|
|
38
|
+
- run:
|
|
39
|
+
name: test
|
|
40
|
+
command: npm test
|
|
41
|
+
- run:
|
|
42
|
+
name: codecov
|
|
43
|
+
command: npm run coverage
|
|
44
|
+
lint:
|
|
45
|
+
docker:
|
|
46
|
+
- image: cimg/node:lts
|
|
47
|
+
auth:
|
|
48
|
+
username: $DOCKERHUB_USERNAME
|
|
49
|
+
password: $DOCKERHUB_PASSWORD
|
|
50
|
+
working_directory: ~/repo
|
|
51
|
+
steps:
|
|
52
|
+
- checkout
|
|
53
|
+
- run:
|
|
54
|
+
name: install standard
|
|
55
|
+
command: |
|
|
56
|
+
npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN
|
|
57
|
+
npm install
|
|
58
|
+
- run:
|
|
59
|
+
name: lint
|
|
60
|
+
command: npm run lint
|
|
61
|
+
release:
|
|
62
|
+
docker:
|
|
63
|
+
- image: cimg/node:lts
|
|
64
|
+
auth:
|
|
65
|
+
username: $DOCKERHUB_USERNAME
|
|
66
|
+
password: $DOCKERHUB_PASSWORD
|
|
67
|
+
environment:
|
|
68
|
+
TZ: "Europe/London"
|
|
69
|
+
working_directory: ~/repo
|
|
70
|
+
steps:
|
|
71
|
+
- checkout
|
|
72
|
+
- restore_cache:
|
|
73
|
+
keys:
|
|
74
|
+
- v1-deps-{{ checksum "package.json" }}
|
|
75
|
+
- v1-deps-
|
|
76
|
+
- run:
|
|
77
|
+
name: install
|
|
78
|
+
command: |
|
|
79
|
+
npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN
|
|
80
|
+
npm install
|
|
81
|
+
- save_cache:
|
|
82
|
+
key: v1-deps-{{ checksum "package.json" }}
|
|
83
|
+
paths:
|
|
84
|
+
- node_modules
|
|
85
|
+
- run:
|
|
86
|
+
name: release
|
|
87
|
+
command: |
|
|
88
|
+
chmod +x version_plugin
|
|
89
|
+
npx semantic-release
|
|
90
|
+
workflows:
|
|
91
|
+
version: 2
|
|
92
|
+
test_and_release:
|
|
93
|
+
jobs:
|
|
94
|
+
- unit test:
|
|
95
|
+
context:
|
|
96
|
+
- docker-hub-creds
|
|
97
|
+
- build-env-vars
|
|
98
|
+
- lint:
|
|
99
|
+
context:
|
|
100
|
+
- docker-hub-creds
|
|
101
|
+
- build-env-vars
|
|
102
|
+
- release:
|
|
103
|
+
context:
|
|
104
|
+
- docker-hub-creds
|
|
105
|
+
- build-env-vars
|
|
106
|
+
requires:
|
|
107
|
+
- unit test
|
|
108
|
+
- lint
|
|
109
|
+
filters:
|
|
110
|
+
branches:
|
|
111
|
+
only: master
|
package/.releaserc.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"branch": "master",
|
|
3
|
+
"analyzeCommits": {
|
|
4
|
+
"preset": "angular",
|
|
5
|
+
"releaseRules": [
|
|
6
|
+
{"type": "build", "scope":"deps", "release": "minor"}
|
|
7
|
+
]
|
|
8
|
+
},
|
|
9
|
+
"verifyConditions": [
|
|
10
|
+
"@semantic-release/changelog",
|
|
11
|
+
"@semantic-release/npm",
|
|
12
|
+
"@semantic-release/git"
|
|
13
|
+
],
|
|
14
|
+
"generateNotes": {
|
|
15
|
+
"preset": "metahub"
|
|
16
|
+
},
|
|
17
|
+
"prepare": [
|
|
18
|
+
{
|
|
19
|
+
"path": "@semantic-release/exec",
|
|
20
|
+
"cmd": "echo 'VERSION=${nextRelease.version}' > .version && ./version_plugin"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": "@semantic-release/changelog",
|
|
24
|
+
"changelogFile": "CHANGELOG.md"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"path": "@semantic-release/git",
|
|
28
|
+
"assets": ["CHANGELOG.md"]
|
|
29
|
+
},
|
|
30
|
+
"@semantic-release/npm"
|
|
31
|
+
],
|
|
32
|
+
"publish": [
|
|
33
|
+
"@semantic-release/npm",
|
|
34
|
+
"@semantic-release/github"
|
|
35
|
+
],
|
|
36
|
+
"success": ["@semantic-release/github"],
|
|
37
|
+
"fail": ["@semantic-release/github"]
|
|
38
|
+
}
|
package/.version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION=1.0.0
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# 1.0.0 (2022-03-15)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### ✨ Features
|
|
5
|
+
|
|
6
|
+
* ability to stop tasks ([1540f7a](https://github.com/wmfs/tymly-schedule-plugin/commit/1540f7ae85aac4ae9cdd87b3b12ee9ddadf6beb3))
|
|
7
|
+
* allow for timezone option ([6dc4082](https://github.com/wmfs/tymly-schedule-plugin/commit/6dc408296538b4f3815a5aad372b140ce5651529))
|
|
8
|
+
* allow rescheduling ([3b3f1f9](https://github.com/wmfs/tymly-schedule-plugin/commit/3b3f1f9e7e4b788a24ee45559e351a873d7c46dc))
|
|
9
|
+
* allow rescheduling, cron style or date style ([a55b75c](https://github.com/wmfs/tymly-schedule-plugin/commit/a55b75c5a639ad5aa49cbaea929fbc9ff82dea09))
|
|
10
|
+
* function to convert datetime back to rule ([b7b68e7](https://github.com/wmfs/tymly-schedule-plugin/commit/b7b68e7c4836001965243fef22773703553f574e))
|
|
11
|
+
* move rule/datetime converting into utils directory ([a425105](https://github.com/wmfs/tymly-schedule-plugin/commit/a42510596a87bb1fc617a18526bb9b1d14b92ab3))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### 🐛 Bug Fixes
|
|
15
|
+
|
|
16
|
+
* add log for execution error ([c49d261](https://github.com/wmfs/tymly-schedule-plugin/commit/c49d26104a72be787d361b1df2381ecbe5ca13f2))
|
|
17
|
+
* issues with scheduling for dates ([9c4b40f](https://github.com/wmfs/tymly-schedule-plugin/commit/9c4b40f9bf88ae7eaf8398a9634c065f935d91ae))
|
|
18
|
+
* persist schedule config from db ([5034eff](https://github.com/wmfs/tymly-schedule-plugin/commit/5034eff6dd953653b1094f132112992b25232413))
|
|
19
|
+
* rename some things ([b42cf44](https://github.com/wmfs/tymly-schedule-plugin/commit/b42cf447eb02e20435498c19c159fb64f787293e))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### 📦 Code Refactoring
|
|
23
|
+
|
|
24
|
+
* use node-schedule instead ([a8e57ff](https://github.com/wmfs/tymly-schedule-plugin/commit/a8e57ffe1a7d4728596eac187cfe7229f6a4d6a3))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### 🚨 Tests
|
|
28
|
+
|
|
29
|
+
* more tests ([87f9de1](https://github.com/wmfs/tymly-schedule-plugin/commit/87f9de1a1133d0ffe1421e699d260da3f677af85))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### ⚙️ Continuous Integrations
|
|
33
|
+
|
|
34
|
+
* add db config for circle ([4541e9d](https://github.com/wmfs/tymly-schedule-plugin/commit/4541e9d9df5794c55acc618ff8ce3fa5aa03a4ef))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
### ♻️ Chores
|
|
38
|
+
|
|
39
|
+
* disable tymly sync ([d0776ba](https://github.com/wmfs/tymly-schedule-plugin/commit/d0776ba009ae234a5644b64911142ef8abc7bc54))
|
|
40
|
+
* initial commit, simple state machine scheduling ([26d7163](https://github.com/wmfs/tymly-schedule-plugin/commit/26d7163980df98100bf561237af339123c710e8d))
|
|
41
|
+
* rename to tymly schedule plugin ([2937228](https://github.com/wmfs/tymly-schedule-plugin/commit/29372288cfeefba537afdb6d65e4c11709ddfbb0))
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017 West Midlands Fire Service
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# tymly-schedule-plugin
|
|
2
|
+
[](https://tymly.io/)
|
|
3
|
+
[](https://www.npmjs.com/package/@wmfs/tymly-schedule-plugin)
|
|
4
|
+
[](https://circleci.com/gh/wmfs/tymly-schedule-plugin)[](https://codecov.io/gh/wmfs/tymly-schedule-plugin)
|
|
5
|
+
[](https://www.codefactor.io/repository/github/wmfs/tymly-schedule-plugin)
|
|
6
|
+
[](https://dependabot.com/)
|
|
7
|
+
[](http://commitizen.github.io/cz-cli/)
|
|
8
|
+
[](https://standardjs.com)
|
|
9
|
+
[](https://github.com/wmfs/tymly-schedule-plugin/blob/master/LICENSE)
|
|
10
|
+
|
|
11
|
+
> Provides state machine scheduling for the Tymly framework
|
|
12
|
+
|
|
13
|
+
## <a name="install"></a>Install
|
|
14
|
+
```bash
|
|
15
|
+
$ npm install tymly-schedule-plugin --save
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## <a name="license"></a>License
|
|
20
|
+
|
|
21
|
+
MIT
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "State machine schedules",
|
|
3
|
+
"description": "Stores schedule configuration for state machines",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"primaryKey": [
|
|
6
|
+
"key"
|
|
7
|
+
],
|
|
8
|
+
"properties": {
|
|
9
|
+
"key": {
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
12
|
+
"stateMachineName": {
|
|
13
|
+
"type": "string"
|
|
14
|
+
},
|
|
15
|
+
"input": {
|
|
16
|
+
"type": "object"
|
|
17
|
+
},
|
|
18
|
+
"sendResponse": {
|
|
19
|
+
"type": "string"
|
|
20
|
+
},
|
|
21
|
+
"scheduleType": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"enum": [
|
|
24
|
+
"interval",
|
|
25
|
+
"datetime"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"interval": {
|
|
29
|
+
"type": "string"
|
|
30
|
+
},
|
|
31
|
+
"datetime": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"format": "date-time"
|
|
34
|
+
},
|
|
35
|
+
"startOnBoot": {
|
|
36
|
+
"type": "boolean"
|
|
37
|
+
},
|
|
38
|
+
"lastRanStart": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"format": "date-time"
|
|
41
|
+
},
|
|
42
|
+
"lastRanEnd": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"format": "date-time"
|
|
45
|
+
},
|
|
46
|
+
"lastRanExecutionName": {
|
|
47
|
+
"type": "string"
|
|
48
|
+
},
|
|
49
|
+
"totalRunCount": {
|
|
50
|
+
"type": "integer"
|
|
51
|
+
},
|
|
52
|
+
"status": {
|
|
53
|
+
"type": "string"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
const nodeSchedule = require('node-schedule')
|
|
2
|
+
const ruleToDatetime = require('./utils/rule-to-datetime')
|
|
3
|
+
const datetimeToRule = require('./utils/datetime-to-rule')
|
|
4
|
+
|
|
5
|
+
class CronService {
|
|
6
|
+
async boot (options) {
|
|
7
|
+
this.statebox = options.bootedServices.statebox
|
|
8
|
+
this.taskModel = options.bootedServices.storage.models.schedule_task
|
|
9
|
+
this.scheduledTasks = await this.findTasks(options.blueprintComponents.stateMachineSchedules)
|
|
10
|
+
|
|
11
|
+
for (const [key, task] of Object.entries(this.scheduledTasks)) {
|
|
12
|
+
if (task.startOnBoot === true) {
|
|
13
|
+
await this.startTask(key)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
} // boot
|
|
17
|
+
|
|
18
|
+
startTask (key) {
|
|
19
|
+
const task = this.scheduledTasks[key]
|
|
20
|
+
|
|
21
|
+
if (!task) {
|
|
22
|
+
console.log(`Cannot find schedule task to start '${key}'`)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
scheduleType,
|
|
28
|
+
interval,
|
|
29
|
+
datetime,
|
|
30
|
+
stateMachineName,
|
|
31
|
+
sendResponse,
|
|
32
|
+
input
|
|
33
|
+
} = task
|
|
34
|
+
|
|
35
|
+
const jobOptions = {
|
|
36
|
+
tz: 'UTC'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (scheduleType === 'interval') {
|
|
40
|
+
jobOptions.rule = interval
|
|
41
|
+
} else if (scheduleType === 'datetime') {
|
|
42
|
+
const rule = datetimeToRule(datetime)
|
|
43
|
+
|
|
44
|
+
Object
|
|
45
|
+
.entries(rule)
|
|
46
|
+
.forEach(([key, value]) => {
|
|
47
|
+
jobOptions[key] = value
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// todo: allow window for start/end datetimes
|
|
52
|
+
|
|
53
|
+
task.job = nodeSchedule.scheduleJob(
|
|
54
|
+
jobOptions,
|
|
55
|
+
() => {
|
|
56
|
+
console.log(`Running scheduled state machine ${stateMachineName}`)
|
|
57
|
+
const startedAt = new Date()
|
|
58
|
+
const executionOptions = {
|
|
59
|
+
instigatingClient: {
|
|
60
|
+
appName: 'tymly',
|
|
61
|
+
domain: 'tymly-schedule-plugin'
|
|
62
|
+
},
|
|
63
|
+
sendResponse
|
|
64
|
+
}
|
|
65
|
+
const cb = async (err, executionDescription) => {
|
|
66
|
+
if (err) {
|
|
67
|
+
console.log(`There was an error on execution of scheduled state machine ${stateMachineName}`)
|
|
68
|
+
}
|
|
69
|
+
await this.updateTaskOnExecution(key, executionDescription.executionName, startedAt)
|
|
70
|
+
}
|
|
71
|
+
this.statebox.startExecution(input, stateMachineName, executionOptions, cb)
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
this.scheduledTasks[key] = task
|
|
76
|
+
|
|
77
|
+
return this.updateTaskStatus(key, 'STARTED')
|
|
78
|
+
} // startTask
|
|
79
|
+
|
|
80
|
+
stopTask (key) {
|
|
81
|
+
const task = this.scheduledTasks[key]
|
|
82
|
+
|
|
83
|
+
if (!task) {
|
|
84
|
+
console.log(`Cannot find schedule task to stop '${key}'`)
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!task.job) {
|
|
89
|
+
console.log(`Cannot find job on task to stop '${key}'`)
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
task.job.cancel()
|
|
94
|
+
|
|
95
|
+
return this.updateTaskStatus(key, 'STOPPED')
|
|
96
|
+
} // stopTask
|
|
97
|
+
|
|
98
|
+
updateTaskStatus (key, status) {
|
|
99
|
+
return this.taskModel.update(
|
|
100
|
+
{
|
|
101
|
+
key,
|
|
102
|
+
status
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
setMissingPropertiesToNull: false
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
stopTasks () {
|
|
111
|
+
return Promise.all(
|
|
112
|
+
Object.keys(this.scheduledTasks).map(key => this.stopTask(key))
|
|
113
|
+
)
|
|
114
|
+
} // stopTasks
|
|
115
|
+
|
|
116
|
+
async findTasks (blueprintTasks) {
|
|
117
|
+
const tasks = {}
|
|
118
|
+
|
|
119
|
+
// From database
|
|
120
|
+
const persistedTasks = await this.taskModel.find({})
|
|
121
|
+
|
|
122
|
+
for (const config of persistedTasks) {
|
|
123
|
+
tasks[config.key] = config
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// From blueprint
|
|
127
|
+
for (const [key, config] of Object.entries(blueprintTasks)) {
|
|
128
|
+
if (tasks[key]) {
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// todo: recurrence rules?
|
|
133
|
+
|
|
134
|
+
if (typeof config.interval === 'string') {
|
|
135
|
+
config.scheduleType = 'interval'
|
|
136
|
+
config.datetime = null
|
|
137
|
+
} else if (typeof config.rule === 'object') {
|
|
138
|
+
config.scheduleType = 'datetime'
|
|
139
|
+
config.datetime = ruleToDatetime(config.rule)
|
|
140
|
+
config.interval = null
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
tasks[key] = config
|
|
144
|
+
|
|
145
|
+
await this.persistTask(key, config)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return tasks
|
|
149
|
+
} // findTasks
|
|
150
|
+
|
|
151
|
+
persistTask (key, config) {
|
|
152
|
+
return this.taskModel.create({
|
|
153
|
+
key,
|
|
154
|
+
stateMachineName: config.stateMachineName,
|
|
155
|
+
input: config.input,
|
|
156
|
+
sendResponse: config.sendResponse,
|
|
157
|
+
scheduleType: config.scheduleType,
|
|
158
|
+
interval: config.interval,
|
|
159
|
+
datetime: config.datetime,
|
|
160
|
+
startOnBoot: config.startOnBoot,
|
|
161
|
+
lastRanStart: null,
|
|
162
|
+
lastRanEnd: null,
|
|
163
|
+
lastRanExecutionName: null,
|
|
164
|
+
totalRunCount: 0,
|
|
165
|
+
status: null
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async updateTaskOnExecution (key, executionName, startedAt) {
|
|
170
|
+
const { totalRunCount } = await this.taskModel.findOne(
|
|
171
|
+
{
|
|
172
|
+
where: { key: { equals: key } },
|
|
173
|
+
fields: ['totalRunCount']
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return this.taskModel.update(
|
|
178
|
+
{
|
|
179
|
+
key,
|
|
180
|
+
lastRanStart: startedAt,
|
|
181
|
+
lastRanEnd: new Date(),
|
|
182
|
+
lastRanExecutionName: executionName,
|
|
183
|
+
totalRunCount: totalRunCount + 1
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
setMissingPropertiesToNull: false
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async stopAndUpdateTaskSchedule (key, rule) {
|
|
192
|
+
await this.stopTask(key)
|
|
193
|
+
|
|
194
|
+
if (typeof rule === 'string') {
|
|
195
|
+
this.scheduledTasks[key].interval = rule
|
|
196
|
+
this.scheduledTasks[key].datetime = null
|
|
197
|
+
this.scheduledTasks[key].scheduleType = 'interval'
|
|
198
|
+
} else if (typeof rule === 'object') {
|
|
199
|
+
this.scheduledTasks[key].interval = null
|
|
200
|
+
this.scheduledTasks[key].datetime = ruleToDatetime(rule)
|
|
201
|
+
this.scheduledTasks[key].scheduleType = 'datetime'
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return this.taskModel.update(
|
|
205
|
+
{
|
|
206
|
+
key,
|
|
207
|
+
interval: this.scheduledTasks[key].interval,
|
|
208
|
+
datetime: this.scheduledTasks[key].datetime,
|
|
209
|
+
scheduleType: this.scheduledTasks[key].scheduleType
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
setMissingPropertiesToNull: false
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async shutdown () {
|
|
218
|
+
await nodeSchedule.gracefulShutdown()
|
|
219
|
+
} // shutdown
|
|
220
|
+
} // class CronService
|
|
221
|
+
|
|
222
|
+
module.exports = {
|
|
223
|
+
serviceClass: CronService,
|
|
224
|
+
bootAfter: ['statebox']
|
|
225
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module.exports = function (datetime) {
|
|
2
|
+
if (!(datetime instanceof Date)) {
|
|
3
|
+
datetime = new Date()
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
year: datetime.getFullYear(),
|
|
8
|
+
month: datetime.getMonth(),
|
|
9
|
+
date: datetime.getDate(),
|
|
10
|
+
hour: datetime.getHours(),
|
|
11
|
+
minute: datetime.getMinutes(),
|
|
12
|
+
second: datetime.getSeconds()
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module.exports = function (rule = {}) {
|
|
2
|
+
const now = new Date()
|
|
3
|
+
return new Date(
|
|
4
|
+
isNaN(rule.year) ? now.getFullYear() : rule.year,
|
|
5
|
+
isNaN(rule.month) ? now.getMonth() : rule.month,
|
|
6
|
+
isNaN(rule.date) ? now.getDate() : rule.date,
|
|
7
|
+
isNaN(rule.hour) ? now.getHours() : rule.hour,
|
|
8
|
+
isNaN(rule.minute) ? now.getMinutes() : rule.minute,
|
|
9
|
+
isNaN(rule.second) ? now.getSeconds() : rule.second
|
|
10
|
+
)
|
|
11
|
+
}
|
package/lib/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wmfs/tymly-schedule-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Provides state machine scheduling for the Tymly framework",
|
|
5
|
+
"homepage": "https://github.com/wmfs/tymly-schedule-plugin#readme",
|
|
6
|
+
"author": "West Midlands Fire Service",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"tymly",
|
|
9
|
+
"plugin",
|
|
10
|
+
"task",
|
|
11
|
+
"scheduling",
|
|
12
|
+
"schedule",
|
|
13
|
+
"cron"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/wmfs/tymly-schedule-plugin.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/wmfs/tymly-schedule-plugin/issues"
|
|
22
|
+
},
|
|
23
|
+
"main": "./lib/index.js",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"node-schedule": "2.1.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@semantic-release/changelog": "6.0.1",
|
|
29
|
+
"@semantic-release/exec": "6.0.3",
|
|
30
|
+
"@semantic-release/git": "10.0.1",
|
|
31
|
+
"@wmfs/tymly": "1.235.1",
|
|
32
|
+
"@wmfs/tymly-pg-plugin": "1.268.0",
|
|
33
|
+
"chai": "4.3.6",
|
|
34
|
+
"codecov": "3.8.3",
|
|
35
|
+
"conventional-changelog-metahub": "4.0.1",
|
|
36
|
+
"cz-conventional-changelog": "3.3.0",
|
|
37
|
+
"dirty-chai": "2.0.1",
|
|
38
|
+
"mocha": "9.2.2",
|
|
39
|
+
"moment": "2.29.1",
|
|
40
|
+
"nyc": "15.1.0",
|
|
41
|
+
"semantic-release": "19.0.2",
|
|
42
|
+
"standard": "16.0.4"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"lint": "standard",
|
|
46
|
+
"test": "nyc mocha",
|
|
47
|
+
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
|
|
48
|
+
"semantic-release": "semantic-release"
|
|
49
|
+
},
|
|
50
|
+
"config": {
|
|
51
|
+
"commitizen": {
|
|
52
|
+
"path": "cz-conventional-changelog"
|
|
53
|
+
},
|
|
54
|
+
"tymly": {
|
|
55
|
+
"sync": false
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "restricted"
|
|
60
|
+
}
|
|
61
|
+
}
|
package/plugin.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "schedule-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"label": "Tymly Schedule Plugin",
|
|
5
|
+
"author": "WMFS",
|
|
6
|
+
"organisation": "West Midlands Fire Service",
|
|
7
|
+
"description": "Provides state machine scheduling for the Tymly framework",
|
|
8
|
+
"categories": [
|
|
9
|
+
"cron",
|
|
10
|
+
"scheduling",
|
|
11
|
+
"schedule"
|
|
12
|
+
]
|
|
13
|
+
}
|
package/renovate.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"config:base"
|
|
4
|
+
],
|
|
5
|
+
"dependencyDashboard": true,
|
|
6
|
+
"enabledManagers": ["npm"],
|
|
7
|
+
"packageRules": [
|
|
8
|
+
{
|
|
9
|
+
"matchDepTypes": ["devDependencies"],
|
|
10
|
+
"automerge": true,
|
|
11
|
+
"semanticCommitType": "build",
|
|
12
|
+
"semanticCommitScope": "deps-dev",
|
|
13
|
+
"labels": ["devDependencies"]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"matchDepTypes": ["dependencies"],
|
|
17
|
+
"semanticCommitType": "build",
|
|
18
|
+
"semanticCommitScope": "deps",
|
|
19
|
+
"labels": ["dependencies"],
|
|
20
|
+
"reviewers": ["team:tymly-reviewers"]
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"vulnerabilityAlerts": {
|
|
24
|
+
"labels": ["security"]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
|
|
3
|
+
"namespace": "tymlyTest",
|
|
4
|
+
"name": "cats",
|
|
5
|
+
"version": "1.0",
|
|
6
|
+
|
|
7
|
+
"label": "Cats",
|
|
8
|
+
"author": "WMFS",
|
|
9
|
+
|
|
10
|
+
"organisation": "Tymlys",
|
|
11
|
+
"description": "An example Tymly Blueprint to help model cat behaviour...",
|
|
12
|
+
"categories": ["cats", "pets"]
|
|
13
|
+
|
|
14
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Comment": "Send cat update",
|
|
3
|
+
"StartAt": "HappinessLevel",
|
|
4
|
+
"States": {
|
|
5
|
+
"HappinessLevel": {
|
|
6
|
+
"Type": "Task",
|
|
7
|
+
"Resource": "function:tymlyTest_randomNumber",
|
|
8
|
+
"ResultPath": "$.happinessLevel",
|
|
9
|
+
"Next": "HungerLevel"
|
|
10
|
+
},
|
|
11
|
+
"HungerLevel": {
|
|
12
|
+
"Type": "Task",
|
|
13
|
+
"Resource": "function:tymlyTest_randomNumber",
|
|
14
|
+
"ResultPath": "$.hungerLevel",
|
|
15
|
+
"Next": "Upserting"
|
|
16
|
+
},
|
|
17
|
+
"Upserting": {
|
|
18
|
+
"Type": "Task",
|
|
19
|
+
"InputPath": "$",
|
|
20
|
+
"Resource": "module:upserting",
|
|
21
|
+
"ResourceConfig": {
|
|
22
|
+
"modelId": "tymlyTest_catStats"
|
|
23
|
+
},
|
|
24
|
+
"End": true
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"restrictions": [
|
|
28
|
+
{
|
|
29
|
+
"roleId": "$everyone",
|
|
30
|
+
"allows": [
|
|
31
|
+
"*"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Comment": "Send cat update",
|
|
3
|
+
"StartAt": "TirednessLevel",
|
|
4
|
+
"States": {
|
|
5
|
+
"TirednessLevel": {
|
|
6
|
+
"Type": "Task",
|
|
7
|
+
"Resource": "function:tymlyTest_randomNumber",
|
|
8
|
+
"ResultPath": "$.tirednessLevel",
|
|
9
|
+
"Next": "Upserting"
|
|
10
|
+
},
|
|
11
|
+
"Upserting": {
|
|
12
|
+
"Type": "Task",
|
|
13
|
+
"InputPath": "$",
|
|
14
|
+
"Resource": "module:upserting",
|
|
15
|
+
"ResourceConfig": {
|
|
16
|
+
"modelId": "tymlyTest_catStats"
|
|
17
|
+
},
|
|
18
|
+
"End": true
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"restrictions": [
|
|
22
|
+
{
|
|
23
|
+
"roleId": "$everyone",
|
|
24
|
+
"allows": [
|
|
25
|
+
"*"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/* eslint-env mocha */
|
|
2
|
+
|
|
3
|
+
const expect = require('chai').expect
|
|
4
|
+
|
|
5
|
+
const ruleToDatetime = require('../lib/components/services/schedule/utils/rule-to-datetime')
|
|
6
|
+
const datetimeToRule = require('../lib/components/services/schedule/utils/datetime-to-rule')
|
|
7
|
+
|
|
8
|
+
describe('Rule to datetime tests', function () {
|
|
9
|
+
it('No options, should default to now', () => {
|
|
10
|
+
const now = new Date()
|
|
11
|
+
const datetime = ruleToDatetime()
|
|
12
|
+
|
|
13
|
+
expect(datetime.getFullYear()).to.eql(now.getFullYear())
|
|
14
|
+
expect(datetime.getMonth()).to.eql(now.getMonth())
|
|
15
|
+
expect(datetime.getDate()).to.eql(now.getDate())
|
|
16
|
+
expect(datetime.getHours()).to.eql(now.getHours())
|
|
17
|
+
expect(datetime.getMinutes()).to.eql(now.getMinutes())
|
|
18
|
+
expect(datetime.getSeconds()).to.eql(now.getSeconds())
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('Year only, the rest defaults to now', () => {
|
|
22
|
+
const now = new Date()
|
|
23
|
+
const year = now.getFullYear() + 2
|
|
24
|
+
|
|
25
|
+
const datetime = ruleToDatetime({ year })
|
|
26
|
+
|
|
27
|
+
expect(datetime.getFullYear()).to.eql(year)
|
|
28
|
+
expect(datetime.getMonth()).to.eql(now.getMonth())
|
|
29
|
+
expect(datetime.getDate()).to.eql(now.getDate())
|
|
30
|
+
expect(datetime.getHours()).to.eql(now.getHours())
|
|
31
|
+
expect(datetime.getMinutes()).to.eql(now.getMinutes())
|
|
32
|
+
expect(datetime.getSeconds()).to.eql(now.getSeconds())
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('All options passed in as integers', () => {
|
|
36
|
+
const rule = {
|
|
37
|
+
year: 2020,
|
|
38
|
+
month: 1,
|
|
39
|
+
date: 2,
|
|
40
|
+
hour: 12,
|
|
41
|
+
minute: 30,
|
|
42
|
+
second: 0
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const datetime = ruleToDatetime(rule)
|
|
46
|
+
|
|
47
|
+
expect(datetime.getFullYear()).to.eql(rule.year)
|
|
48
|
+
expect(datetime.getMonth()).to.eql(rule.month)
|
|
49
|
+
expect(datetime.getDate()).to.eql(rule.date)
|
|
50
|
+
expect(datetime.getHours()).to.eql(rule.hour)
|
|
51
|
+
expect(datetime.getMinutes()).to.eql(rule.minute)
|
|
52
|
+
expect(datetime.getSeconds()).to.eql(rule.second)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('All options passed in as strings', () => {
|
|
56
|
+
const rule = {
|
|
57
|
+
year: '2020',
|
|
58
|
+
month: '1',
|
|
59
|
+
date: '2',
|
|
60
|
+
hour: '12',
|
|
61
|
+
minute: '30',
|
|
62
|
+
second: '0'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const datetime = ruleToDatetime(rule)
|
|
66
|
+
|
|
67
|
+
expect(datetime.getFullYear().toString()).to.eql(rule.year)
|
|
68
|
+
expect(datetime.getMonth().toString()).to.eql(rule.month)
|
|
69
|
+
expect(datetime.getDate().toString()).to.eql(rule.date)
|
|
70
|
+
expect(datetime.getHours().toString()).to.eql(rule.hour)
|
|
71
|
+
expect(datetime.getMinutes().toString()).to.eql(rule.minute)
|
|
72
|
+
expect(datetime.getSeconds().toString()).to.eql(rule.second)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('Datetime to rule tests', function () {
|
|
77
|
+
it('No options, should default to now', () => {
|
|
78
|
+
const now = new Date()
|
|
79
|
+
const rule = datetimeToRule()
|
|
80
|
+
|
|
81
|
+
expect(rule.year).to.eql(now.getFullYear())
|
|
82
|
+
expect(rule.month).to.eql(now.getMonth())
|
|
83
|
+
expect(rule.date).to.eql(now.getDate())
|
|
84
|
+
expect(rule.hour).to.eql(now.getHours())
|
|
85
|
+
expect(rule.minute).to.eql(now.getMinutes())
|
|
86
|
+
expect(rule.second).to.eql(now.getSeconds())
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('Specific year, the rest should default to now', () => {
|
|
90
|
+
const datetime = new Date()
|
|
91
|
+
const year = datetime.getFullYear() + 2
|
|
92
|
+
datetime.setFullYear(year)
|
|
93
|
+
|
|
94
|
+
const rule = datetimeToRule(datetime)
|
|
95
|
+
|
|
96
|
+
expect(rule.year).to.eql(year)
|
|
97
|
+
expect(rule.month).to.eql(datetime.getMonth())
|
|
98
|
+
expect(rule.date).to.eql(datetime.getDate())
|
|
99
|
+
expect(rule.hour).to.eql(datetime.getHours())
|
|
100
|
+
expect(rule.minute).to.eql(datetime.getMinutes())
|
|
101
|
+
expect(rule.second).to.eql(datetime.getSeconds())
|
|
102
|
+
})
|
|
103
|
+
})
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/* eslint-env mocha */
|
|
2
|
+
|
|
3
|
+
const expect = require('chai').expect
|
|
4
|
+
const tymly = require('@wmfs/tymly')
|
|
5
|
+
const path = require('path')
|
|
6
|
+
|
|
7
|
+
const scheduleKey = 'tymlyTest_twoSecondCatUpdates'
|
|
8
|
+
|
|
9
|
+
describe('Tymly schedule tests', function () {
|
|
10
|
+
this.timeout(60000) // 60 second timeout
|
|
11
|
+
|
|
12
|
+
before(function () {
|
|
13
|
+
if (process.env.PG_CONNECTION_STRING && !/^postgres:\/\/[^:]+:[^@]+@(?:localhost|127\.0\.0\.1).*$/.test(process.env.PG_CONNECTION_STRING)) {
|
|
14
|
+
console.log(`Skipping tests due to unsafe PG_CONNECTION_STRING value (${process.env.PG_CONNECTION_STRING})`)
|
|
15
|
+
this.skip()
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
let tymlyService
|
|
20
|
+
let scheduleService
|
|
21
|
+
// let statebox
|
|
22
|
+
let catStatsModel
|
|
23
|
+
// let executionsModel
|
|
24
|
+
let taskModel
|
|
25
|
+
let client
|
|
26
|
+
|
|
27
|
+
let lastRunCount
|
|
28
|
+
|
|
29
|
+
it('boot Tymly', async () => {
|
|
30
|
+
const tymlyServices = await tymly.boot(
|
|
31
|
+
{
|
|
32
|
+
blueprintPaths: [
|
|
33
|
+
path.resolve(__dirname, './fixtures/cats-blueprint')
|
|
34
|
+
],
|
|
35
|
+
pluginPaths: [
|
|
36
|
+
path.resolve(__dirname, '..'),
|
|
37
|
+
require.resolve('@wmfs/tymly-pg-plugin')
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
tymlyService = tymlyServices.tymly
|
|
43
|
+
scheduleService = tymlyServices.schedule
|
|
44
|
+
// statebox = tymlyServices.statebox
|
|
45
|
+
taskModel = tymlyServices.storage.models.schedule_task
|
|
46
|
+
catStatsModel = tymlyServices.storage.models.tymlyTest_catStats
|
|
47
|
+
// executionsModel = tymlyServices.storage.models.tymly_execution
|
|
48
|
+
client = tymlyServices.storage.client
|
|
49
|
+
|
|
50
|
+
const catStats = await catStatsModel.find({})
|
|
51
|
+
expect(catStats.length).to.eql(0)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('check the task has persisted to the database', async () => {
|
|
55
|
+
const tasks = await taskModel.find({})
|
|
56
|
+
expect(tasks.length).to.eql(1)
|
|
57
|
+
expect(tasks[0].scheduleType).to.eql('interval')
|
|
58
|
+
expect(tasks[0].interval).to.eql('*/2 * * * * *')
|
|
59
|
+
expect(tasks[0].datetime).to.eql(null)
|
|
60
|
+
expect(tasks[0].status).to.eql('STARTED')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('wait 10 seconds, let the updates run for a bit', done => setTimeout(done, 10000))
|
|
64
|
+
|
|
65
|
+
it('stop cat updates', async () => {
|
|
66
|
+
await scheduleService.stopTask(scheduleKey)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('check the state machine has run multiple times', async () => {
|
|
70
|
+
const tasks = await taskModel.find({})
|
|
71
|
+
expect(tasks.length).to.eql(1)
|
|
72
|
+
expect(tasks[0].scheduleType).to.eql('interval')
|
|
73
|
+
expect(tasks[0].interval).to.eql('*/2 * * * * *')
|
|
74
|
+
expect(tasks[0].datetime).to.eql(null)
|
|
75
|
+
expect(tasks[0].status).to.eql('STOPPED')
|
|
76
|
+
|
|
77
|
+
const { totalRunCount } = tasks[0]
|
|
78
|
+
|
|
79
|
+
lastRunCount = totalRunCount
|
|
80
|
+
|
|
81
|
+
// Waited 10 seconds and runs every 2 seconds so can be 5 or 6
|
|
82
|
+
expect(lastRunCount).to.be.greaterThanOrEqual(5)
|
|
83
|
+
expect(lastRunCount).to.be.lessThanOrEqual(6)
|
|
84
|
+
|
|
85
|
+
const catStats = await catStatsModel.find({})
|
|
86
|
+
expect(catStats.length).to.eql(lastRunCount)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('wait 5 seconds', done => setTimeout(done, 5000))
|
|
90
|
+
|
|
91
|
+
it('ensure scheduled task has not run again', async () => {
|
|
92
|
+
const tasks = await taskModel.find({})
|
|
93
|
+
expect(tasks.length).to.eql(1)
|
|
94
|
+
expect(tasks[0].scheduleType).to.eql('interval')
|
|
95
|
+
expect(tasks[0].interval).to.eql('*/2 * * * * *')
|
|
96
|
+
expect(tasks[0].datetime).to.eql(null)
|
|
97
|
+
expect(tasks[0].status).to.eql('STOPPED')
|
|
98
|
+
|
|
99
|
+
expect(tasks[0].totalRunCount).to.eql(lastRunCount)
|
|
100
|
+
|
|
101
|
+
const catStats = await catStatsModel.find({})
|
|
102
|
+
expect(catStats.length).to.eql(lastRunCount)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('update task to specific date/time', async () => {
|
|
106
|
+
const datetime = new Date()
|
|
107
|
+
datetime.setSeconds(datetime.getSeconds() + 10)
|
|
108
|
+
|
|
109
|
+
await scheduleService.stopAndUpdateTaskSchedule(
|
|
110
|
+
scheduleKey,
|
|
111
|
+
{
|
|
112
|
+
year: datetime.getFullYear(),
|
|
113
|
+
month: datetime.getMonth(),
|
|
114
|
+
date: datetime.getDate(),
|
|
115
|
+
hour: datetime.getHours(),
|
|
116
|
+
minute: datetime.getMinutes(),
|
|
117
|
+
second: datetime.getSeconds()
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
await scheduleService.startTask(scheduleKey)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('wait 15 seconds', done => setTimeout(done, 15000))
|
|
125
|
+
|
|
126
|
+
it('check the state machine has run once more', async () => {
|
|
127
|
+
lastRunCount++
|
|
128
|
+
|
|
129
|
+
const tasks = await taskModel.find({})
|
|
130
|
+
expect(tasks.length).to.eql(1)
|
|
131
|
+
expect(tasks[0].interval).to.eql(null)
|
|
132
|
+
expect(tasks[0].datetime).to.not.eql(null)
|
|
133
|
+
expect(tasks[0].scheduleType).to.eql('datetime')
|
|
134
|
+
expect(tasks[0].totalRunCount).to.eql(lastRunCount)
|
|
135
|
+
expect(tasks[0].status).to.eql('STARTED')
|
|
136
|
+
|
|
137
|
+
const catStats = await catStatsModel.find({})
|
|
138
|
+
expect(catStats.length).to.eql(lastRunCount)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('wait 10 seconds', done => setTimeout(done, 10000))
|
|
142
|
+
|
|
143
|
+
it('ensure scheduled task has not run again', async () => {
|
|
144
|
+
const tasks = await taskModel.find({})
|
|
145
|
+
expect(tasks.length).to.eql(1)
|
|
146
|
+
expect(tasks[0].status).to.eql('STARTED')
|
|
147
|
+
|
|
148
|
+
expect(tasks[0].totalRunCount).to.eql(lastRunCount)
|
|
149
|
+
|
|
150
|
+
const catStats = await catStatsModel.find({})
|
|
151
|
+
expect(catStats.length).to.eql(lastRunCount)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('shutdown Tymly', async () => {
|
|
155
|
+
await tymlyService.shutdown()
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('re-boot tymly', async () => {
|
|
159
|
+
const tymlyServices = await tymly.boot(
|
|
160
|
+
{
|
|
161
|
+
blueprintPaths: [
|
|
162
|
+
path.resolve(__dirname, './fixtures/cats-blueprint')
|
|
163
|
+
],
|
|
164
|
+
pluginPaths: [
|
|
165
|
+
path.resolve(__dirname, '..'),
|
|
166
|
+
require.resolve('@wmfs/tymly-pg-plugin')
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
tymlyService = tymlyServices.tymly
|
|
172
|
+
scheduleService = tymlyServices.schedule
|
|
173
|
+
taskModel = tymlyServices.storage.models.schedule_task
|
|
174
|
+
catStatsModel = tymlyServices.storage.models.tymlyTest_catStats
|
|
175
|
+
client = tymlyServices.storage.client
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('check blueprint tasks not overwritten updates', async () => {
|
|
179
|
+
const tasks = await taskModel.find({})
|
|
180
|
+
expect(tasks.length).to.eql(1)
|
|
181
|
+
expect(tasks[0].key).to.eql(scheduleKey)
|
|
182
|
+
expect(tasks[0].stateMachineName).to.eql('tymlyTest_sendCatUpdate')
|
|
183
|
+
expect(tasks[0].scheduleType).to.eql('datetime')
|
|
184
|
+
expect(tasks[0].interval).to.eql(null)
|
|
185
|
+
expect(tasks[0].status).to.eql('STARTED')
|
|
186
|
+
expect(tasks[0].totalRunCount).to.eql(lastRunCount)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('clean up data', async () => {
|
|
190
|
+
await client.query('DROP SCHEMA tymly CASCADE;')
|
|
191
|
+
await client.query('DROP SCHEMA tymly_test CASCADE;')
|
|
192
|
+
await client.query('DROP SCHEMA schedule CASCADE;')
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('shutdown Tymly again', async () => {
|
|
196
|
+
await tymlyService.shutdown()
|
|
197
|
+
})
|
|
198
|
+
})
|
package/version_plugin
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
. .version
|
|
4
|
+
echo $VERSION
|
|
5
|
+
version=$VERSION
|
|
6
|
+
sudo apt-get install jq
|
|
7
|
+
sudo jq --arg version "$version" '.version=$version' plugin.json > plugin_edit.json && mv plugin_edit.json plugin.json
|
|
8
|
+
git config --global user.name "$GIT_COMMITTER_NAME"
|
|
9
|
+
git config --global user.email "$GIT_COMMITTER_EMAIL"
|
|
10
|
+
git remote set-url origin https://${GIT_COMMITTER_NAME}:${GH_TOKEN}@github.com/wmfs/tymly-schedule-plugin.git
|
|
11
|
+
git checkout master
|
|
12
|
+
git add plugin.json
|
|
13
|
+
git commit -m "chore(version): plugin $version [skip ci]"
|
|
14
|
+
git push
|